Laravel - Adding back slash to array after edit - laravel

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.

Related

Using cast "decimal:2" on model returns string. Only "float" cast returns actual number

I'm using Laravel 8 with MySQL 8.
This works:
protected $casts = [
'amount' => 'float'
];
This doesn't work (returns string):
protected $casts = [
'amount' => 'decimal:2'
];
I'm trying to avoid float for money values.
add this in your model
public function getAmountAttribute($value) {
return round($value, 1);
}

Laravel - Mutator doesn't seem to work on update

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

laravel DB update get changes column

i want to save log of changes when i update something on the database.
there is elegant way to get the column that will be updated (just if there is change).
i want to save the old column value in log..
for example:
$updateUser = DB::table('users')->where('id','1')->update(array('email' => 'new#email.com', 'name' => 'my new name'));
from this i want to get back the old email was in database (if changed) and the old name (again, only if changed)
thanks!
As others have mentioned, Eloquent is a great way to go if using Laravel. Then you can tap directly into Laravel's events using Observers. I have used a method very similar to what is below. Of course, you would need to set up Models for User and AuditLog.
See more info regarding Observers.
https://laravel.com/docs/5.8/eloquent#observers
In Controller Method
$user = User::find(1);
$user->update([
'email' => 'new#email.com',
'name' => 'my new name'
]);
App/Providers/EventServiceProvider.php
class EventServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
User::observe(UserObserver::class);
}
}
App/Observers/UserObserver.php
class UserObserver
{
/**
* The attributes to exclude from logging.
*
* #var array
*/
protected $except = [
'created_at',
'updated_at'
];
/**
* The attributes to mask.
*
* #var array
*/
protected $masked = [
'password',
];
/**
* Listen for model saved event.
*
* #var array
*/
public function saved($model)
{
// search for changes
foreach ($model->getChanges() as $key => $new_value) {
// get original value
$old_value = $model->getOriginal($key);
// skip type NULL with empty fields
if ($old_value === '' && $new_value === null) {
continue;
}
// attribute not excluded and values are different
if (!in_array($key, $this->except) && $new_value !== $old_value) {
// mask designated fields
if (in_array($key, $this->masked)) {
$old_value = '********';
$new_value = '********';
}
// create audit log
AuditLog::create([
'user_id' => auth()->user()->id,
'model_id' => $model->id,
'model' => (new \ReflectionClass($model))->getShortName(),
'action' => 'update',
'environment' => config('app.env'),
'attribute' => $key,
'old_value' => $old_value,
'new_value' => $new_value,
]);
}
}
}
}
I hope this helps!
EDIT: See comment regarding update.
I will suggest 2 options:
1) to use the Eloquent model on every changes,
and then to use the existing methods like :
model->isDirty()
model->getChanges()
you can implement it on the model life cycle of updating / updated events listeners
more information and example you can see here:
https://laravel.com/docs/5.8/events
https://medium.com/#JinoAntony/10-hidden-laravel-eloquent-features-you-may-not-know-efc8ccc58d9e
https://laravel.com/api/5.3/Illuminate/Database/Eloquent/Model.html
2) if you want to log changes even if you are running regular queries and not only via model life cycle,
you can use MySql Triggers on every table updates and then to check OLD vs NEW and insert directly to the log changes db
more information you can find here:
https://dev.mysql.com/doc/refman/8.0/en/trigger-syntax.html
MySQL Trigger after update only if row has changed
Why not just something like this:
$changeArr = ['email' => 'new#email.com', 'name' => 'my new name'];
$id = 1;
$table = 'users';
foreach($changeArr as $key => $value){
DB::table('updateTable')->insert(['table' => $table, 'id' => $id, 'col' => $key, 'oldVal' => $value]);
}
$updateItem = DB::table($table)->where('id', $id)->update($changeArr);
Check for the changed values and update accordingly, saving the old values to log table if changed
$newData = ['email' => 'new#email.com', 'name' => 'my new name'];
$user = App\User::find(1);
$log = [];
if ($user->email != $newData['email']) {
$log['user_id'] = $user->id;
$log['email'] = $user->email;
$user->email = $newData['email'];
} elseif ($user->name != $newData['name']) {
$log['name'] = $user->name;
$user->name = $newData['name'];
$logged = DB::table('log')->insert($log);
}
$updateUser = $user->save();
//try this. hpe it helps out:
function Update(Request $request, $id)
{
$dbrecord = DB::table('users')->where('id',$id)->first();
$oldemail = $dbrecord->email;
$oldname = $dbrecord->name;
if(($oldemail==$request->input('email'))&&($oldname==$request->input('name')))
{
//do nothing
}
elseif(($oldemail!=$request->input('email'))or($oldname!=$request->input('name')))
{
$updateUser = DB::table('users')->where('id',$id)->update(array('email' => $request->input('email'), 'name' => $request->input('name')));
if($updateUser)
{
DB::table('log')->where('id',$id)->insert(array('email' => $oldemail, 'name' => $oldname));
}
}
}

Get all the related fields in one json response

i want to make an api with laravel and i want to get all the fields like the following format
projects: [
{
id,
name,
logo,
thumbnail,
phases: [{
id,
name,
date,
views: [{
id,
name,
thumbnail,
images: [{
id,
path: [ 5 urls for the various images ]
date
}]
}]
}]
}
]
my database model like the following
- projects -> hasmany phases
- phases -> hasmany view
- views -> hasmany images
the model like the following
class Project extends \Eloquent {
// Add your validation rules here
public static $rules = [
'title' => 'required',
];
// Don't forget to fill this array
protected $fillable = [ 'title', 'desc' ];
public function phases() {
return $this->hasMany('Phase');
}
}
class Phase extends \Eloquent {
protected $fillable = [ 'title', 'from', 'to', 'project_id' ];
public static $rules = [
'title' => 'required',
'from' => 'required',
'to' => 'required'
];
public function views() {
return $this->hasMany( 'View' );
}
public function project(){
return $this->belongsTo('Project');
}
}
class View extends \Eloquent {
// Add your validation rules here
public static $rules = [
'title' => 'required',
'image_path' => 'required'
];
// Don't forget to fill this array
protected $fillable = [ 'title', 'image_path', 'caption', 'view_date', 'phase_id' ];
public function phase(){
return $this->belongsTo('Phase');
}
}
How can i get all the json in the index response, i use the following but not getting the same format
Project::all();
You have to eager load the datasets:
Project::with('phases.views','phases.images')->get();
Looks like you don't have the images in your model as a related model, but you do have projects. But your json example shows images and not projects, is this right?
Relationships aren't loaded by default when retrieving a collection (this is called lazy loading). But you can load them by using the with() method (this is called eager loading):
Project:all()->with('phases')->get();
You can also chain nested relationships with dot notation:
Project:all()->with('phases.views')->get();

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