Laravel 5 Eloquent, How to set cast attribute dynamically - php

In laravel 5.1 there is new feature called Attribute Casting, well documented at here :
http://laravel.com/docs/5.1/eloquent-mutators#attribute-casting
My question is, it is possible to make attribute casting dynamically ?
for example, I have a table with columns :
id | name | value | type |
1 | Test_Array | [somearray] | array |
2 | Test_Boolean | someboolean | boolean |
it is possible to set value attribute cast, depends on type field, that work both in write(create/update) and fetch ?

You'll need to overwrite Eloquent model's getCastType() method in your model class:
protected function getCastType($key) {
if ($key == 'value' && !empty($this->type)) {
return $this->type;
} else {
return parent::getCastType($key);
}
}
You'll also need to add value to $this->casts so that Eloquent recognizes that field as castable. You can put the default cast there that will be used if you didn't set type.
Update:
The above works perfectly when reading data from the database. When writing data, you have to make sure that type is set before value. There are 2 options:
Always pass an array of attributes where type key comes before value key - at the moment model's fill() method respects the order of keys when processing data, but it's not future-proof.
Explicitely set type attribute before setting other attributes. It can be easily done with the following code:
$model == (new Model(['type' => $data['type']))->fill($data)->save();

The $casts attribute it used whenever you access a field, not when it is fetched from the database. Therefore, you can update the $casts attribute after the model has been populated, and it should work fine whenever you access the value field. You just need to figure out how to update the $casts attribute when the type field is changed.
One potential option would be to override the fill() method so that it calls the parent fill() method first, and then updates the $casts attribute with the data in the type field.
Another potential option would be to abuse the mutator functionality, and create a mutator on the type field, so that whenever it changes, it would update the $casts attribute.

Some of the answers here are really overthinking things, or they're subtly wrong.
The principle is simply to set the $casts property before you need it, eg. before writing or reading properties to the database.
In my case I needed to use a configured column name and cast it. Because PHP doesn't allow function calls in constant expressions, it can't be set in the class declaration, so I just declared my column/property's cast in my model's constructor.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class MyModel extends Model
{
protected $casts = [
// we can't call a function in a class constant expression or
// we'll get 'Constant expression contains invalid operations'
config('my-table.array-column.name') => 'array',
];
public function __construct()
{
$this->casts = array_merge(
$this->casts,
[
// my column name is configured so it isn't known at
// compile-time so I have to set its cast run-time;
// the model's constructor is as good a place as any
config('my-table.array-column.name') => 'array',
]
);
parent::__construct(...func_get_args());
}
}

Related

What's difference between Laravel Model functions "create" and "insert"?

Laravel Model allows two functions for inserting the values to the database table. They are
Create:
User::create(['id'=>1,'name'=>'stack']);
Insert:
User::insert(['id'=>2,'name'=>'overflow']);
I found they perform similar operations. What's difference between them?
insert() :
If you using insert() method you can't default created_at and updated_at database column
it will be null
DefaultUser::insert(['username' => $request->username, 'city' => $request->city, 'profile_image' => $request->profile_image]);
create() :
when we use create method you must define this model in fillable fields
Add in Your Model
protected $fillable = ['username','city', 'profile_image'];
Add your Controller
DefaultUser::create(['username' => $request->username, 'city' => $request->city, 'profile_image' => $request->profile_image]);
then we can use create method without **mass assignment error **
basically here , table defined fields are protected in your model
you should define which model attributes you want to make mass assignable. You may do this using the $fillable property on the model
The model does not have an insert, calling Model::insert results in a call to (the Query Builder) Builder::insert through the __call() magic method which then avoids the Eloquent benefits like setting the timestamps for created_at and updated_at fields.
It also avoids the Mass-assignment protection which Eloquent protects you from inserting unintentional data.
So I would always use create or setting each field separately (if you need to modify the incoming data) and call save() on the model instance.
Insert method :
The insert method accepts an array of column names and values.using this method you can insert data without specify fillable and guarded attribute on the model and here created_at and updated_at values put as NULL value by default.
User::insert(['userName'=>'manish','email'=>'test#gmail.com']);
Create method
The create method also used to insert a new model in a single line. It's instance will return you from that method. before using create() will need to specify fillable or guarded attribute on model its protect against mass-assignment by default and its auto fillable value for create_at and updated_at
User::create(['userName'=>'manish','email'=>'test#gmail.com'])
save()
save() method is used both for saving new model, and updating existing one. here you are creating new model or find existing one, setting its properties one by one and finally saves in database.
save() accepts a full Eloquent model instance
create()
while in creating method you are passing an array, setting properties in model and persists in the database in one shot.
create() accepts a plain PHP array

October cms, cant save model with empty values

I cretead model, with columns:
like4u_id, vtope_id, panel_id
I set these columns option - Nullable, but if i save model, i get error:
SQLSTATE[HY000]: General error: 1366 Incorrect integer value: '' for
column 'like4u_id' at row 1 (SQL: update
master_vars set updated_at = 2018-05-23
16:03:14, like4u_id = , vtope_id = where id = 328)
What is the problem? It's field not required and column nullable..
It may happen with MySQL databases.
You need to use the Nullable trait in your model for set to NULL the attributes when left empty.
Here is the docs of the Nullable trait
When a form field is empty but should contain an "integer" value in the database some engines will try to insert an empty string into an integer field.
This is what causes the error message, basically trying to put the square through the round hole.
If you implement the Nullable trait you can make sure that these fields, if left empty are inserted as a true NULL instead of a string
I prefer to have my trait as a use statement above the class and then a short use int the class itself.
You can also put it directly in the class by use \October\Rain\Database\Traits\Nullable; instead of use Nullable; if you don't wish to have it appear in two spots. It's up to your preference.
use Model;
use October\Rain\Database\Traits\Nullable;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/**
* Page Model
*/
class ModuleLink extends Model
{
public $table = 'your_table_name_here';
use Nullable; // This sets the trait to be used in this Model.
//^^^^^^^^^^^^^
public $nullable = [
'module_id', // Define which fields should be inserted as NULL when empty
'sort_order',
];

Why is toJSON returning incorrect value

if I run the following:
App\User::selectRaw('country as id, COUNT(*) as value')->groupBy('country')->get()
I get the correct output:
all: [ App\User {#705 id: "UK", value: 2,},],}
However when I run
App\User::selectRaw('country as id, COUNT(*) as value')->groupBy('country')->get()->toJSON()
it seems to flip the id value to 0:
[{"id":0, "value":2}]
now what's interesting is if I convert it so that I don't alias the country field I get the correct output:
[{"country":"UK", "value":2}]
I need to have the field returned as id but can't seem to work out why it's doing this
When you call toJson() on the model, if the $incrementing property is true, Laravel will automatically attempt to cast the $primaryKey field to the $keyType type. If you have not overridden any of these values, that means the id field will get cast to an int during the json generation.
In your select, you named your country field as id, so when you generate the json, it attempts to cast this value (UK) to an int, which of course is 0.
You can change your select statement to not name the field id, or you can work around this functionality in a ton of different ways, either modifying model properties before and after the call to toJson(), or by overriding the getCasts() method to avoid this functionality altogether.
Edit
Without knowing your codebase or your use cases, I would say the most flexible solution for you would be to add a new method to your App\User model, something like:
public function toJsonCustom($options = 0)
{
$incrementing = $this->getIncrementing();
$this->setIncrementing(false);
$json = $this->toJson($options);
$this->setIncrementing($incrementing);
return $json;
}
Then, you call your custom toJson method instead of the original one:
App\User::selectRaw('country as id, COUNT(*) as value')
->groupBy('country')
->get()
->toJsonCustom();

How to add value of guarded fields in database

I am using laravel 5.3 and in my custom model, I have some guarded fields like following.
protected $guarded = ['id', 'tnant_id', 'org_id', 'fac_id', 'slug', 'created_at', 'updated_at', 'deleted_at'];
Now When I try to add record using following.
CUSTOM::create(['tnant_id'=>123]);
It returns me following error.
Field 'tnant_id' doesn't have a default value.
Setting field default value in table will not work because each time I am passing value and it is giving error for all guarded fields.
So how I can add guarded fields value in database? In update query, It is allowing to update but on create it gives error.
You simply can't. Model::create(array $attributes = []) is using method fill(array $attributes = []), which, we may say, filter out all guarded attributes, so they will not be assigned. So in point of creation tnant_id will be null.
I come up with two ways of doing this:
A
create a new model instance
set your attribute
save (persist) it to dabase;
So:
$model = new Model;
$model->tnant_id = 123;
$model->save();
B
This is more likely update than create, but, might be useful for you.
Change your DB schema to allow null values for your attribute or put default value.
create model using Model::create()
set attribute & update.
So:
Assuming you are using migrations, in your migration file use:
Schema::create(..
$table->integer('trant_id')->nullable();
//OR
$table->integer('trant_id')->default(0);
...);
Note: It's hard to say which one is more suitable for you use-case, but I see your attribute is called trant_id, which is some form of relation I guess, so I suggest you to take look at Eloquent's relationship, which might be a better answer.

Is possible get olny form fields have Similar Name of model properties in update method of resource controller

I have a Course Model that have many fields like this :
course_id
title
description
creator
start_date
end_date
reg_start_date
reg_end_date
picture
lesson_count
cost
status
active
teacher
created_at
updated_at
deleted_at
And I have a Form to edit a specified Model. action attribute of the edit form tag is referenced to course.update route.
In the edit Form,in addition to fields with same names of above Model properties, there are many other form fields that not related to Course Model (and used for manyTomany relations or other operations)
Now in public update method , when I want to use Eloquent update() method , Since the number of irrelevant field names are many, I must to use except() method for incoming request. like this :
public
function update (StoreCourseRequest $request, $id)
{
$data = $request->except(['search_node', '_token', 'start_date_picker', 'end_date_picker', 'reg_start_date_picker', 'reg_end_date_picker', 'orgLevels', 'courseCats','allLessonsTable_length']);
$course = Course::findOrFail($id);
$course->update($data);
$course->org_levels()->sync($request->get('orgLevels'));
$course->course_categories()->sync($request->get('courseCats'));
$result = ['success' => true];
return $result;
}
As you see on usage of $request->except() method, I passed many field names to it to filter only proper attributes for use in $course->update($data);.
Now my Question is that Are there any way that we can get only same name model attributes from a field name?
If I understand your question correctly you are trying to avoid having to use the except() method for incoming requests, correct?
If that is the case, you can just skip it altogether and pass the entire request to the update() method as it will only update matching fields (provided they are listed as "fillable" in the method class). This process is called "mass-assignment".

Categories