Laravel - Mutator doesn't seem to work on update - laravel

I have the following mutator:
public function setFormattedCriteriaAttribute($value)
{
$this->attributes['formatted_criteria'] = serialize($value);
}
When I call the following why doesn't it update the formatted_criteria value - note the field is listed in my fillable attributes array ?
$jobAlert = JobAlert::findOrFail($id);
$jobAlert->update([
'frequency' => $request->frequency,
'criteria' => $criteria,
'formatted_criteria' => ['test']
]);

Be sure formated_criteria in your $fillable variable.
Update
if you have casts array in your model modify else add.
protected $casts = [
'formatted_criteria' => 'array',
];
then update your field as LONGTEXT with binary

Related

laravel endpoint hide field

How can i hide some fields ?
i want to hide the file field
Eloquent :
$reports = Report::select('id', 'file','company_id', 'title', 'year', 'created_at')
->orderBy('id', 'desc')
->paginate(10);
return ReportResource::collection($reports);
Model :
...
public function getFileSizeAttribute()
{
return Storage::disk('files')->size($this->attributes['file']);
}
....
ReportResource:
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file' => $this->whenNotNull($this->file), <-- i want to hide the file field
'file_size' => $this->fileSize, <-- but always show file_size
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
to get file_size field i must select the file field , because it's depends on it to calculate the file size.
but i want to hide the file field before send the response.
i know i can use the protected $hidden = [] method in the model , but i don't want that, because file field it's required on others place. i just want to hide it on this endpoint only.
Since you are using API resources the best and clean way to do this is by using a Resource class for your collection.
Said that, you will have 3 Resources:
The first one, as it is, just for retrieving a single Model with file and file_size attributes. The one you already have ReportResource.php
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file' => $this->whenNotNull($this->file),
'file_size' => $this->fileSize,
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
A new second resource to be used in your endpoint, without the file attribute. IE: ReportIndexResource.php
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file_size' => $this->fileSize,
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
Now you need to create a Resource collection which explicitly defines the Model Resource to use. IE: ReportCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ReportCollection extends ResourceCollection
{
/**
* The resource that this resource collects.
*
* #var string
*/
public $collects = ReportIndexResource::class;
}
Finally, use this new resource collection in your endpoint
$reports = Report::select('id', 'file','company_id', 'title', 'year', 'created_at')
->orderBy('id', 'desc')
->paginate(10);
return new ReportCollection($reports);
Of course, you can make use of makeHidden() method, but IMO is better to write a little more code and avoid a non desired attribute in your response because you forgot to make it hidden.
Also, in case you make use of makeHidden() method and you want to show the attribute in a future, you will have to update all your queries instead of a silgle resource file.
If you want to make it Hide From All Returns , you can Do this in model
protected $hidden = ['file'];
and if you want to do it temporirly with this query , you can Use MakeHidden method
$users = $reports->makeHidden(['file']);
It's clear in laravel docs , take a look
https://laravel.com/docs/9.x/eloquent-collections#method-makeHidden

Laravel - Adding back slash to array after edit

this is my controller:
$sql = Raid::findOrFail($request['id']);
$sql = $sql->update($request->all());
I have a array in my table , after update value will be like this:
"{\"Plate\":0,\"Cloth\":0,\"Mail\":0,\"Leather\":0}"
but it should be:
{"Plate":"0","Cloth":"0","Mail":"0","Leather":"0"}
so I will get an error
before this , I was updating like this and it was ok:
$sql = Raid::where('id', $request['id'])->update($request->all());
and this is my model (traders and class_traders is fields that I have problem with):
use SoftDeletes;
use \OwenIt\Auditing\Auditable;
protected $table = 'raid';
protected $dates = ['date_and_time','deleted_at'];
protected $fillable = [
'admin_id', '....
];
protected $casts = [
'bosses' => 'array',
'traders' => 'array',
'class_traders' => 'array',
'boosters' => 'array',
];
I think what you need to do is don't update the model using the update($request->all()) way, but you need to specify each of the field's value. For the JSON field, you could try using json_encode() to save it.
That should do it.

How to use relationships in laravel 8

my question has two parts
Firstly, My if statement is not working. My if statement is as followed:
if ($request->is_published) {
$resources_page->published_at = now();
}
This is stored in my controller, I have a model for this and it is as followed:
public function is_published()
{
return $this->published_at !== null;
}
It is meant to check whether my checkbox is checked and return the timestamp, I have it cast in my model like followed:
protected $casts = [
'published_at' => 'datetime',
];
And in my view:
#include('components.form.input-checkbox', [
'label' => 'Publish?',
'form_object' => 'page',
'name' => 'is_published'
])
Could anyone elude to the answer?
Secondly, when trying to sync, it is not storing to my resources_category_resources_page table
In my controller, i have the following code
$resources_page->resources_categories()->sync(
ResourcesCategory::whereIn('slug', $request->resources_categories)->pluck('id')
);
In my model I have the relationships declared properly, so I don't know why its not storing?

Laravel insert and update to JSON not working

Can't figure out why JSON field is being ignored.
This one doesn't work:
Registries::create([
'nr' => $old_document->no,
'metas->name' => 'r01',
]);
In model I have set:
protected $casts = [
'metas' => 'array',
];
And:
protected $fillable = [
'nr',
'metas'
];
I think the problem is in attributes casting, because this one is working:
Registries::create([
'nr' => $old_document->no,
'metas' => json_encode(['name'=>'r01']),
]);
I'm not getting any errors just JSON column stays empty.
I'm not fan of attribute casting in relational dabatases.
So it's just my guess based on documentation about array.
The array cast type is particularly useful when working with columns that are stored as serialized JSON.
You're trying to put associative array which for JSON is an object.
I might guess that it's a bug.
So try to typecast it on insert:
Registries::create([
'nr' => $old_document->no,
'metas' => (object)['name'=>'r01'],
]);
attribute casting
and make it object:
protected $casts = [
'metas' => 'object',
];
So it seams that $metas->name is working only for updating, in my case I need to insert like this:
Registries::create([
'nr' => $old_document->no,
'metas' => ['name'=>'r01']
]);

Fillable list ignored while inserting related model

I am using Ardent and I faced strange behaviour of ignoring $fillable list while inserting/updating related models.
I have the following models defined:
class User extends LaravelBook\Ardent\Ardent
{
protected $table = 'users';
public static $relationsData = [
'contacts' => [self::HAS_MANY, 'Contact'],
];
}
class Contact extends LaravelBook\Ardent\Ardent
{
protected $table = 'user_contacts';
protected $guarded = ['*'];
protected $fillable = [
'user_id',
'type',
'value'
];
public static $relationsData = [
'user' => [self::BELONGS_TO, 'User'],
];
}
Now I am trying to add new contact to user:
$user->contacts()->create([
'type' => 'some type',
'value' => 'some value',
'unknown_field' => 'unknown value'
]);
... and I got SQL insert error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'unknown_field' in 'field list' (SQL: insert into `user_contacts` (`type`, `value`, `unknown_field`, `user_id`, `updated_at`, `created_at`) values (?, ?, ?, ?, ?, ?)) (Bindings: array ( 0 => 'some type', 1 => 'some value', 2 => 'unknown value', 3 => 2, 4 => '1384854899', 5 => '1384854899', ))
In the same time this is working fine:
UserContact::create([
'user_id' => 2,
'type' => 'some type',
'value' => 'some value',
'unknown_field' => 'unknown value'
]);
I didn't get any SQL errors and 'unknown_field' was just ignored.
Any ideas why $fillable fields could be ignored while working via builder?!
I don't understand why the HasManyOrOne relationship intentionally ignores fillable. It seems really counter intuitive. Either way, I think this should work for you.
$user->contacts()->save(Contact::create([ ... ]));
It seems I found the reason of this behaviour. This is explicitly implemented in HasOneOrMany abstract class.
abstract class HasOneOrMany extends Relation {
...
/**
* Create a new instance of the related model.
*
* #param array $attributes
* #return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes)
{
$foreign = array(
$this->getPlainForeignKey() => $this->parent->getKey()
);
// Here we will set the raw attributes to avoid hitting the "fill" method so
// that we do not have to worry about a mass accessor rules blocking sets
// on the models. Otherwise, some of these attributes will not get set.
$instance = $this->related->newInstance();
$instance->setRawAttributes(array_merge($attributes, $foreign));
$instance->save();
return $instance;
}
...
}
I am still looking for the suffitient solution to control this behaviour.
As stated in the offical documentation:
To get started, set the fillable or guarded properties on your model.
You have set both. You should remove the following line: protected $guarded = ['*'];
Fortunately this will be fixed in version 4.2: https://github.com/laravel/framework/pull/2846
Added to all this, you can also filter the attributes manually:
$input = [
'user_id' => 2,
'type' => 'some type',
'value' => 'some value',
'unknown_field' => 'unknown value'
];
$fillable = $user->contacts()->getRelated()->fillableFromArray($input);
$user->contacts()->create($fillable);
Keeping in mind that the example are using Eloquent\Model\fillableFromArray() method, which is protected, so it will be necessary, for example, replicate it:
class BaseModel extends Eloquent
{
public function fillableFromArray(array $attributes)
{
return parent::fillableFromArray($attributes);
}
}
Use protected $guarded = array(); instead of protected $guarded = ['*'];
by using [*] you're telling laravel to guard all entities from autohydration / mass assignment!
array() sets this $guarded list to null.
The fillable property specifies which attributes should be mass-assignable. This can be set at the class or instance level.
The inverse of fillable is guarded, and serves as a "black-list" instead of a "white-list":
Read more at Laravel documentation on mass assignment
The update methods are not on the model level, and won't respect the $fillable fields.
You could filter the input data by using Input::only['fillable fields here']

Resources