Select specific fields from array of Eloquent collecton | Laravel - php

I have a collection object which includes the array of Model objects and I would like to select specific fields from the model.
Illuminate\Database\Eloquent\Collection Object
(
[items:protected] => Array
(
[0] => App\Model Object
[1] => App\Model Object
[2] => App\Model Object
)
)
Now I would like to select some fields from the model object. When I try to do the following syntax
print_r($collection->select('filed a', 'field b'));
then the following error occurs.
BadMethodCallException in Macroable.php line 74: Method select does
not exist.
I think select can work directly with the eloquent model but not with a collection.

Are you looking for only()
$filtered = $collection->only(['list', 'of', 'fields', 'to', 'keep']);
or perhaps mapWithKeys()

You are correct that select is not present on the Collection class.
What you can do is map, filter or transform the collection yourself e.g.
$whiteList = ['filed a', 'field b'];
$filledOnly = $collection->map(function ($item) use ($whiteList) {
$properties = get_object_vars($item);
foreach ($properties as $property) {
if(!in_array($property, $whiteList) {
unset($item->{property});
}
}
return $item;
});
The problem is in PHP once a property (or field) is set on an object, you really have to unset it or create new objects of the same class). This is why I came up with this solution.
Question is: How did you retrieve this collection in the first place, could you not add the select to the query itself?

The best would have been to select the fields you need before you execute the query on the model. However, you can use map() if you want to preserve the initial collection or transform() if you want to override the collection (for example):
$selected_fields = ['filed a', 'field b']
$models->map(function ($zn) use ($selected_fields) {
return $zn->newInstance(array_only($zn->getAttributes(), $selected_fields));
})->toArray();
newInstance() method creates a new empty instance of that model then getAttributes() retrieves the attributes present in the model. So the initial model is preserved in this process.
For reference sake, the implementation of newInstance() can be found on at Illuminate\Database\Eloquent\Model class and it is as follows (on Laravel 5.2):
/**
* Create a new instance of the given model.
*
* #param array $attributes
* #param bool $exists
* #return static
*/
public function newInstance($attributes = [], $exists = false)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
// hydration of new objects via the Eloquent query builder instances.
$model = new static((array) $attributes);
$model->exists = $exists;
return $model;
}

Related

Yii2 ArrayHelper::toArray doesn't work recursively

Yii2 ArrayHelper's helper method toArray doesn't convert nested objects.
Here is my test code.
public function actionTest()
{
$product = \common\models\Product::find()
->where(['id' => 5779])
->with('firstImage')
->one();
$product = \yii\helpers\ArrayHelper::toArray($product);
print_r($product);
}
Recursive property is enabled by default.
public static array toArray ( $object, $properties = [], $recursive =
true)
So this piece of code should work correctly but it doesn't.
Action returns one level array without firstImage object.
What I'm doing wrong here?
PS:
Code was simplified for test purposes. I know that in this certain situation one can simply use asArray() method to get AR record in array.
You should use this instead :
$product = \common\models\Product::find()
->where(['id' => 5779])
->with('firstImage')
->asArray()
->one();
Read more about Retrieving Data in Arrays.
If your really want to use toArray(), and since a relation is not really an attribute or property, you should simply use the second parameter, e.g. :
$product = \yii\helpers\ArrayHelper::toArray($product, [
'common\models\Product' => [
// add needed properties here
// ...
'firstImage',
],
]);
Or, if you are using REST, you could override extraFields() in your model :
public function extraFields()
{
return ['firstImage'];
}
Read more about REST fields.

Additional data members in Eloquent Collection not being copied

I have extended the basic Eloquent\Collection class as follows:
class BoogerCollection extends \Illuminate\Database\Eloquent\Collection {
// Metadata used by my collection
protected $some_array = [];
public function setArray(){
$this->some_array = [1,2,3];
}
public function getArray(){
return $this->some_array;
}
}
So, the following works as expected:
$collection = new BoogerCollection();
$collection->setArray();
print_r($collection->getArray()); // Prints out Array ( [0] => 1 [1] => 2 [2] => 3 )
However, if I then perform some operation on the collection which returns a modified copy of the collection, this metadata gets lost:
// Perform some operation on the collection that makes a copy
$collection = $collection->reverse();
print_r($collection->getArray()); // Prints out Array ( )
So, my $some_array member is not getting copied. I dug into the code for Eloquent\Collection and Support\Collection, and it looks like these methods all create new Collections by directly constructing them from the Collection's internal representation of the items:
in Eloquent's Support\Collection:
public function reverse()
{
// $this->items is the Collection's internal representation of the collection of items
return new static(array_reverse($this->items));
}
Does this mean that to have my metadata array copied as well, I must override every single method?
Extending Eloquent's Collection class doesn't mean it will be used by Eloquent to return a collection of models. For now, Eloquent will stick with it's Collection class, which is Illuminate\Database\Eloquent\Collection when returning a collection. If you look at Illuminate\Database\Eloquent\Builder's codes, you'll see the methods that returns a collection (get(), findMany(), etc.) will call the supplied model's newCollection() method which in turn returns an instance of Illuminate\Database\Eloquent\Collection and not your newly created subclass.
Your code: $collection = new BoogerCollection(); works because you're the one who instantiated your class. But when doing something like MyModel::all() won't return your BoogerCollection class as Eloquent will be the one to choose what Collection class it wants to return and right now it sticks with Illuminate\Database\Eloquent\Collection.

laravel 5 Query Builder error "must be of the type array, object given"

Ok so i am trying to perform a mysql query to join to tables and return the results.
So in my controller i have an array of serviceIDs that when print_r() looks like this:
Array
(
[0] => 50707
[1] => 50709
)
The name of this array is $serviceIDS
Okay Then i now call a function from one of my models. This is a scope as seen below:
$services = Services::getWatchListInfo($serviceIDS)->get();
This is the scope function in my model:
public function scopegetWatchListInfo($serviceIDS){
$services = DB::table('services')
->join('reviews','services.serviceID','=','reviews.serviceID')
->select('services.name','services.type','services.review_count_approved','reviews.escalate','reviews.average_rating')
->whereIn('serviceID',$serviceIDS);
return $services;
}
Okay so this should get results from both my services and reviews table where service id is in the array.
Instead i am getting the following error.
Argument 1 passed to Illuminate\Database\Grammar::parameterize() must be of the type array, object given, called in /Users/user/sites/informatics-2/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php on line 311 and defined
Any ideas?
You're not using Eloquent scopes correctly. If you read the docs, you're attempting to use a Dynamic Scope, so you need to define your scope as:
/**
* getWatchList Eloquent Scope
*
* #param object $query
* #param array $servicesIDS
* #return object $query
*/
public function scopegetWatchListInfo($query, $serviceIDS = []) {
return $query
->join('reviews','services.serviceID','=','reviews.serviceID')
->select('services.name','services.type','services.review_count_approved','reviews.escalate','reviews.average_rating')
->whereIn('serviceID', $serviceIDS);
}
The DB::table('services') should not be necessary if you're defining a Scope within your Services model (because the services table is automatically handled by Eloquent:
Now, let's look at an example Flight model class, which we will use to retrieve and store information from our flights database table

How to manually create a new empty Eloquent Collection in Laravel 4

How do we create a new Eloquent Collection in Laravel 4, without using Query Builder?
There is a newCollection() method which can be overridden by that doesn't really do job because that is only being used when we are querying a set result.
I was thinking of building an empty Collection, then fill it with Eloquent objects. The reason I'm not using array is because I like Eloquent Collections methods such as contains.
If there are other alternatives, I would love to hear them out.
It's not really Eloquent, to add an Eloquent model to your collection you have some options:
In Laravel 5 you can benefit from a helper
$c = collect(new Post);
or
$c = collect();
$c->add(new Post);
OLD Laravel 4 ANSWER
$c = new \Illuminate\Database\Eloquent\Collection;
And then you can
$c->add(new Post);
Or you could use make:
$c = Collection::make(new Post);
As of Laravel 5. I use the global function collect()
$collection = collect([]); // initialize an empty array [] inside to start empty collection
this syntax is very clean and you can also add offsets if you don't want the numeric index, like so:
$collection->offsetSet('foo', $foo_data); // similar to add function but with
$collection->offsetSet('bar', $bar_data); // an assigned index
I've actually found that using newCollection() is more future proof....
Example:
$collection = (new Post)->newCollection();
That way, if you decide to create your own collection class for your model (like I have done several times) at a later stage, it's much easier to refactor your code, as you just override the newCollection() function in your model
Laravel >= 5.5
This may not be related to the original question, but since it's one of the first link in google search, i find this helpful for those like me, who are looking for how to create empty collection.
If you want to manually create a new empty collection, you can use the collect helper method like this:
$new_empty_collection = collect();
You can find this helper in Illuminate\Support\helpers.php
snippet:
if (! function_exists('collect')) {
/**
* Create a collection from the given value.
*
* #param mixed $value
* #return \Illuminate\Support\Collection
*/
function collect($value = null)
{
return new Collection($value);
}
}
Just to add on to the accepted answer, you can also create an alias in config/app.php
'aliases' => array(
...
'Collection' => Illuminate\Database\Eloquent\Collection::class,
Then you simply need to do
$c = new Collection;
In Laravel 5 and Laravel 6 you can resolve the Illuminate\Database\Eloquent\Collection class out of the service container and then add models into it.
$eloquentCollection = resolve(Illuminate\Database\Eloquent\Collection::class);
// or app(Illuminate\Database\Eloquent\Collection::class). Whatever you prefer, app() and resolve() do the same thing.
$eloquentCollection->push(User::first());
For more information about understanding resolving objects out of the service container in laravel take a look here:
https://laravel.com/docs/5.7/container#resolving
I am using this way :
$coll = new Collection();
$coll->name = 'name';
$coll->value = 'value';
$coll->description = 'description';
and using it as normal Collection
dd($coll->name);
It is better to use the Injection Pattern and after $this->collection->make([]) than new Collection
use Illuminate\Support\Collection;
...
// Inside of a clase.
...
public function __construct(Collection $collection){
$this->collection = $collection;
}
public function getResults(){
...
$results = $this->collection->make([]);
...
}
What worked for me was to name the use namespace and instantiate it directly:
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
# Usage
$this->latest_posts = new EloquentCollection();
Allowed me to merge two data subsets of eloquent collection results, this maintains the relationships - a regular collection (collect()) loses relationship and probably some more metadata.
$limit = 5;
$this->latest_posts = new EloquentCollection();
$pinned_posts = PinnedPostReference::where('category', $category)->get();
if($pinned_posts->count() > 0) {
foreach($pinned_posts as $ppost) {
$this->latest_posts->push($ppost->post);
}
}
# Another Eloquent result set ($regular_posts)
foreach($regular_posts as $regular_post) {
$this->latest_posts->push($regular_post);
}

Change object type with hydrators, inside a pagination collection with ZendFramework 2 + Doctrine 2

I'm building a RESTful API, and I have problem.
The goal:
I want to hydrate a collection, that comes from a Paginator.
I mean, inside the collection, I don't want to return a Project object, I want to return a HalCollection of HalResources. To create these HalResources, I need to use the Project object (plus additional information).
The scenario:
I create a class ProjectHydrator, that implements HydratorInterface, with the two methods:
class ProjectHydrator implements HydratorInterface {
public function hydrate(array $data, $project){ .... }
public function extract($project) { .... }
}
I attach this Hydrator to my module, inside the module.config.php
'phlyrestfully' => array (
'renderer' => array (
'hydrators' => array (
'MYPROJECT\Entity\Project' => new \MYPROJECT\Hydrators\ProjectHydrator()
)
),
......
)
And the fetchAll of the listener method y create the pagination in this way:
$dql = 'SELECT e FROM \MYPROJECT\Entity\Project e';
$query = $this->getEntityManager()->createQuery($dql); // Class: \Doctrine\ORM\Query
$ormPaginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query); //Class: Doctrine\ORM\Tools\Pagination\Paginator
$doctrinePaginator = new \DoctrineORMModule\Paginator\Adapter\DoctrinePaginator($ormPaginator); //Class: DoctrineORMModule\Paginator\Adapter\DoctrinePaginator
$paginator = new \Zend\Paginator\Paginator($doctrinePaginator); //Class: Zend\Paginator\Paginator
return $paginator;
The problem: The hydrator is being executed... but is called the method "extract", with parameter a Project object. In this method I must return and array, and this is my problem, I want to return a HalResource, not an array.
I want to use the hydrator to change the type of object, from Project object (project Entity object) to a HalResource. To build this HalResource, I want to use the Project object plus an array with other parameters.
What I am doing wrong?
Any ideas?
Thank you very much!
No need to extend the Doctrine Paginator, simply set your query hydration mode to hydrate array mode.
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as PaginatorAdapter;
use Zend\Paginator\Paginator;
$dql = 'SELECT e FROM \MYPROJECT\Entity\Project e';
$query = $this->getEntityManager()->createQuery($dql);
//Set query hydration mode
$query->setHydrationMode(Query::HYDRATE_ARRAY);
$paginator = new Paginator(new PaginatorAdapter(new DoctrinePaginator($query)));
return $paginator;
Hope it helps.

Categories