Mutate all dates on getAttributeValue automatically - php

I'm trying to create a sort of global mutator for any date string that is retreived from the database.
Now this is probably the wrong way of going about it (and doesn't even work), but I created a class called App\Http\Mutators\ModelMutator which extends Eloquent\Model.
I then changed the Eloquent alias in app.php to point to this new class.
Here is the code for the new class:
<?php
namespace App\Http\Mutators;
use Illuminate\Database\Eloquent\Model;
class ModelMutator extends Model
{
function getAttributeValue($key)
{
$value = parent::getAttributeValue($key);
if (strtotime($value)) {
$value = tolocal($value);
}
return $value;
}
}
This isn't working at all. In fact it appears it isn't even getting called whenever something is retrieved from the database.
I just want to have any date string retrieved from the database to apply the tolocal function.
My biggest issue is I am using packages outside of the App\ namespace and do not want to have to write mutators and modify every single package extends Model class.

Eloquent already has this functionality. Use the $dates property:
protected $dates = [
'created_at',
'updated_at',
'deleted_at'
];
By default, Eloquent will convert the created_at and updated_at columns to instances of Carbon, which extends the PHP DateTime class to provide an assortment of helpful methods. You may customize which dates are automatically mutated, and even completely disable this mutation, by overriding the $dates property of your model
https://laravel.com/docs/5.4/eloquent-mutators#date-mutators

Related

Retrieve fillable fields in Laravel

In laravel 5.4, I'm able to retrieve fillable fields by using fillable index of model instance.
$model = new AnyClass();
dd($model['fillable']);
The above code prints all fillable fields of AnyClass. But the same code prints null on laravel 5.6. I know I can retrieve fillable fields using $model->getFillable(). My question is what is the reason / why it is not working in laravel 5.6 but works in 5.4?
From the upgrade guide here I believe this is the answer to the question:
Model Methods & Attribute Names
To prevent accessing a model's private properties when using array access, it's no longer possible to have a model method with the same name as an attribute or property. Doing so will cause exceptions to be thrown when accessing the model's attributes via array access ($user['name']) or the data_get helper function.
If you look at Laravel's source code you'll see the difference.
The Model class, which is extended by the application models, implements the ArrayAccess interface, which, among others, force the class to define the offsetGet method.
In Laravel 5.4 the offsetGet method looks like:
public function offsetGet($offset)
{
return $this->$offset;
}
which means that if you call $model['fillable'], you actually call $model->offsetGet('fillable') which actually returns the fillable property of the class.
I couldn't find the Laravel 5.6 tag but I'm pretty sure it is the same code as Laravel 5.5.45. In this version the offsetGet method was changed to:
public function offsetGet($offset)
{
return $this->getAttribute($offset);
}
which means that it actually returns the attribute if found or null otherwise.
In Laravel 7, I'm doing this by calling the getFillable method on a new instance of my model. Like so:
$model = new MyModel();
$fillable = $model->getFillable();
Late to the party but I don't like the concept of having to always instance a Model, specially if you're using Eloquent serialization.
Let's say you wanted to build some filters, but wanted to whitelist the columns based on the model's fillable. You don't want to instance an entire model, so you can instead use reflection:
(new ReflectionClass(MyModel::class))->getDefaultProperties()['fillable']
See it working over at 3v4l.org - Here I demonstrate why you potentially wouldn't want to instance this model due to having serialization and always eager loading.
Change the property in the class to public $fillable = [ instead of protected $fillable = [

Laravel on some kind of Model Ready method

Well i don't know how to format the title of this post in very clear way, but here's my question:
Say i have
Posts::find('1);
Photos:find('1');
... and so on, every mode db request
now by default i can access db columns, for instance the id: through model->id
$Photos = Photos::find('1')->first();
echo $Photos->id; // will return 1
what i want is that i need all those kind of requests to add a custom field automatically like hashed_id, which is not in the database, which in return will make all models have a hashed_id as well, i know i can add that field to database and then grab it but i need it for different reasons/implementations
i did create a BaseModel and every Model will extend that BaseModel, so Photos extends BaseModel, BaseModel extends Model... and all that etc etc.
but i need some kind of constructor, upon retrieving data to process the data automatically without having to add -let's say- a hash_id() after retrieving the data.
something like, onAfterGet(), onReady()....sort of commands.
i hope my question is clear.
Thanks.
What you're looking for is an Accessor. Accesors can be used to add custom attributes to the model. Combine this with the $appends property and you have exactly what you need. The $appends property adds the custom accessor in every result.
You can do this by creating a base model like you've stated in the question or by using traits. I'll show you an example on how to achieve this using a base model.
Let's create base model called BaseModel. All other models that need this custom attribute will extend this.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
protected $appends = ['hashed_id'];
public function getHashedIdAttribute()
{
return some_hash_function($this->id);
}
}
We have a Image model which extends our BaseModel.
<?php
namespace App;
class Image extends BaseModel
{
}
Now every result from the Image model will have the hashed_id field added by default.
Accesor documenation https://laravel.com/docs/5.4/eloquent-mutators#defining-an-accessor
If I understand you right, all you need to do is to define mutator, for example:
<?php
class Photo extends Model
{
/* ... model implementation ... */
public function getHashedIdAttribute()
{
return md5($this->id);
}
}
Then you can access property like it was in database:
echo Photo::find(5)->hashed_id;

Laravel Dynamic Fillable in Models

Got stuck in a issue with laravel 5.2.
Following is the error during eloquent create operation(post call),
Mass Assignment Exception in Model.php 453: column_name
Following are the prerequisites, which are to be taken into consideration:
Fillables in model are filled in a dynamic manner by the following code:
public function __construct() {
$this->fillable(\Schema::getColumnListing($this->getTable()))
}
Following are the methods which are debugged till now:
Before insertion, in controller, $model::getillableField(), gives proper fillable array.
In model.php line(450),
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
}
the above code returns the value as "false" and $model::getFillableField() has the column_name in the array list.
Hardcoding $fillable variable with columns of table removes the error.
Please Help, where i am going wrong and what is the solution for it?
Thanks in advance.
What you are really trying to do is make ALL fields fillable.
The correct way to do this in Laravel is this:
protected $guarded = [];
This works in 5.2, even though the documentation for it is found in 5.3.
(relevant source code for 5.2)
(Documentation from 5.3):
If you would like to make all attributes mass assignable, you may define the $guarded property as an empty array:
By setting $guarded to an empty array, you are creating an empty black list, allowing all fields to be mass assignable.
Also, if this model is ever going to be constructed directly from user input, please do not do this. Laravel requires either $fillable or $guarded to be defined for a reason. Unless your model has fields that are literally 1:1 with a public form, then allowing all fields to be writable on mass assignment is a security vulnerability.
Try this.
Put the below code in your model,
public function __construct()
{
$this->setFillable();
}
public function setFillable()
{
$fields = \Schema::getColumnListing('table_name_here');
$this->fillable[] = $fields;
}
This makes each and every column is fillable from that table.
Create a trait that uses the database columns.
<?php
namespace App\Traits;
use Illuminate\Support\Facades\Schema;
trait ColumnFillable
{
public function getFillable()
{
return Schema::getColumnListing($this->getTable());
}
}
Now use this trait in your models.
<?php
namespace App;
use App\Traits\ColumnFillable;
class MyModel extends Model
{
use ColumnFillable;
...
Now you'll never have to manually specify $fillable.

Always modify returned values from laravel model

So I have a model, Post that has no methods defined within it.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use DB;
class Post extends Model
{
}
From a controller, I make calls to that model like:
return view('pages.post', ['post' => Post::where('url_route', '=', $url_route)->first()]
This works fine, but I now want to format the date column that is returned from that request, every time that model is called. Is there a way to modify the returned array without defining a new method?
I am new to Laravel to thanks for the help. Just trying to figure out the most efficient way of doing things within the framework...
If your model has $timestamps set to true, the created_at and updated_at fields are natively a Carbon instance.
This means you can format the date in the view like this as a basic example:
$post->updated_at->format('Y-m-d H:i:s')
Carbon instances allow you to leverage its extensive api as you can see at http://carbon.nesbot.com
If you would like to do the same for another field other than created_at and updated_at, you can add an extra property in your model:
protected $dates = ['added_on']
The fields you specify in the array will be treated as Carbon instances.

Store dates using Eloquent/Laravel

What's a good way of storing a date in a Eloquent Model? I'm using PHP with Laravel framework.
class MyModel extends Eloquent {
// I want a date field here... should it be:
public $endTime = ?;
}
Should I use a integer timestamp which gets saved as an int? What's the behaviour if I use a Date object in a atributte and save that object?
If you are using migration then use $table->timestamp('endTime'), this will create a timestamp field by using endTime as it's name.
By default, Eloquent will convert the created_at, updated_at, and
deleted_at columns to instances of Carbon, which provides an
assortment of helpful methods, and extends the native PHP DateTime
class. You may customize which fields are automatically mutated, and
even completely disable this mutation, by overriding the getDates
method of the model.
Add this method in your model:
public function getDates()
{
return array('created_at', 'updated_at', 'deleted_at', 'endTime');
}
Laravel will take care of these fields and by default each of these fields will be an instance of Carbon object. Read about date mutators. In this case all methods of Carbon could be used on these fields.

Categories