Laravel: override eloquent function for get results? - laravel

I can override function before save :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
But which method I can use for get result ? and where is documentation for this. I am looking for something like :
public function get()
{
parent::get();
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
}
}
With that I can convert 10 date rows without need of 20 mutators..

It seems that Attribute casting fits your needs or use Date mutators
You may customize which fields are automatically mutated, and even completely disable this mutation, by overriding the $dates property of your model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
// more dates
];
}
EDIT
Another way, you can override getAttribute method in Model
<?php
namespace App;
use Carbon\Carbon;
trait DateFormatting
{
protected function dateFields()
{
return [];
}
public function getAttribute($key)
{
if ( array_key_exists( $key, $this->dateFields() ) ) {
return Carbon::createFromFormat('d/m/Y', $this->attributes[$key])->format('Y-m-d');
}
return parent::getAttribute($key);
}
}
then you can use this trait in any your model, just don't forget override dateFields in it
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\DateFormatting;
class User extends Model
{
use DateFormatting;
protected function dateFields()
{
return [
'finished_at',
// other field names that you need to format
];
}
after all you can access to this fields as usual(using magic __get())
$model->finished_at;

I find a solution, My solution is :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = \Carbon\Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
public function getAttribute($key)
{
$value = parent::getAttribute($key);
if(isset($this->attributes[$key])){
if(isset($this->datesConvert) && in_array($key, $this->datesConvert)){
$value = \Carbon\Carbon::createFromFormat('Y-m-d', $value)->format('d/m/Y');
}
}
return $value;
}

Laravel comes with something very useful for this problem. I'm not sure what it's called, but you can modify attributes or even add new attributes like this:
class YourModel extends Model
{
...
public function getDateAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
...
}
You can retrieve this attribute like:
$yourModel->date;
Edit after comment #fico7489
You can't ignore the fact you always have to modify things. However there are still some solutions to make it easier.
For example you can change your date column to a string and just store your date with the desired date format.
Other solution should be modifying the date through sql. FORMAT(Now(),'YYYY-MM-DD').
Example in laravel would look like (not tested):
YourModel::select([
'*',
DB::raw('
FORMAT(yourDateColumn,'YYYY-MM-DD')
')
])->get();

Related

Custom attribute crashing on toJSON

I am trying to determine which position the order is in to generate a order id, but this crashes laravel, nothing in the logs, just a 500 error in the browser:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Load extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $guarded = ['id', 'created_at', 'updated_at'];
protected $appends = ['order_no'];
public function workorder()
{
return $this->belongsTo('App\WorkOrder', 'work_order_id');
}
public function getOrderNoAttribute()
{
$count = 1;
foreach ($this->workorder->loads as $load) {
if ($load->id == $this->id) {
break;
}
$count++;
}
return $this->workorder->id . "-" . $count;
}
}
When I changed it to return just an integer it worked, so I am almost certain it is the relation access causing the issue. Is there a way to do this that is better?
Generally while defining calculated attributes, dependence on relationship should be avoided. So one way to achieve what you are trying is (as you mentioned solved) is to get all loads having the same work_orderid and proceed with it.
public function getLoadCountAttribute ()
{
$id = $this->work_order_id;
return static::where('work_order_id', $id)->count();
}
Another way, more logical I guess, would be to define a relationship and eager load
//define a relation in your Load model
public function load_count ()
{
return count($this->workorder->loads)
//-1 if you want to exclude the current load from count
;
}
//Then use Load::with('load_count') to eager load the load_count
//You may also use global scope
Yet another way would be to define a static function on Workorder model, which will accept an id and return the load_count
//Workorder model
public static function getLoadCount($id)
{
$workorder = static::findOrFail($id);
return count($workorder->loads);
}
Hope this helps.

Laravel - how to makeVisible an attribute in a Laravel relation?

I use in my model code to get a relation
class User extends Authenticatable
{
// ...
public function extensions()
{
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
// ...
}
The Extension has field password hidden.
class Extension extends Model
{
// ...
protected $hidden = [
'password',
];
// ...
}
Under some circumstances I want to makeVisible the password field.
How can I achieve this?
->makeVisible([...]) should work:
$model = \Model::first();
$model->makeVisible(['password']);
$models = \Model::get();
$models = $models->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many
$related = $parent->relation->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many - with loading
$related = $parent->relation()->get()->each(function ($i, $k) {
$i->makeVisible(['password']);
});
Well, I got the idea from https://stackoverflow.com/a/38297876/518704
Since my relation model Extension::class is called by name in my code return $this->belongsToMany(Extension::class,... I cannot even pass parameter to it's constructor.
So to pass something to the constructor I may use static class variables.
So in my Extension model I add static variables and run makeVisible method.
Later I destruct the variables to be sure next calls and instances use default model settings.
I moved this to a trait, but here I show at my model example.
class Extension extends Model
{
public static $staticMakeVisible;
public function __construct($attributes = array())
{
parent::__construct($attributes);
if (isset(self::$staticMakeVisible)){
$this->makeVisible(self::$staticMakeVisible);
}
}
.....
public function __destruct()
{
self::$staticMakeVisible = null;
}
}
And in my relation I use something like this
class User extends Authenticatable
{
...
public function extensions()
{
$class = Extension::class;
$class::$staticMakeVisible = ['password'];
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
...
}
The highest voted answer didn't seem to work for me (the relations attribute seems to be a protected array now so can't be used as a collection in #DevK's answer), I instead used:
$parent->setRelation('child', $parent->child->first()->setVisible(['id']));

Reusing an accessors & mutators

In several of my models I have code like this
public function setTotalAttribute($value)
{
return $this->attributes['total'] = $value * 100;
}
public function getTotalAttribute($value)
{
return $value * 0.01;
}
Sometimes the field that I am mutating is called purchase or price, but the code is the same (changing 7.99 to 799 to store in the DB, and change it back on return).
If all the fields were named the same I could use a trait, however they are slightly different.... is there a way I can setup something similar to the date fields which auto-mutate to Carbon instances?
One solution is to define the fields that deal with dollars/cents conversion in the models that have such fields, and then use a trait to override the global mutators/accessors.
class Model
{
use HasMoneyFields;
protected $moneyFields = ['purchase', 'price', 'total'];
}
trait HasMoneyFields
{
public function getAttributeValue($key)
{
$value = parent::getAttributeValue($key);
if (property_exists($this, 'moneyFields')) {
if (in_array($key, $this->moneyFields)) {
$value /= 100;
}
}
return $value;
}
public function setAttribute($key, $value)
{
parent::setAttribute($key, $value);
if (property_exists($this, 'moneyFields')) {
if (in_array($key, $this->moneyFields)) {
$this->attributes[$key] = $value * 100;
}
}
}
}
You might be interested in https://github.com/topclaudy/eloquent-mutators
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use \Awobaz\Mutator\Mutable;
protected $accessors = [
'title' => 'trim_whitespace',
'content' => 'trim_whitespace',
];
}
The package allows you to create custom accessors/mutators extensions.

Laravel Mutators with Constructor?

Ill have a problem because my mutators never get called when ill use an constructor:
Like this:
function __construct() {
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // Never gets called
}
Ill already found out, that the mutators would ne be called when ill use an constructor, so i should use:
public function __construct(array $attributes = array()){
parent::__construct($attributes);
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // now its getting called
}
But so ill get the following error:
array_key_exists() expects parameter 2 to be array, null given
But i dont know where? Can anyone help me out how to create a default value (like a UUID) for a specific column, and use mutators in the same class?
Edit: Thanks Martin Bean for your help, but i am now getting the following error:
Cannot declare class App\Uuid because the name is already in use
I have tried:
Creating a File called "Uuid.php" in /app/ -> /app/Uuid.php
With this content:
<?php namespace App;
use Webpatser\Uuid\Uuid;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = Uuid::generate(4)->string();
});
}
}
Changed my Model to:
<?php namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Task extends Model {
use \App\Uuid;
Thank you very much!
Edit 2:
Ill tried it this way:
class Task extends Model {
protected $table = 'tasks';
protected $fillable = ['..... 'date', 'guid'];
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
TaskController:
public function store() {
$input = Request::all();
$input['guid'] = true;
Task::create($input);
return redirect('/');
}
Works fine, but when ill use:
public function setDateAttribute(){
$this->attributes['date'] = date('Y-m-d', $date);
}
In Task.php ill get:
Undefined variable: date
EDITED:
based on your comment:
i would like to set a field on first insert
use Uuid; //please reference the correct namespace to Uuid
class User extends Model{
protected $fillable = [
'first_name',
'email',
'guid' //add guid to list of your fillables
]
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
}
Later:
$user = User::create([
'guid' => true, //setAttribute will handle this
'first_name' => 'Digitlimit',
'email" => my#email.com
]);
dd($user->guid);
NB: Remove the __construct() method from your model
Mutators are called when you try and set a property on the model—they’re invoked via the __get magic method. If you manually assign a property in a method or constructor, then no mutators will ever be called.
Regardless, you should not be creating constructors on Eloquent model classes. This could interfere with how Eloquent models are “booted”.
If you need to set an UUID on a model then I’d suggest using a trait that has its own boot method:
namespace App;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = \Vendor\Uuid::generate(4)->string();
});
}
}
You apply the trait to your model…
class SomeModel extends Model
{
use \App\Uuid;
}
…and now each time a model is created, a UUID will be generated and stored in the database with your model.

How do you use setColumnAttribute with update()?

I'm trying to strip non-numeric characters on an attribute before saving, but the new value is not saved.
My model looks like this:
class Model extends \Eloquent {
public function setColumnAttribute($value) {
$value = (int)preg_replace('/[^0-9]/', '', $value);
return $value;
}
}
That method do run, if I add a dd($value) in it, that dumps.
I update the model with this:
Model::find($id)->update($attributes);
Why doesn't it save the value given from the setColumnAttribute method? What am I missing?
By searching on eloquent mutator I find out you had to insert it in the $this->attributes array instead of returning the value.
class Model extends \Eloquent {
public function setColumnAttribute($value) {
$value = (int)preg_replace('/[^0-9]/', '', $value);
$this->attributes['column'] = $value;
}
}

Resources