On a User model (table with 4 records), when I do:
$coll = User::all();
echo $coll->count();
I get the amount of records found (4).
But when I do:
$coll = User::find(2);
echo $coll->count();
I do not get 1 (As I expect) but the amount of attributes in the resulting collection (23 in this case).
How can I check if more then one records are found?
UPDATE:
OK, thanks to you all I now see the difference in result between collection and model.
But my real problem is that I have to detect if I am having a model or a collection as a result. Depending on this result I perform some changes on the contents of the fields in the items (with map()) or model. How can I detect if the result is a model or a collection?
if(count($coll) > 1)
Works, but is this the right approach?
Here's what's going on with the code you have there:
1. When calling User::all() you'll get a Illuminate\Database\Eloquent\Collection on which you can call count which counts the elements in the collection like so:
public function count()
{
return count($this->items);
}
This will return the number of items in the collection as you correctly expected.
2. When calling User::find(2) however, the Eloquent Query Builder will not return a Collection, because it will check to see how many results there are, and since you passed one ID you'll get at most one result, so it will return an Eloquent Model instead. The Model does not have a count() method, so when you try to call $coll->count(); it will go to the magic __call method that the class has implemented which looks like this:
public function __call($method, $parameters)
{
if (in_array($method, array('increment', 'decrement')))
{
return call_user_func_array(array($this, $method), $parameters);
}
$query = $this->newQuery();
return call_user_func_array(array($query, $method), $parameters);
}
As you can see the method tries to see if it should call a couple of hardcoded methods (increment and decrement), which of course don't match in this case because $method = 'count', so it continues to create a new Query on which it will call the count method.
The bottom line is that both the first and second code samples end up doing the same thing: counting all the entries in the users table.
And since, as I pointed our above, one ID cannot match more than one row (since IDs are unique), the answer to your question is that there's no need or way to count the results of find(2), since it can only be 0 (if null is returned) or 1 (if a Model is returned).
UPDATE
First of all, for future reference you can use the PHP get_class to determine the class name of an object or get_parent_class to determine the class it is extending. In your case the second function get_parent_class might be useful for determining the model class since the User class extends a Laravel abstract Model class.
So if you have a model get_class($coll) will report User, but get_parent_class($coll) will report \Illuminate\Database\Eloquent\Model.
Now to check if the result is a Collection or a Model you can use instanceof:
instanceof is used to determine whether a PHP variable is an instantiated object of a certain class
Your checks should look something like this:
// Check if it's a Collection
if ($coll instanceof \Illuminate\Support\Collection)
// Check if it's a Model
if ($coll instanceof \Illuminate\Database\Eloquent\Model)
You might also want to check if the result is null, since find will return null if no entry is found with the given ID:
if (is_null($coll))
It seems you are expecting the find()-method to behave differently. From the docs
Find a model by its primary key.
If you problem is by checking if its from collection. Why don't you check it if its from Illuminate\Database\Eloquent\Collection.
if (get_class($coll) == 'Illuminate\Database\Eloquent\Collection') {
your code...
}
or
if ($coll instanceof \Illuminate\Database\Eloquent\Collection) {
your code...
}
Related
I was wondering about the best way to get the count of all the rows created before the selected one. Right now I have defined an accessor that looks like this:
// In the model
public function getPositionAttribute() {
return self::where([
// Some other condition
['created_at', '<', $this->created_at->toDateTimeString()]
])->count();
}
// In the code
$model->position
It works correctly, but I'm worried about 2 things:
Is it a bad practice to call self on the model? Looks somehow off to me.
When called in a foreach this obviously generates a query for each element which is far from optimal. Is there any way to refactor this so that it can be eager loaded in a single query?
Bonus: I have totally discarded the idea of keeping a column with some kind of index because that initially sounded impossible to maintain, eg. when a record is deleted all the others should somehow shift position. Should I reconsider it? Is there a better way?
Pretty sure that using self here is the "best practice" because that is how that keyword was designed to be used.
In regards to refactoring, i personally can't think of optimizing the query as is but instead you could create a function that preloads all the position then use it normally. Assuming your model has a unique key 'id' and you are passing in a collection of model then, you can try something like this:
public static function populateOrderPositions($modelCollection){
// Optimize this query to include your "other condition"
$idCollection = Model::orderBy('created_at') // this will make it in the order of creation
->pluck('id'); // this will only retrieve the id field
// This array will contain an array with the model object ids as key and a numeric position (starts at 0)
$positionArr = $idCollection->flip()->all();
// Then just load all the position into the object and return it.
return $modelCollection->map(function($modelObj) use ($positionArr){
return $modelObj->position = $positionArr[$modelObj->id] + 1; // +1 because array key starts at 0
};
}
You would also need to adjust your attribute code to use the loaded attribute instead of ignoring the loaded attribute like so:
public function getPositionAttribute() {
return $this->attributes['position'] ?? self::where([
// Some other condition
['created_at', '<', $this->created_at->toDateTimeString()]
])->count();
}
With these changes, you can then prepopulate the position then use it afterward without the need to query the database.
These code are untested as i don't know how your model and query will be structured and is more of an example. Also you would need to compare the performance vs your original code.
I have various relationships within my Eloquent Models that look like this:
public function main_image()
{
return $this->hasOne(Media::class, 'id', 'main_image_id');
}
However, it will run a SQL query if the main_image_id is null or 0, so I end up with a number of queries like this:
select * from `media` where `media`.`id` is null and `media`.`id` is not null limit 1
Which obviously will not return anything, but still wastes resources. Is there any way of automatically checking for that?
Currently what I do is have a method, like hasMainImage(), that checks that main_image_id is not null and not 0, but a lot of the current system already uses the relationships, and I was wondering if I can add the check to the relationship method itself?
I have tried adding a check to it and return null if the column has no real value, but I've got an Exception that it has to return a Relation object. Or if I'm trying to Eager load it, I receive following error:
Call to a member function addEagerConstraints() on null
public function main_image()
{
if (!$this->main_image_id) {
return null;
}
return $this->hasOne('App\Modules\Media\Models\Media', 'id', 'main_image_id');
}
Thanks for your help!
EDIT:
A perhaps more clear example:
$page = Page::find(1);
var_dump($page->main_image); // This will run a query as shown above that is guaranteed to return nothing
// Since at this point system knows that $page->main_image_id is 0 or null, I would like to use that to not run the query and automatically set $page->main_image to null
You declare your relations in wrong order, Eloquent documentation is clear about this.
The entity that does no make sense by itself (without its "parent" you may say) should incorporate "belongsTo" relation (as documentation says inverse).
For demonstration lets say you have User model and you have many details about that user so you create Detail model.
Now User model should have detail relation:
public function detail() {
return $this->hasOne('App\Detail', 'user_id', 'id'); //you can see the difference here from your code sample, you have 2nd and 3rd parameter reversed
}
And Detail model should have user relation:
public function user()
{
return $this->belongsTo('App\User');
}
Just wondering if it is possible that some kind of findOrNew for relationships exist in Eloquent (in case if relationship do not exist attach new model instance)?
What that mean:
Lets say that we have devices and specifications tables. Device belongs to specification. Specification_id is an FK (Know that is not best approach, but I have something like this left by previous programmer). Under id 11 we have device that do not have specification but we have to display that for user anyway.
$device = Device::find(11);
echo $device->specification->cpu;
In this case it will throw an error because specification will be null - it do not exist for device with id 11.
Know that I could check first if it exist but there a a lot of similar lines and app is pretty big. I need to move it from Kohana to Laravel. It works in Kohana because empty object is loaded then and 2nd line just return null. For Laravel I can just check if relationship exist and load new model then but I am curios if maybe there is any other and better way?
I would go for creating extra method in Device model this way:
public function getSpecification()
{
if ($device->specification) {
return $device->specification;
}
return Specification::find(20); // some default specification
// or
// return new Specification(['cpu' => 'Not provided']);
}
And now you could use it this way:
$device = Device::find(11);
$device->getSpecification()->cpu;
Of course it depends how would you need to use it. If you have many properties, you should run this method just once for object to not run multiple queries and in case you would use it for big collections you should also rethink improvements to lower database queries.
This doesn't quite create the related object as you requested, but for the purposes of outputting the data or replicating Kohana's null output in the absence of a related model, I tend to use the data_get() or object_get() helpers for this purpose.
$device = Device::find(11);
echo object_get($device->specification, 'cpu');
// You could probably do this too (untested)
echo object_get($device, 'specification.cpu');
Having had a bit of a look, you can override the getRelationshipFromMethod() method in Illuminate\Database\Eloquent\Model
protected function getRelationshipFromMethod($method)
{
// Different relationships return different types of data so
// tweak this as necessary. In theory you only care if the relationship
// type is a single entity rather than a collection.
$results = parent::getRelationshipFromMethod($method);
if ($results instanceOf Illuminate\Database\Eloquent\Collection) {
return $results;
}
// Generate a null value for any missing attributes
// PHP7 anonymous class. Return a real class < 7.0
return $this->relations[$method] = new class {
public function __get($attribute) {
return null;
}
};
// Or perhaps actually create a relationship with a specification
$this->relations[$method] = Specification::where('default', true)->first();
$this->specification()->associate($this->relations[$method]);
return $this->relations[$method];
}
I have a user model which stores basic user information such as username, password etc.
There are also 3 types of user, Student, Staff and Parent. Each type also has a seperate model. For example, there is a Student model which belongs to a User model.
I also have a relationships table, which stores relationships between students and parents. This relationship is stored in the User model.
If I do something like:
App\Student::first()->user->relations;
It happily returns a collection of related parents.
In my Students model, I have a method called hasParent() which accepts a given user ID, and checks to ensure the student has a parent with that id. In that method, I have the following:
public function hasParent($parent)
{
return $this->user->relations->where('id', $parent)->count() === 1;
}
However, this returns an error Cannot call 'where' on a non-object. If I debug further, $this->user->relations returns an empty array.
The problem is, like above, if I call the methods separately, I get the results I want.
So to clarify, if I run:
App\Student::first()->user->relations;
This returns a collection of users just fine.
In my Student model however, if I call:
$this->user
Then I get the correct student
If I call
$this->user->relations
I get an empty array. Which doesn't make sense! Can anyone shed any light on this, or what I might be doing wrong? If you need any further info, please let me know.
You need to call where on the relation like below.
public function hasParent($parent)
{
return $this->user->relations()->where('id', $parent)->count() === 1;
}
See the parenthesis after the relations. If you call the relation without the parenthesis Laravel returns you a collection. To get the builder you need to call the relation with the parenthesis.
I'd suggest - to avoid creating a huge query overhead (which you'll do by calling where and count on the Query builder, not the collection) - to do what you're doing already, except using Illuminate Collections filter-method:
public function hasParent($parent)
{
return $this->user->relations->filter(function($relation) use ($parent){return $entity->id === $parent;})->count() === 1;
}
In my Room object I want to get an Picture with the lowest priority. So to my Room model i've added:
public function picture(){
Return Picture::where('room_id', $this->id)->orderBy('priority', 'asc')->first();
}
In my controller I call this method like:
public function($id){
$room = Room::findOrFail($id);
$room->picture();
}
But when i try to get it in my view like:
{{$room->picture}}
I get the following error:
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
When using {{$room}} I dont see any picture object in the room object but the app doesnt crash.
If you want to be able to fetch the latest picture like that, you'll need to put relation definition in your pictures() method instead of fetching the object. This way you'll be able to make use of Eloquent's eager loading; fetching the picture with the lowest (in terms of value) priority will also be very easy.
Add the following to your Room class:
//relation definition - one to many
public function pictures() {
return $this->hasMany(Picture::class);
}
//Eloquent getter
public function getPictureAttribute() {
return $this->pictures()->orderBy('priority', 'asc')->first();
}
Now, you can easily access the most important picture on a $room object by doing:
$picture = $room->picture;
You can read more about how to set up different types of relations in your models here: http://laravel.com/docs/5.1/eloquent-relationships