Laravel Eloquent getting data (with relationship) confusion - laravel

I am a bit confused on how to properly pull the data from Eloquent Models. I did read the documentation, but this is not mentioned anywhere or I missed it.
Let's assume we have two models, Client and Country (note: I only added relevant code, otherwise this question would be quite long):
Client:
<?php
namespace App\Models\Client;
use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* #property int id
* #property int country_id
*/
class Client extends BaseModel
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
...
'country_id',
...
];
/**
* Return Country relationship
*
* #return BelongsTo
*/
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
}
Country:
<?php
namespace App\Models\Country;
use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* #property int id
*/
class Country extends BaseModel
{
public function clients(): hasMany
{
return $this->hasMany(Client::class);
}
}
Nothing special, two normal Models. Now, when I try to pull the data I have a few issues:
dd(Country::findOrFail(32)); // CORRECT: returns selected country
dd(Country::findOrFail(32)->first()); // WRONG: returns first country in database, not the one with ID of 32, EXPECTED: single country object with selected country
dd(Country::findOrFail(32)->get()); // WRONG: returns all countries in database, EXPECTED: an array of objects with one country in it
dd(Country::with('clients')->findOrFail(32)->first()); // WRONG: returns first country in the database and not the one with ID of 32, relationship is also not correct, EXPECTED: object with selected country with valid relationship (clients) as array of objects
dd(Country::findOrFail(32)->clients()); // WRONG: returns HasMany relationship, no data, EXPECTED: an array of clients as objects
dd(Country::findOrFail(32)->clients()->get()); // CORRECT: returns valid data, array of clients as objects
dd(Country::findOrFail(32)->clients()->first()); // CORRECT: returns first client for selected country
So, why are all WRONG wrong ones? Am I interpreting the documentation wrongly? The most frustrating is dd(Country::with('clients')->findOrFail(32)->first()) since now I have no means on how to filter based on selected country and then also provide a list of clients in that country. I thought that Eloquent is quite advanced, so I assume that I am doing something wrong and would appreciate some guidance.
Please note that in latest example I also tried reversing query as dd(Country::findOrFail(32)->with('clients')->first()), same result.

In my opinion, you are mixing up a few things here. For example, combining FindOrFail() with first() is double. Look here, that's a good explanation I found:
find($id) takes an id and returns a single model. If no matching
model exist, it returns null.
findOrFail($id) takes an id and returns a single model. If no matching model exist, it throws an error1.
first() returns the first record found in the database. If no matching model exist, it returns null.
firstOrFail() returns the first record found in the database. If no matching model exist, it throws an error1.
get() returns a collection of models matching the query.
pluck($column) returns a collection of just the values in the given column. In previous versions of Laravel this method was called lists.
toArray() converts the model/collection into a simple PHP array.
Source: https://stackoverflow.com/a/33027466/14807111

The method description for the Builder method FindOrFail() is:
* Find a model by its primary key or throw an exception.
In your usecase this method is calling the find()method and executing:
$this->whereKey($id)->first($columns);
This will return the first model and by default all of its columns. Therefore any other first() afterwards will introduce another query.
Your first three "Wrongs" are because of execution order, the query executes the findOrFail first and afterwards it calls first() on the whole query again.
The fourth "WRONG" happens because your relationship returns a hasMany relationship, this is normal behaviour. You could either call ->get() afterwards or call ->clients without the () to receive the collection.
After all you are using the first() method wrong. You either use find(), findOrFail() or you use ->first(). But you should not use them together (there might be some edge cases where you have to, but genreally speaking, don't!).
Using find() or findOrFail() will already return the first() element that matched the ID.

Related

Call to a member function first() on null

When I try to fetch my user data, I receive the error
Call to a member function first() on null
public function show($id) {
$user=User::findOrFail($id);
$employee = $user->employees->first();
return view('admin.profile')
->with(['employee' => $employee , 'user' => $user]);
}
The problem is probably in your User model.
Check that you have declared the employees relationship:
public function employees()
{
return $this->hasMany(Employee::class); // I'm assuming you have a Employee model with expected column names, but feel free to replace everything with what you actually have in your app
}
If the problem persists, edit your question with your tables structure and your models.
It's very useful to understand the difference between $user->employees and $user->employees().
$user->employees: returns a Collection of employee models, or null if none are found.
$user->employees(): returns a query builder instance that you can chain additional conditions to (where's, etc).
Both options have a first() option available to them, but one is using a Collection method, where the other is using the query builder method.
Some have already suggested this, and I will as well - the safer and simplest solution to your problem is to use the query builder version of the relationship, since there is no risk of the employees() result being null. It also has the added benefit of not needing to load the entire relationship into a collection just to get the first result.
In short: $user->employees()->first(); is the best way to go.

Eloquent join with where clause

I have problems to build a relationship with eloquent.
I have two models created, Spielplan and Verein. In model Spielplan I have the fields Team_ID and Spiel_ID. In model Verein I have the field V_ID and Name. Now I need to join this two tables about Team_ID = V_ID.
This is my model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Spielplan extends Model
{
protected $table = 'Spielplan';
public function vereinFunction(){
return $this->hasOne('App\Verein', 'V_ID');
}
}
And this is a function in my web route where I want to get Spiel_ID and Name.
Route::get('test', function(){
$spieleT = App\Spielplan::where('Spiel_ID', '=', 30)->get();
foreach($spieleT as $da){
echo $da->Spiel_ID;
echo $da->vereinFunction->Name;
}
});
The first echo works and I get back Spiel_ID but the second echo gives back ErrorException Trying to get property of non-object.
What is wrong with my code?
Try editing this line:
$spieleT = App\Spielplan::with('vereInFunction')->where('Spiel_ID', '=', 30)->get();.
The with() allows you to fetch the association at the time you use get(). After using get(), you're working with a collection, and can't query the database again.
Try specifying the model primary key as a third argument, because if not, Laravel will assume it is named id, which is not the case.
Allow me to suggest you something: I used to name the tables and fields like you do (in the days I use Codeigniter) but since I started using Laravel around three years ago, I follow Laravel convention (which is recommended, but not imposed). I now name the tables in lowercase, (snakecase) plural, table fields also snakecasm lowercase. Models singular, camelcase similar corresponding table, relation function names as related model, being singular if relation is to one, plural if to many, etc. The advantage of this is among other reflected in model relationship declaration, which is a lot simpler and easier to define.
For instance (only as demonstration of stated above),
tables (with relation one to many:
managers (primarykey: id, name, ......)
technicians (primary key: id, foreingkey: manager_id (related table name in singular plus underscore plus id), name, .....)
models:
Manager:
/* relationships */
public function technicians () // see name as related table, plural due to as many relationship)
{
return $this->hasMany(Technician::class); // as naming convention has followed, you don't need to specify any extra parameters;
}
Techician:
/* relationship */
public function manager() // named as related table, singular due to to one relationship
{
$this->belongsToOne(Manager::class); // again, as naming convention has followed, you don't need to specify any extra parameters;
}
Therefore you can do this:
$manager::find(1);
echo $manager->technicians->first()->name,
or
foreach ($manager->technicians as $technician) {
echo $technician->name;
}
as well as:
$technician->manager->name;
Remember, a proper model relationship definition will save a lot of headache along the way, like the one you have
Hope this help in anyway

Laravel using find() and get() together

I have a table 'tour2s' with 2 rows and when I do:
$tour = Tour2::find(1);
dd($tour);
it returns the tour with 'id' = 1. And it's Object.
I want to turn the object to collection of only attributes of the model, nothing else. And I know that when I use ->get() it returns collection.
But when I am trying:
$tour = Tour2::find(1)->get();
dd($tour);
It returns a collection but of all 2 tour objects (full objects, not only attributes):
I did it like:
$tour = Tour2::find(1);
$tour = collect($tour);
dd($tour);
and now it's what i what - it return a collection of only model attributes (WHAT I WANTED):
SO, my question is why when I used $tour=Tour2::find(1)->get() it returned all tours not only the one with 'id'=1 ?
Passing an array to find() will return a collection.
$tour = Tour2::find([1]);
However, it will be a collection of Tour2 objects, not only the attributes.
Then, if you want only the attributes, you could use $tour->toArray()
You could also do $tour = collect(Tour2::find(1));
And to answer your question, when you use $tour=Tour2::find(1)->get(), Laravel fetch the first tour, and then calling get() on $tour will fetch all other records, so return two tours in your case.
Ok, the main question, as i understand is: "Why when i wrote Tour2::find(1)->get() i receives collection of all records".
when you wrote Tour2::find(1) it assumes that you receive instanse of model Tour2. So we can simple write $tourInstanse->get()
If you go to \Illuminate\Database\Eloquent\Model you can see that here is no method called get() but we have a magic method __call. Look at his implementation:
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
So, when you call get() method on a model instance you get model`s QueryBuilder (as described in last row) and call get() method on a QueryBuilder. As a result, you receiving all records of that model Class.

Laravel 5 - Limiting Eloquent Where Parameters When Using Request Object Data For Get

I have controller functions that look like:
public function get(Request $request){
return json_encode($this->users->get($request->all));
}
where $this->users references a repository for my User model. Inside of this repository, I have a function that looks like:
public function get($request){
return User::where($request)->get()->toArray();
}
I can dynamically pass in any number of parameters to search on through my HTTP request with the above code without having to explicitly state the column names.
This works well as long as all the parameters passed in through the request are valid column names. It will error out if I pass in a parameter that is a not valid table column.
I have the protected $fillable array defined in each of my models corresponding to my repositories.
Is there anyway to enforce what can be passed into the where() method above so that only valid columns defined in the $fillable array in my model are ever passed in?
For example, let's say I have a users table with columns (id, name, description). The GET URL I pass in looks like:
GET /user?name=mike&description=skinny&age=45
There are three parameters in the URL above, but only two of them are valid column names. I would like the age parameter to be automatically taken out. In my current code, the above URL will return an error response since the age column does not exist.
You can use array_only() helper:
return User::where(array_only($request, $this->fillable))->get()->toArray();
It will filter $request and will keep only key/value pairs which are in the $fillable array.
If this code is not in a model, change $this->fillable to appropriate variable or method call.
I wrote a package for doing this kind of filtering for models. It will allow you to filter based on User::filter($request->all())->get(); while defining each possible column's where() constraint.
https://github.com/Tucker-Eric/EloquentFilter
Try doing something like this:
public function get(Request $request)
{
$user = new User();
return User::where(
$request->only($user->getFillable())
)->get()->toArray();
}
By creating an instance of User you can then access it's fillable properties using getFillable(). By passing this list into $request->only(...) you can restrict the values you pull out to only those that are in your fillable array. Hopefully that should mean only fillable values are considered as part of your query.
Hope it helps.

Querying ORM with WHERE clause - Eloquent/Laravel 4

The relevant code is at: https://gist.github.com/morganhein/9254678
I have nested resource controllers, which query similarly structured tables in a database.
If I go to www.example.com/v1/jobs/1/departments, I want to query all departments that are associated with job 1. However I cannot figure out how to do that using the ORM.
Help?
I didn't test, but I would suggest you to try something like this:
Route::resource('/v1/jobs/{id}/departments', 'DepartmentController');
Route::resource('/v1/jobs', 'JobController');
After that, your DepartmentController methods will receive one argument, which is job id in your case. It is easier to use find() method if you are using id to retrieve any specific model. When you found the model you can get access to related models.
class DepartmentsController extends BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index($jobId)
{
Clockwork::startEvent('List_Default_Departments', 'Starting.');
$positions = Auth::user()->jobs()->find($jobId)->departments;
Clockwork::endEvent('List_Default_Departments', 'Done.');
return Response::json($positions->toArray());
}
}
Note: There is a different between $job->departments() (returns Builder object to create more complex queries) and $job->departments (returns Collection object directly).
Also if you would like to get the list of jobs with all related departments you can always call:
$jobs = Auth::user()->jobs()->with('departments')->get();

Resources