Laravel 4 - Get Array of Attributes from Collection - laravel

I have a collection of objects. Let's say the objects are tags:
$tags = Tag::all();
I want to retrieve a certain attribute for each tag, say its name. Of course I can do
foreach ($tags as $tag) {
$tag_names[] = $tag->name;
}
But is there a more laravelish solution to this problem?
Something like $tags->name?

Collections have a lists method similar to the method for tables described by #Gadoma. It returns an array containing the value of a given attribute for each item in the collection.
To retrieve the desired array of names from my collection of tags I can simply use:
$tags->lists('name');
Update: As of laravel 5.2 lists is replaced by pluck.
$tags->pluck('name');
More specifically, the laravel 5.2 upgrade guide states that "[t]he lists method on the Collection, query builder and Eloquent query builder objects has been renamed to pluck. The method signature remains the same."

Yep, you can do it nice and easily. As the Laravel 4 Documentation states, you can do
Retrieving All Rows From A Table
$users = DB::table('users')->get();
foreach ($users as $user)
{
var_dump($user->name);
}
Retrieving A Single Row From A Table
$user = DB::table('users')->where('name', 'John')->first();
var_dump($user->name);
Retrieving A Single Column From A Row
$name = DB::table('users')->where('name', 'John')->pluck('name');
Retrieving A List Of Column Values
$roles = DB::table('roles')->lists('title');
This method will return an array of role titles.
You may also specify a custom key column for the returned array:
$roles = DB::table('roles')->lists('title', 'name');
Specifying A Select Clause
$users = DB::table('users')->select('name', 'email')->get();
$users = DB::table('users')->distinct()->get();
$users = DB::table('users')->select('name as user_name')->get();
EDIT:
The above examples show how to access data with the help of Laravel's fluent query builder. If you are using models you can access the data with Laravel's Eloquent ORM
Because Eloquent is internaly using the query builder, you can without any problem do the following things:
$tag_names = $tags->lists('tag_name_label', 'tag_name_column')->get();
which could be also done with:
$tag_names = DB::table('tags')->lists('tag_name_label', 'tag_name_column')->get();

Here are a few snippets from my own experimentation on the matter this morning. I only wish (and maybe someone else knows the solution) that the Collection had a $collection->distinct() method, so I could easily generate a list of column values based on an already filtered collection.
Thoughts?
I hope these snippets help clarify some alternative options for generating a list of unique values from a Table, Collection, and Eloquent Model.
Using a Collection (Happy)
/**
* Method A
* Store Collection to reduce queries when building multiple lists
*/
$people = Person::get();
$cities = array_unique( $people->lists('city') );
$states = array_unique( $people->lists('state') );
// etc...
Using an Eloquent Model (Happier)
/**
* Method B
* Utilize the Eloquent model's methods
* One query per list
*/
// This will return an array of unique cities present in the list
$cities = Person::distinct()->lists('city');
$states = Person::distinct()->lists('state');
Using an Eloquent Model PLUS Caching (Happiest)
/**
* Method C
* Utilize the Eloquent model's methods PLUS the built in Caching
* Queries only run once expiry is reached
*/
$expiry = 60; // One Hour
$cities = Person::remember($expiry)->distinct()->lists('city');
$states = Person::remember($expiry)->distinct()->lists('state');
I would love to hear some alternatives to this if you guys have one!
#ErikOnTheWeb

You could use array_column for this (it's a PHP 5.5 function but Laravel has a helper function that replicates the behavior, for the most part).
Something like this should suffice.
$tag_names = array_column($tags->toArray(), 'name');

Related

Append Relation Count while using select in eloquent query

So i have a query where i want to select specific columns and relation count on a model.
Something like this
$posts = Post::withCount('comments')->select('title', 'content')->get();
foreach ($posts as $post) {
$post->comments_count // this is not available because of select in query
}
Now when I use select in the query comments_count is no longer available. There is $appends option on the model where I can do something like $appends = ['comments_count'] on the Post model but it will not work. Any idea how to append the data and use the select on model while querying using eloquent.
there is $withCount option on the model as well but it will lazy load while querying comments with post (i.e. inverse relation query).
The problem if that withCount('comments') will generate the comments_count for each post.
Also, the select() overrides the previous withCount(). Simply change the order.
$posts = Post::select('title', 'content')->withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
If you want the global count, you'll need to use a collection method to sum all the counts.
echo $posts->sum('comments_count');

Laravel: return multiple relationships

I have the following table:
The table is called user_eggs and it stores the user eggs.
eggs are items with additional data (hatch_time)
As you can see, user 2 has 2 eggs, which are items 46 and 47.
My items table stores the item general information such as name, image, description, etc...
How I can return the user eggs using $user->eggs() including the item data in my items table of the egg item_id?
I tried:
User Model:
/**
* Get the eggs
*/
public function eggs()
{
return $this->belongsToMany(Egg::Class, 'user_eggs','user_id','item_id')
->withPivot('id','hatch_time');
}
but $user->eggs() returns an empty array.
Any ideas?
A simple approach will be:
in your UserEgg model define:
/**
* Get the user associated with egg.
*/
public function _user()
{
return $this->belongsTo('App\User','user_id');
}
/**
* Get the item associated with egg.
*/
public function item()
{
return $this->belongsTo('App\Item','item_id');
}
then in your controller:
use the model to extract everything like this:
$userEggs = UserEgg::where('user_id',2)->get();
foreach($userEggs as $userEgg){
$associateduser = $userEgg->_user;
$associatedItem = $userEgg->item;
}
Short answer
If you loop through the user's eggs:
foreach($user->eggs as $egg){
$item = Item::find($egg->pivot->item_id);
}
If you want to query:
$user->eggs()->wherePivot('item_id', 1)->get();
Long answer
From the Laravel Documentation
Retrieving Intermediate Table Columns
As you have already learned, working with many-to-many relations requires the presence of an intermediate table. Eloquent provides some very helpful ways of interacting with this table. For example, let's assume our User object has many Role objects that it is related to. After accessing this relationship, we may access the intermediate table using the pivot attribute on the models:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Notice that each Role model we retrieve is automatically assigned a pivot attribute. This attribute contains a model representing the intermediate table, and may be used like any other Eloquent model.
By default, only the model keys will be present on the pivot object. If your pivot table contains extra attributes, you must specify them when defining the relationship:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
If you want your pivot table to have automatically maintained created_at and updated_at timestamps, use the withTimestamps method on the relationship definition:
return $this->belongsToMany('App\Role')->withTimestamps();
Filtering Relationships Via Intermediate Table Columns
You can also filter the results returned by belongsToMany using the wherePivot and wherePivotIn methods when defining the relationship:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);

orderBy() a relationship without using joins

Up until now, I've been doing this to order by a model's relationship:
$users = User::leftJoin('user_stats', 'users.id', '=', 'user_stats.user_id')
->orderBy('user_stats.num_followers')
->get();
Here's the User model:
class User extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'users';
public function stats()
{
return $this->hasOne('App\Models\UserStat');
}
}
Is there a better way to orderBy a model's relationship without having to use any joins? I'm using Laravel 5.2.
You could use Collection instead:
$users = User::with('stats')->get()->sortBy(function($user, $key) {
return $user['stats']['num_followers'];
})->values()->all();
https://laravel.com/docs/5.2/collections#method-sortby
As long as the column you want to order by is on a different table (eg. on a related model), you'll have to join that table in.
The Collection sort:
The proposal by crabbly does not save you the join. It only sorts the collection manually in php, after the data has been fetched using a join.
Edit: In fact, the with('stats') generates no join, but instead a separate SELECT * FROM user_stats WHERE ID IN (x, y, z, …). So well, it actually saves you a join.
Maintain a copy:
A way of truly saving the join would be to have a copy of num_followers directly in the users table. Or maybe store it only there, so you don't have to keep the values in sync.

Bulk insertion of array of eloquent models

Is it possible to insert several eloquent models in one query?
Let's say I have eloquent model Page and I want to insert array of pages or collection of pages in one query
$page1 = new Page();
$page2 = new Page();
$pages = [
$page1,
$page2,
];
or
$pages = Collection([$page1, $page2]);
I want something like
$pages->save();
But it warns that "Method save does not exist".
I saw this, but there they insert array of arrays and I want to insert array of eloquent models.
You could try the saveMany() option.
This example might work for you:
DB::table('pages')->saveMany($pages);
And if you are working with relationships you can even use a "cleaner" way (just showing you example of ways to use this function):
$book = App\Book::find(1);
$book->pages()->saveMany([
new App\Page(['title' => 'A new page.']),
new App\Page(['title' => 'Another page.']),
]);

Eloquent MorphMany relationship proxy attribute

In my application I made the strange observation, that a seemingly equal operation has different results to another.
I have the following tables:
aliases:
$table->engine = "InnoDB";
$table->increments("id");
$table->morphs("aliasable");
$table->string("alias");
$table->string("locale");
categories:
$table->engine = "InnoDB";
$table->increments('id');
The Category model has a relationship to the Alias model:
public function aliases()
{
return $this->morphMany("Alias", "aliasable");
}
When trying to manipulate the data of the alias of a category there is a difference between the two following methods:
$category = Category::find(1);
$alias = $category->aliases()->first();
$alias->alias = "test";
$alias->save();
$category = Category::find(1);
$alias = $category->aliases()->first()->alias = "test";
$category->aliases()->first()->save();
The first one is working, the second one is not saving the change.
I would like to get the second version working as I try to implement a proxy attribute on my Category model in order to change the alias like so:
$category = Category::find(1);
$category->germanAlias = "Heidi";
$category->push();
Do you have any idea why my way is not working as expected?
The key thing to understand here is the way relationships are cached on the model. When you access the relationship method directly, then no caching occurs. You are basically instantiating a new Query based off of that relationships definition.
$query = $category->aliases();
$alias = $query->first();
If you access the relationship as if it were a member/attribute of the model, then it will load and cache the relationship as a collection on the model.
$collection = $category->aliases;
$alias = $collection->first();
Future attempts to access the related model by this method will reference the same cached collection of models. This should work.
$category->aliases->first()->alias = 'test';
$category->aliases->first()->save();
Within /Illuminate/Database/Eloquent/Model __get() is a magic method that redirects request for undefined members to the getAttribute() method. The code comments do a pretty good job of explaining the rest but basically it checks for, loads, caches and reuses the relationships.
$category->aliases()->first()->save();
This line select first alias and saves it. Doesnt actually do anything. Your first method is correct. You could also do
$category->aliases()->first()->update(['alias' => 'test']);
if your alias field is included in the $fillable property of the Alias model
EDIT
Run the code below and compare the object ids;
$alias = $category->aliases()->first()->alias = "test";
var_dump(alias);
dd($category->aliases()->first());

Resources