Laravel collection: pluck before serialization - php

I have a laravel Model with a one to many relationship which the user can edit via a multiple select tag.
Before exporting the model as a JSON, I use the "pluck" method to get an array of related IDs instead of an array of models, so that they can be used in the select tag and later be synced again with the "sync" method of Laravel.
However the result of "pluck()" seemingly doesn't persist over serialization. The following code doesn't work -upon serialization, "relationship" becomes again an array of objects-
$model->relationship = $model->relationship->pluck('id');
This one, however, does what it should: somePropertyIHaveJustCameUpWith is an array of IDs
$model->somePropertyIHaveJustCameUpWith = $model->relationship->pluck('id');
1) Why does this happen?
2) I have seen there is this resources way in the documentation, but creating an entire new class for something that could be solved with a single line of code feels like a bit overkill. Isn't there a cleaner way to do that?

I think this is likely a result of the way the model implements toArray().
The you can trace the steps taken, but eventually the relations are read from the $this->relations property on the Model, not from each individual relationship.
So, instead of setting the value of your relation directly like:
$model->relationship = $newValue
... you could try setting it using:
$model->setRelation('relationship',$newValue)
This will update the $model->relations property.
This should allow the toArray() method to get the new value that you set when serializing.
Note that the toJson() method in turn calls the toArray() method when serializing. So either approach will be the same result.

Related

How to iterate over Doctrine collection mixed with Proxies and Objects

I'm using Doctrine in a project where I need data from multiple tables. In order to escape from N + 1 problem I'm fetching all the data, that I need before it go to the view. As I saw in the documentation the way to do this is to make a join with tables and then to call addSelect with the aliases of the joined table. The problem is that when I build the query that I need and call getResult, Doctrine returns me Collection of both entity objects and Proxies of the joined entity, which causes problems during the iteration of the array in the view, because Proxies don't have the same properties as the entity objects. I'm really confused of this behaviour. Can you help me in order to solve this issue?
You get proxy objects because they are lazy loaded which is correct. You shouldn't have got any problems with them, because when you iterate over collection all data should be set correctly. Proxies generally behave like fully loaded entity. If you have got problems, maybe problem is somewhere else. However you can set fetch to EAGER in your relations to force build full entity.
#ManyToOne(targetEntity="target", fetch="EAGER")
#JoinColumn(name="target", referencedColumnName="id")
What's more you can use $query->getResult(Doctrine\ORM\Query::HYDRATE_ARRAY); to hydrate all objects in query. You can try as well $queryBuilder->getQuery()->setHint (Query::HINT_FORCE_PARTIAL_LOAD, true)->get();
You can also load proxy object by yourself during iteration. Something like that
foreach($collection as $object) {
if ($object instanceof Doctrine\ORM\Proxy\Proxy) {
$object->__load();
}
}
However you may post your code (entities, query from repository and the view part) because there shouldn't be any problems with proxies.

Basic Eloquent Relationship Enquiry - One to One

**USER MODEL**
public function post(){
return $this->hasOne('App\Post','user_id','id');
}
**WEB ROUTE**
use App\User;
Route::get('/{id}/post',function($id){
return User::find($id)->post;
});
Hi everyone, I'm fairly new to both PHP and Laravel and have been struggling a bit. I just have 2 questions for this code.
In the web routes, why doesn't post have any () beside it? It was declared a function in the user model. And.. I am unsure of how these relationships work (correct me if I am wrong) but does the code above look for a user with a specific $id and connects it with a post having a similar $user_id value?
For the first bit, it is a dynamic property, here you can find how you can actually make one yourself Laravel: create a dynamic property. They essentially work because the result is a single object search based on the id, since it doesn't have to retrieve a collection it allows itself to be accessed like an attribute of the object.
And yea pretty much on the second one. It also uses laravels models to retrieve the data from the database so that you get an object back without needing to create the repositories yourself.
There are major differences between User::find($id)->post and User::find($id)->post(). First one is returning the result of the related relations so you get the post that has user_id equal to $id.
The second one returns a query builder,so you can add more conditions. For example User::find($id)->post()->where("status", 1)->get().

Laravel 5.2 - modify a model's builder

So, I am using Laravel's built in soft Deletes and I have run into an issue. When I call withTrashed() it returns a query builder object. This would be fine for most cases but after I call this I then want to call a method like filter($this->filters) which is specific to the MODEL and I no longer have access to now that I only have a query builder.
The reverse relationship also does not work as withTrashed() also wants a model where any kind of parsing method I can think of would return the model's query builder.
So I was hoping to find a way to modify a model object with where clauses so I can add my filters and send the model to withTrashed() WITH the filters in place. I am honestly not sure how to do this. Worst case scenario I can have the filter method return the query builder without any global scopes and add the withTrashed() query on to the end manually.
So the end result would be something like this:
$model->filter($this->filters)->withTrashed();
Im not trying to get a giant collection and whittle it down. Even with chunking when you have a few million rows it can get slow really quick, especially when you bring in filtering. Technically, for a one off, I could just add multiple ->where() clauses to the query builder before i call ->get() but that would mean doing custom filtering in every controller's index. So i am looking to abstract it as a method in the model that parses filters sent in and adds them to the model (this is the part im not sure on. Kind of like: github.com/marcelgwerder/laravel-api-handler
Any ideas?
I believe you're looking for query scopes. You can create a new query scope on your model, and then it can be used like any other query builder method.
For example, to create a filter() scope:
class MyModel extends Model {
public function scopeFilter($query, $filters) {
// modify $query with your filters
return $query;
}
}
With that query scope defined, you call it like any other query builder method:
$data = \App\MyModel::withTrashed()->filter(/* your filters */)->get();
You can read more about query scopes here.
Filter is a method for a collection, as you can see here https://laravel.com/docs/5.2/collections#method-filter
To make that returns a collection you should use a method like get(), as you can see here https://laravel.com/docs/5.2/eloquent#querying-soft-deleted-models
$yourVar = \App\YourModel::withTrashed()->get();
You will be able to filter in $yourVar after that! Or use where() method to get collection filtered by the query
$yourVar = \App\YourModel::withTrashed()->where('active = 1')->get();
In both, $yourVar will be a Collection of results returned by get() method
Don't know if already has this problem, but i think that i figured out a way to do it. Using Eloquent's Global Scopes (https://laravel.com/docs/5.2/eloquent#global-scopes)
Soft deletes in fact are a global scope.
You will create an implementation for Scope class, then you will create a apply method that will perform the where clause.
Finally in your model you will override the boot method to say that this Model uses this global scope, every query in this Model will be performed in this way.
In the doc link there is a example of how do it and more explanations from Laravel creator.
Hope that this help!
-------- Edit --------
I see now that already has a solution, but maybe this way may be better for you or not, or for other, anyway, just another approach!

Fields in Laravel Eloquent models

I am new to Laravel and Eloquent, so excuse me if this is a totally stupid question.
I have been looking at how to create a model at both the documentation here and also another tutorial here (in the Creating Models using Eloquent ORM section) and I've noticed that the actual fields of the table are never mentioned, unless there is something specific about them (like having a relationship with another table, or not requiring mass assignment, or if they need to be hidden from JSON output etc.)
Are these fields being omitted on purpose and PHP just adds them when it performs the query using PDO with FETCH_OBJ turned on?
If yes why is it that we do not explicitly put the fields in the model? Doesn't it help us to know what fields we have, and also IDEs such as PHPStorm to pop up the right auto-complete fields?
If they are actually required, what access level do they need to have?
Thanks.
Column names (fields) are not required in Eloquent models. As you pointed out, it is only necessary to define the functions which determine the relationships that a model has with others.
It isn't necessary to include them, because of the reason you mentioned (Laravel does a select * and then adds all of the returned rows to the model object as public properties). This is a process dubbed hydration and you can see exactly what is happening by digging into the Laravel source. Here's a summary of what happens:
You call (for example), Users::find(123);
Illuminate\Database\Eloquent\Model::find() calls Illuminate\Database\Eloquent\Builder::find()
find() constructs the SELECT * FROM users WHERE id = 123 query and then returns the first result by calling Illuminate\Database\Eloquent\Builder::first()
first() adds LIMIT 1 by calling Illuminate\Database\Query\Builder::take()
Then first() sets the columns to be retrieved (* by default) by calling Illuminate\Database\Eloquent\Builder::get().
get() returns an Illuminate\Database\Eloquent\Collection by using the return value of Illuminate\Database\Eloquent\Builder::getModels()
getModels() actually performs the query and then calls Illuminate\Database\Eloquent\Model::newFromBuilder() for each row returned
newFromBuilder() creates a new instance of the model and sets the columns (fields) by calling Illuminate\Database\Eloquent\Model::setRawAttributes()
I've omitted some unrelated things such as eager loading to simplify the process, but this is basically what happens for each query.
You make a good point that knowing the fields beforehand can be helpful for autocompletion. Because of the nature of setRawAttributes() it is perfectly OK to declare all column names (fields) in your model (just make sure they are public). The convention, though (and for you sanity), is to omit them. Such declarations should be left to migration files.
After further examination of the source, it is not ok to declare the fields beforehand. This is because the actual attribute values are stored in an $attributes property and then accessed by the magic method __get(). The trouble here is that by defining the properties beforehand, you will prevent __get() from being called when you access the fields. Therefore, this is not an option.
However, there are ways to hint to editors (like PhpStorm) about the existence of properties without explicitly defining them.
There is another way to make phpstorm to auto-complete column name and avoid warning.
/**
* #property string $title Title of article
*/
class Article extends Eloquent

PhalconPHP: "Partial objects" and relationship calls?

So, I'm setting up relationships with PhalconPHP and then trying to access those relationships from an object.
When I do:
Companies::find()[0]->companyTitles
it works fine. But when I add fields to it, it doesn't:
Companies::find(array('fields'=>'id,name'))[0]->companyTitles
// Gives: Undefined property: Phalcon\Mvc\Model\Row::$companyTitles
I know I'm getting back a partial object when I specify fields, but does that mean that I'm unable to use some of the "more advanced" features? (I am asking for the id). I'd just rather not have to pull back everything from the database if I don't need everything (performance and all...).
Is there a way to overcome this?
As you can see, the type of the returned object is \Phalcon\Mvc\Model\Row. When you limit the fields, the type of the returned object is not your model, and that means you don't have access to all the extra functionality on your model, and you can only use the properties to have access to the values you have previously noted.
TL;DR: No, you can't, because what you get is not an instance of Companies.

Categories