Laravel, how cast object to new Eloquent Model? - laravel

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();

Related

Eloquent casting attributes to Collections unexpected behaviour

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.

Laravel insert into database request()->all() and addition

In laravel if i want to insert all the form input and i want to add text in one of the column why cant i use this code?
Example
$B2 = new B2;
$B2::create([
request()->all(),
$B2->column9 = "aaaa",
]);
The inserted database only insert column9, the other column is Null.
Because create() accepts an array as the only parameter:
public static function create(array $attributes = [])
You can do this:
$data = request()->all();
$data['column9'] = 'aaaa';
B2::create($data);
When ever you use request all you must first make sure that you have either fillable fields in your model or guarded = to an empty array so for example:
class B2 extends Model
{
protected $table = 'db_table';
protected $fillable = [
'email',
'name',
];
}
or you can use
protected $guarded = [];
// PLEASE BE CAREFUL WHEN USING GUARDED AS A POSE TO FILLABLE AS IT OPENS YOU TO SECURITY ISSUES AND SHOULD ONLY REALLY BE USED IN TEST ENVIRONMENTS UNLESS YOU REALLY KNOW WHAT YOU ARE DOING!
As for your create method you should make sure its an associative array like this:
$B2::create([
$B2->column9 => "aaaa",
]);
Or you could do something like:
$data = $request->except('_token');
$B2::create($data);
You'll have to merge the array.
$B2::create(array_merge(request()->all(), ['column9' => 'text']));
When you are adding to a database in that was it is called mass assignment. Laravel Automatically protects against this so you need to add the firld names to a fillable attribute in your model
protected $fillable = ['field1', 'column9'] //etc
https://laravel.com/docs/5.4/eloquent#mass-assignment
You also need to make sure you pass an array to the create method
$my_array = $request->all()
$my_array['column9'] = 'aaaa';
$B2::create(
$my_array
);

Can you create a new Model instance without saving it to the database

I want to create a whole bunch of instances of a model object in Laravel, then pick the optimal instance and save it to the database. I know that I can create an instance with Model::create([]), but that saves to the database. If possible I'd like to create a bunch of models, then only "create" the one that is best.
Is this possible?
I am using Laravel 5.0
You create a new model simply by instantiating it:
$model = new Model;
You can then save it to the database at a later stage:
$model->save();
You can create instances with Model::make(). It works the same way as create but it doesn't save it.
Whether or not this is best practice is another matter entirely.
Yes, it is possible different ways: you can use the mass assignment without saving.
Please remember to set first the $fillable property in your model.
WAY #1: using the method fill
$model = new YourModel;
$model->fill([
'field' => 'value',
'another_field' => 'another_value'
]);
$model->save();
WAY #2: using the constructor
$model = new YourModel([
'field' => 'value',
'another_field' => 'another_value'
]);
$model->save();
In YourModel set the $fillable property with the fileds allowed for mass assignment:
class YourModel extends Model
{
protected $fillable = ['field', 'another_field'];
// ...
}
Laravel documentation: https://laravel.com/docs/9.x/eloquent#mass-assignment
there is also a method you can call it statically to get new instance:
$modelInstance = $modelName::newModelInstance();
it takes array $attributes = [] as a parameter

Casting Eloquent\Collection (Laravel) to stdClass array using Repository pattern

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.

passing an array from controller to model in laravel

I want to know that how to pass an array from laravel controller to laravel model .So that i can save them into db using a model . I don't want to make my controllers to heavy for that I am asking for this . When I have been saving form submissions into db where I should put my saving function ? in controller or model?
Laravel has something called Mass Assignment. It let's you pass an associative array and fills the model with those values. To make it work you have to define all the attributes you want to be able to mass assign in your model:
protected $fillable = ['foo', 'bar', 'and', 'much', 'more'];
Now you can just pass an array to the constructor of the model:
$input = [
'foo' => 'value1',
'bar' => 'value2',
'and' => 'value3',
'much' => 'value4',
'more' => 'value5',
];
$model = new Model($input);
$model->save();
Or even shorter, use the create() method which fills the model and directly saves it afterwards:
Model::create($input);
Try this..
//In Model get data from controller
public function functionname($data1, $data2){
//query
}
//In Controller sent data to Model
$data = Modelname::functionname($data1, $data2)->get();

Resources