Laravel overridden/appended date attributes aren't serialized in json output - laravel

I have an Organisation model that has a two data attributes, allowance_starts_at and allowance_ends_at, when returning this model as JSON I get the following back:
allowance_starts_at: "2017-06-01 00:00:00",
allowance_ends_at: "2018-05-31 23:59:59",
However I want to override these attributes to set the year to be the current year (or allowance period) so come 2018-06-01 I want the attributes to return:
allowance_starts_at: "2018-06-01 00:00:00",
allowance_ends_at: "2019-05-31 23:59:59",
To achieve this I'm overriding the attributes in the model like so:
public function getAllowanceStartsAtAttribute($value)
{
$currentYear = Carbon::now()->format('Y');
$d = Carbon::createFromFormat('Y-m-d H:i:s', $value);
$d->year($currentYear);
return $d;
}
public function getAllowanceEndsAtAttribute($value)
{
$d = Carbon::createFromFormat('Y-m-d H:i:s', $value);
if ($d->copy()->subYear()->addSecond() != $this->allowance_starts_at) {
$d->addYear();
}
return $d;
}
This works and gives me the result I want however, now when I output the model as JSON the Carbon date isn't serialised:
allowance_starts_at: {
date: "2017-01-01 00:00:00.000000",
timezone_type: 3,
timezone: "Europe/London"
},
allowance_ends_at: {
date: "2017-12-31 23:59:59.000000",
timezone_type: 3,
timezone: "Europe/London"
},
I've also tested this with appended attributes, so instead of overriding an existing attribute I create an appended attribute that just returned Carbon::now() and set the appended attribute in the $dates array and this outputs in the same way i.e. not serialized.
Does anyone now how Laravel determines how to output the attributes e.g. as Carbon or as a string?
Does anyone know how I can make this work?
Update
Thanks Paul. Using a setter mutator gave me the behaviour that I needed. I replaced my overriding methods with:
public function setAllowanceStartsAtAttribute($value)
{
$currentYear = Carbon::now()->format('Y');
$value->year($currentYear);
$this->attributes['allowance_starts_at'] = $value;
}
public function setAllowanceEndsAtAttribute($value)
{
if ($value->copy()->subYear()->addSecond() != $this->attributes['allowance_starts_at']) {
$value->addYear();
}
$this->attributes['allowance_ends_at'] = $value;
}
And now I get this output when returning JSON:
allowance_starts_at: "2018-06-01 00:00:00",
allowance_ends_at: "2019-05-31 23:59:59",

Have you tried using a mutator? Like instead of getAllowanceEndsAtAttribute, make it setAllowanceEndsAtAttribute?

Related

a problem with using accessors in laravel

I need to use created_at field in two different type
public function getCreatedAtAttribute($value)
{
$time = new \Verta($value);
return $time->formatDifference();
}
this is my code and I need to use default created_at value as well
please help me
You can try to make another accessor with a different name (created_at_diff perhaps) for your special format:
public function getCreatedAtDiff()
{
return (new \Verta($this->attributes['created_at']))->formatDifference();
// or $this->created_at instead of $this->attributes['created_at']
}
Then you can access it via $model->created_at_diff.

Laravel array sum of fields validation

I'm new to Laravel where I'm using Laravel's validator for a project not built in Laravel.
I need to know if there is a simple built in Laravel validator to validate the sum of a certain field in all of the objects in an array.
My input looks something like:
{
"customer":95,
"name": "My object",
"values":
[
{
"name": "AAA",
"percentage": 50
},
{
"name": "BBB",
"percentage": 50
}
]
}
I need to make sure that the sum of my percentages is 100. Is there a simple way?
Use Custom Validation Rules for single attribute validations.
Use After Validation Hook for other or more complex validations, say the sum of multiple fields.
public function withValidator($validator)
{
$validator->after(function ($validator) {
if ($this->get('field1') + $this->get('field2') + $this->get('field3') != 100) {
$validator->errors()->add(null, 'The sum of field1, field2 and field3 must be 100');
}
});
}
I think it would be best for you to create a custom validation rule. In the validation, I'd convert the values array to a collection and use the collection sum method. E.g.:
public function passes($attribute, $value)
{
$values = collect($value);
return $values->sum('percentage') <= 100;
}

Laravel custom attribute not being mutated to Carbon date

I'm having problems getting Laravel to cast a custom attribute as a Carbon date. Here's an example model:
class Organisation extends Model
{
protected $dates = [
'my_date'
];
public function getMyDateAttribute()
{
return "2018-01-01 00:00:00";
}
}
I'd expect my_date to be cast to a Carbon date however if I do dd($organisation->my_date) it just returns the date as a string.
I've seen people suggest to just return a Carbon instance from the custom attribute, and this partially works, the my_date attribute is availabe as a Carbon instance within the application, however if you then return the model as Json you end up with:
{
"name": "Big Business",
"my_date": {
"date": "2018-01-01 00:00:00.000000",
"timezone_type": 3,
"timezone": "Europe/London"
}
}
Instead of the desired:
{
"name": "Big Business",
"my_date": "2018-01-01 00:00:00"
}
Has anyone else come across this and if so have you found a solution?
Update
Upon further investigation I've tracked down the problem (but I don't have a solution yet). When you return an Eloquent model the __toString magic method which runs the toJson method and as you trace down the chain it serializes any Carbon dates in the $dates variable. It also completely skips over this serialization for mutated attributes, which is what I'm seeing.
I need to find a way to seralize mutated attributes that return a Carbon date when __toString is called.
edit your Organization model to this:
use Carbon\Carbon;
class Organisation extends Model
{
public function getMyDateAttribute($value)
{
//you can manipulate the date here:
$date = Carbon::parse($value,config('timezone'));
return $date;
}
}
You if your model represents a table, you can change the data type to timestamp and laravel will put that attribute into a carbon object.
Once the attribute is a carbon object, you can change the format in the blade view.
{{ $organisation->mydate->format('Y-m-d') }}
If either cannot change the data type, or you need to a default format different from a timestamp you can use the 'cast' eloquent model property.
class Organisation extends Model{
protected $cast = [
'mydate'=>'datetime:Y-m-d H:i:s'
];
}
Casting the attribute effectively works the same as an accessor that wraps the date value with a carbon object. This is much cleaner way to write it, however.
As far as timezone is concerned, you should look to change that in the config/app.php file.
Here is the documentation.... https://laravel.com/docs/5.8/eloquent-mutators#attribute-casting

Laravel - How to merge relationship results?

how can i merge laravel relationship results in to one object?
MyController.php
public function getUser($id) {
return TournamentUser::where('customer_id','=', $id)->with('user')->get();
}
MyModel.php
public function user() {
return $this->hasOne('App\Models\Customer','customer_id', 'customer_id');
}
returning result :
[{
"id":1,
"tournament_id":3,
"customer_id":2016827550,
"point":0,
"user":{
"id":1,
"customer_id":2016827550,
"nickname":"OMahoooo"
}
}]
as expected query returns user section as array but i expect result like this :
[{
"id":1,
"tournament_id":3,
"customer_id":2016827550,
"point":0,
"nickname":"OMahoooo"
}]
only get nickname section without user array. I hope you understand me.
Sorry for my english , have a nice day
You can load the data and remap it manually using map() method. I've just tested this code and it works perfectly with hasOne() relationship:
$users = TournamentUser::where('customer_id', $id)->with('user')->get();
$users->map(function ($i) {
$i->nickname = $i->user->nickname; // Set related nickname.
unset($i->user); // Delete user data from the collection.
return $i;
});
Alternatively, you could use an accessor, but in this case, you'll meet the N+1 problem.

Laravel how to get object after and before saving this?

I'm using Laravel 4.2
My model has a function like this:
ServiceLog::saved(function($servicelog) {
if($servicelog->date_created != $old_date_created) {
//do something here
}
});
I want to compare the value of the field date_created after and before saving $servicelog
How do I get the $old_date_created ?
You have the getDirty() and isDirty() methods according to the documentation. isDirty checks if given attribute has changed and getDirty returns the attributes, that have been changed. You also have getOriginal() method, which will return the previous value of given attribute (before the change).
What you can do is this:
ServiceLog::saving(function($model)
{
// Check if property has changed
if ($model->isDirty('date_created')) {
// Get the original value before the change
$oldDate = $model->getOriginal('date_created');
// Get current value for date_changed
$newDate = $model->date_created;
echo "The date_created changed from $oldDate to $newDate";
}
return true; //if false the model won't save!
});

Resources