I'm trying to implement the Repository pattern in a Laravel 5 app by following this article. In it, the repository implementation converts the object for the specific data source (in this case Eloquent) to an stdClass so that the app uses a standard format and doesn't care about the data source.
To convert a single Eloquent object they do this:
/**
* Converting the Eloquent object to a standard format
*
* #param mixed $pokemon
* #return stdClass
*/
protected function convertFormat($pokemon)
{
if ($pokemon == null)
{
return null;
}
$object = new stdClass();
$object->id = $pokemon->id;
$object->name = $pokemon->name;
return $object;
}
Or, as someone in the comments pointed out, this could also work:
protected function convertFormat($pokemon)
{
return $pokemon ? (object) $pokemon->toArray() : null;
}
But then, what happens when I want to cast an entire collection of Eloquent objects to an array of ** stdClass **? Do I have to loop through the collection and cast each element separately? I feel this would be a big hit on performance, having to loop and cast every element every time I need a collection of something and it also feels dirty.
Laravel provides Eloquent\Collection::toArray() which turns the entire collection to an array of arrays. I suppose this is better, but still not stdClass
The benefits of using a generic object would be that I could do this in my code
echo $repo->getUser()->name;
Instead of having to do this:
echo $repo->getUser()['name'];
Using eloquent you can do something like this:
/**
* Gets the project type by identifier.
*
* #param string $typeIdentifier
*
* #return object
*
*/
public function getTypeByIdentifier($typeIdentifier)
{
$type = ProjectType::where(
'type_identifier', $typeIdentifier
)->first();
return (object) $type->toArray();
}
All my factories etc accept stdClass so that it's uniform. In eloquent you can either do as above as Eloquent already has a toArray() function which is needed for serialisation, but you can also easily extend Model (Illuminate\Database\Eloquent) to have this method available to all your eloquent models. I suggest that you extend the model so that you can also automate this collections and not just single records.
Because I use the repository pattern with Eloquent I normally create an abstract EloquentRepository which extends the Eloquent Model methods and also obviously allows us to add new methods such as this one.
You can do something like this way,
For example there is User class
$user = User::find(1)->toArray();
//this is code for convert to std class
$user = json_encode($user);
$user = json_decode($user);
json_decode by default return stdClass object.
I hope this will help.
Yes, you would need to loop through the collection and cast every object. You can save a few lines of code by using array_map.
You can use the getQuery() method to convert/cast the \Illuminate\Database\Eloquent\Builder to \Illuminate\Database\Query\Builder.
return $this->model->getQuery()->get();
will return a collection (or an array before 5.3) of stdClass objects.
return $this->model->where('email', $email)->getQuery()->first();
will return a stdClass object.
There is no need to fetch eloquent models and convert them one by one.
Related
I have two objects of the same record which I am getting from the database. One is before the update, and the other is after the update. I want to know the column values which are changed during this update query.
$before_update = DeliveryRun::find($id);
$before_update->name = $request->input('name');
$before_update->save();
$after_update = DeliveryRun::find($id);
compare($before_update, $after_update)
I would define a method on your DeliveryRun model which can be used to compare objects of the same type.
Lets say we want to be able to do something like $deliveryRun->compareTo($otherDeliveryRun). That seems like a nice fluid syntax and reads well in my opinion.
What we want to do is get the attributes and their values for the DeliveryRun we're calling compareTo on and then compare them against the attributes and values for the DeliveryRun we provide as an arguement to the compareTo method.
class DeliveryRun extends Model
{
public function compareTo(DeliveryRun $other)
{
$attributes = collect($this->getAttributes())
->map(function ($attribute, $key) use ($other) {
if ($attribute != $other->$key) {
return $key = $attribute;
}
})->reject(function ($attribute, $key) {
return !$attribute || in_array($key, ['id', 'created_at', 'updated_at']);
});
return $attributes;
}
}
The above gets the attributes for the current ($this) DeliveryRun, converts the array returned from getAttributes() to a collection so we can use the map() function and then loops over each attribute on the DeliveryRun model comparing the key and value of each against the $other DeliveryRun model provided.
The reject() call is used to remove attributes which are the same and some attribute keys which you might not be interested in leaving you just the attributes that have changed.
Update
I am saving object in other variable before update $before_update = $delivery_run; but after update $before_update variable I also gets updated
If I am understanding you correctly, you're still comparing the same object to itself. Try something like the following.
$before = clone $delivery_run; // use clone to force a copy
$delivery_run->name = 'something';
$delivery_run->save();
$difference = $before->compareTo($delivery_run);
I would consider using getChanges() as suggested by #Clément Baconnier if all you're doing is looking to get the changes of an object straight after the object has been saved/updated.
Q1. I have an Eloquent model that casts an attribute to a Collection.
Calling Collection's method on this attribute doesn't affect the model values. Eg: put()
When using Collections , iam able to do this :
$var = collect();
$var->put('ip', '127.0.0.1');
var_dump($var);
Output as expected :
object(Illuminate\Support\Collection)[191]
protected 'items' =>
array (size=1)
'ip' => string '127.0.0.1' (length=4)
But when i use with a casted attribute on a Eloquent model, this doesn't work as expected
$user = App\User::create(['email'=>'Name', 'email'=>'mail#example.com', 'password'=>bcrypt('1234')]);
$user->properties = collect();
$user->properties->put('ip', '127.0.0.1');
var_dump($user->properties);
object(Illuminate\Support\Collection)[201]
protected 'items' =>
array (size=0)
empty
This doesn't populate the field.
I think that another collection is created, so to work as expected i must assign this new collection to my field.
Like so :
$user->properties = $user->properties->put('ip', '127.0.0.1');
Q2. Is there a proper way to initialize collection of the field by default (create an empty collection if the field is null), without having to call $user->properties = collect(); "manually" every time?
User.php
class User extends Authenticatable
{
protected $casts = [
'properties' => 'collection',
];
...
}
Migration file
Schema::table('users', function($table) {
$table->text('properties')->nullable();
});
Q1: an attribute casted to collection has a getter that returns, each time, a new BaseCollection that is constructed on the value of the attribute.
As already supposed the getter returns another collection instance and every direct change on it does not change the value of the attribute but instead the newly created collection object.
As also pointed by you the only way to set a a collection casted attribute is to assign it his own original value merged with new ones.
So instead of put() you have to use:
$user->properties = $user->properties->put('ip', '127.0.0.1');
// or
$user->properties = $user->properties ->merge(['ip'=>'127.0.0.1'])
Q2: We have to think that the database representation is a text; so IMHO the proper way to initialize a Model in the migration is to give it a default empty json, i.e.:
$table->text('properties')->default('{}');
But this works only for models created without setting the property field and retrieved after.
For a newly created Model my advice is to pass a default void array, i.e.:
App\User::create([
'name'=>'Name',
'email'=>'mail#example.com',
'password'=>bcrypt('1234'),
'properties' => []
]);
In addition to dparoli's outstanding answer, it is also possible to add a default value through Laravel's boot method, which is available on every Model.
Something like the following example code
protected static function boot()
{
parent::boot(); //because we want the parent boot to be run as well
static::creating(function($model){
$model->propertyName = 'propertyValue';
});
}
You can play with this approach if you like as well.
I get via Request a Json Object.
I clearly parse this object in order to check if it may fit the destination model.
Instead of assigning property by property. Is there a quick way to populate the model with the incoming object?
If you have an array of arrays, then you can use the hydrate() method to cast it to a collection of the specified model:
$records = json_decode($apiResult, true);
SomeModel::hydrate($records);
If you just have a single record, then you can just pass that array to the model’s constructor:
$model = new SomeModel($record);
Just pass your object casted to array as Model constructor argument
$model = new Model((array) $object);
Internally this uses fill() method, so you may first need to add incoming attributes to $fillable property or first create model and then use forceFill().
You should convert that object to array and use fill($attributes) method.
As method name says, it will fill object with provided values. Keep in mind that it will not persist to database, You have to fire save() method after that.
Or if You want to fill and persist in one method - there is create($attributes) which runs fill($attributes) and save() under the hood.
You can use Mass Assignment feature of Laravel,
You model would look like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'email', 'phone'];
}
And the process of populating the Model would be like this:
// This would be your received json data converted to array
// use 'json_decode($json, true)' to convert json data to array
$json_arr = [
'name' => 'User Name',
'email' => 'email#example.com',
'phone' => '9999999999'
];
$user = new \App\User($json_arr);
Hope this helps!
Castings may fail due to several reasons. A safe way is to add a static function to the model to generate from both array or object. feels like an extension to the model.
public static function generateFromObject($object)
{
$myModel = new MyModel();
foreach($object as $k => $v)
$myModel->{$k} = $v; //for arrays $myModel[$k] = $v;
return $myModel;
}
and you can use anywhere like,
$myModel = MyModel::generateFromObject($myObjectOrArray)->save();
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');
In Rails (and even in Doctrine < 2, IIRC) you can specify a default order for any model. For example, if you tell Rails to always order your customer table by name, Customer.all will always a list of customers ordered by name. It makes an enormous amount of sense.
From what I gather it's not possible to do this in Doctrine 2. Evidently they want you to create a query instead.
It would be a very DRY, logical and convenient feature to include, and an outstandingly stupid feature to choose to leave out, it seems to me.
I sincerely hope I'm wrong about this option not existing, and before I cry myself to sleep tonight, I wanted to check to see if maybe Doctrine does actually have a way to specify a default order and I just haven't been able to find it. Can anyone enlighten me?
Whilst you don't seem to be able to do this for an entire model ala Doctrine 1, you can specify ordering as a notation on the inverse side of a relation:
// Entity/Category
/**
* #var ArrayCollection $posts
*
* #ORM\OneToMany(targetEntity="Post", mappedBy="category")
* #ORM\OrderBy({"name" = "ASC"})
*/
private $posts;
Also, if you're implementing entity manager services such as those in SonataNewsBundle, you can specify defaults via optional arguments i.e.
class PostManager extends ModelPostManager
{
/**
* {#inheritDoc}
*/
public function findBy(array $criteria, array $orderBy = array('name' => 'asc'))
{
return $this->em->getRepository($this->class)->findBy($criteria, $orderBy);
}
}
$items = $entityManager()->getRepository('Item')->findBy(array(),array('field_to_sort_on'));
And of course you can just add a method to the Item repository
public function findAllWithDefaultSort()
{
return $this->findBy(array(),array('default_field_to_sort_on'));
}
No real need to make a query in this case. Keep in mind that D2 focuses on object models with relations. There are a gazillion active record based alternatives out there.
To answer your question: No.
I had the same question, I found that overriding the findAll() function in your Repository works pretty well:
public function findAll()
{
return $this->findBy(array(), array('lft'=>'asc'));
}
A relatively simple way to achieve this is to override the findBy method in the Entity's repository class:
class MyEntityRepository extends EntityRepository
{
/**
* #inheritdoc
*/
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
$orderBy = $orderBy === null ? array('added' => 'desc') : $orderBy;
return parent::findBy($criteria, $orderBy, $limit, $offset);
}
}
This adds a default ordering to all the findBy methods, and allows you to override the ordering if you need to. The same can be done for findOneBy if needed.
It looks like no, there is no way to specify a default order in Doctrine 2.
findBy's parameter 1 is the search criteria, parameter 2 is orderby and seems to need to take either ASC (ascending) or DESC (descending)
$accounts = $this->em->getRepository('Entities\User')
->findBy(array('active' => 1), array('email' => 'ASC'));