Search code examples
laravelgettermutators

How to create a universal getter/mutator for datetimes in Laravel?


I have created one and I thought it works:

<?php

namespace App\Traits;

use Carbon\Carbon;

trait FormatDates
{

    public function setAttribute($key, $value)
    {
        parent::setAttribute($key, $value);

        if (strtotime($value))
            $this->attributes[$key] = Carbon::parse($value);
    }
}

But there is a problem when calling related models. For example if you have an Article and Tag model and you want to get all tags like this:

$article->tags

it returns null because of that getter mutator.

How to fix this?


update 17.11.2017

I have found a solution to my problem. The best way to present the date in locale is to use this function:

\Carbon\Carbon::setToStringFormat("d.m.Y H:i");

simply create a service provider or a middleware and it will show all $dates in format you want. There is no need to make a getter.


Solution

  • Based from this: https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Concerns/HasAttributes.html#method_getAttribute

    The description says:

    Get a plain attribute (not a relationship).

    Luckily there are another two methods below it called getRelationValue and getRelationshipFromMethod, and it reads:

    Get a relationship.

    Get a relationship value from a method.

    respectively.

    And in your example, it looks like you're calling a relation.

    I think you should consider it when doing your universal getter/mutator.

    UPDATE:

    If you inspect the code, the getAttribute also calls the getRelationValue method. But it is the last resort of the function; if the key is neither an attribute or has a mutator or is a method of the class.

    Here is the stub: https://github.com/laravel/framework/blob/5.5/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L302

    /**
     * Get an attribute from the model.
     *
     * @param  string  $key
     * @return mixed
     */
    public function getAttribute($key)
    {
        if (! $key) {
            return;
        }
        // If the attribute exists in the attribute array or has a "get" mutator we will
        // get the attribute's value. Otherwise, we will proceed as if the developers
        // are asking for a relationship's value. This covers both types of values.
        if (array_key_exists($key, $this->attributes) ||
            $this->hasGetMutator($key)) {
            return $this->getAttributeValue($key);
        }
        // Here we will determine if the model base class itself contains this given key
        // since we don't want to treat any of those methods as relationships because
        // they are all intended as helper methods and none of these are relations.
        if (method_exists(self::class, $key)) {
            return;
        }
        return $this->getRelationValue($key);
    }
    

    ANOTHER UPDATE

    Since you've changed your question:

    You can just put the attribute name to $casts or $dates array (in your Model) so Laravel will automatically transform it into a Carbon instance when accessing it, like this:

    class Article extends Model {
        ...
        protected $dates = ['some_date_attribute`];
    

    or with $casts

        ...
        protected $casts = ['some_date_attributes' => 'date'];