Eloquent allows Enum Casting.
Eloquent also allows you to cast your attribute values to PHP enums.
To accomplish this, you may specify the attribute and enum you wish to
cast in your model's $casts property array:
use App\Enums\ServerStatus;
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'status' => ServerStatus::class,
];
Once you have defined the cast on your model, the specified attribute
will be automatically cast to and from an enum when you interact with
the attribute:
if ($server->status == ServerStatus::provisioned) {
$server->status = ServerStatus::ready;
$server->save();
}
Is it possible to use enum casting in eloquent for array values?
I have an existing eloquent model that use to have one type. And now it needs to support multiple.
Does Laravel support array enum casting or ist this not possible? What would be an alternative approach to achieve something similar?
you can use spatie/laravel-enum
after installing it:
composer require spatie/laravel-enum
you can use it for array enum casting, something like:
protected $casts = [
'status' => StatusEnum::class.':collection',
];
and if the status is nullable you can:
protected $casts = [
'status' => StatusEnum::class.':collection,nullable',
];
this package also provider validation rule and other features.
and here is an interesting pull request that has been merged, but I did not test it.
Related
Is there a way to use the #paginate directive from lighthouse-php without querying data from a model? Let say, i using a third party library to query data using an api or so.
Fortunately, such a feature has been added very recently: https://github.com/nuwave/lighthouse/pull/2232. This PR added support for returning data in a Paginator from option resolver in #paginator directive.
You can provide your own function that resolves the field by directly returning data in a \Illuminate\Contracts\Pagination\Paginator instance.
This is mutually exclusive with builder and model. Not compatible with scopes and builder arguments such as #eq.
type Query {
posts: [Post!]! #paginate(resolver: "App\\GraphQL\\Queries\\Posts")
}
A custom resolver function may look like the following:
namespace App\GraphQL\Queries;
use Illuminate\Pagination\LengthAwarePaginator;
final class Posts
{
/**
* #param null $root Always null, since this field has no parent.
* #param array{} $args The field arguments passed by the client.
* #param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context Shared between all fields.
* #param \GraphQL\Type\Definition\ResolveInfo $resolveInfo Metadata for advanced query resolution.
*/
public function __invoke($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): LengthAwarePaginator
{
//...apply your logic
return new LengthAwarePaginator([
[
'id' => 1,
'title' => 'Flying teacup found in solar orbit',
],
[
'id' => 2,
'title' => 'What actually is the difference between cookies and biscuits?',
],
], 2, 15);
}
}
(The docs are currently not getting updated correctly, which is why you probably did not find out about this. I am working on restoring the deployment.)
In the Laravel 5.5 documentation it is explained that in order to define the database fields it is necessary to write something like this in the model:
protected $fillable = [
'name', 'email', 'password', 'birth_date'
];
How can I specify the type of field (boolean, datetime, etc)?
in the $fillable array you need to define the accessible fields from ur database with ur model, read this in the laravel doc fillable.
to specify the type of field u can use the $casts array inside ur model to cast the data from ur database inside the model with a specific type for that read this in the laravel doc casts
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.
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
);
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();