What is the best way to handle the datetime in Laravel?

Published 3 months ago by malhayek

I have models where I need to capture a datetime input from the user. Then I need to store it in the database, and then display it using the proper format.

I am currently handling the process of converting the customer input to a the correct for mat using a mutator. Also, I am using accessor to convert the format from database format to easier to read format.

This is my current code

    /**
     * Mutate the paid_at to a valid date time
     *
     * @param  string  $value
     * @return void
     */
    public function setPaidAtAttribute($value)
    {
        $this->attributes['paid_at'] = date('Y-m-d H:i:s', strtotime($value));
    }


    /**
     * Get the paid_at value.
     * @param  string  $value
     * @return string
     */
    public function getPaidAtAttribute($value)
    {
        return date('d/m/Y  H:i A', strtotime($value));
    }

What is the best way handle this process so I won't have to create a mutator and an accessor for each datetime field?

Best Answer (As Selected By malhayek)
malhayek

Thank you. What I ended up doing is creating a property that will auto manage this process for me. this is what I have done

in the config\app.php config file I added the following two lines.

    'fallback_in_format' => 'Y-m-d H:i:s',
    'fallback_out_format' => 'd/m/Y H:i A',

Then I created a new base class for my models like the following

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    protected $fixedFormattedFields = null;

    /**
     * Get a plain attribute (not a relationship).
     *
     * @param  string  $key
     * @return mixed
     */
    public function getAttributeValue($key)
    {
        if( ($field = $this->getFieldToFormat($key)) != null)
        {
            return date($field['out_format'], strtotime($this->getAttributeFromArray($key)));
        }

        return parent::getAttributeValue($key);
    }

    /**
     * Set a given attribute on the model.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return $this
     */
    public function setAttribute($key, $value)
    {
        if( ($field = $this->getFieldToFormat($key)) != null)
        {
            $this->attributes[$key] = date($field['in_format'], strtotime($value));

            return $this;
        }

        return parent::setAttribute($key, $value);
    }
    
    /**
     * Get the format property for a given attribute on the model.
     *
     * @param  string  $key
     * @return mix (null|array)
     */
    protected function getFieldToFormat($key)
    {
        foreach($this->getFixedFormattedFields() as $field)
        {
            if($field['key'] == $key)
            {
                return $field;
            }
        }

        return null;
    }

    /**
     * Gets a fields to auto format.
     *
     * @return array
     */
    protected function getFixedFormattedFields()
    {
        if( is_null($this->fixedFormattedFields))
        {
            $this->fixedFormattedFields = [];

            if( property_exists($this, 'formattedDates') && is_array($this->formattedDates))
            {
                foreach($this->formattedDates as $field)
                {
                    if(!is_array($field))
                    {
                        $this->fixedFormattedFields[] = $this->getFixedFormattedField($field);
                        continue;
                    }

                    if(isset($field['key']))
                    {
                        $inFormat = isset($field['in_format']) ? $field['in_format'] : null;
                        $outFormat = isset($field['out_format']) ? $field['out_format'] : null;

                        $this->fixedFormattedFields[] = $this->getFixedFormattedField($field['key'], $inFormat, $outFormat);
                    }
                }
            }
        }

        return $this->fixedFormattedFields;
    }

    /**
     * Get get a standard format for the fields to auto format.
     *
     * @param  string  $key
     * @param  string $inFormat
     * @param  string $outFormat
     * @return array
     */
    protected function getFixedFormattedField($key, $inFormat = null, $outFormat = null)
    {
        return 
        [   
            'key' => $key,
            'in_format' => is_null($inFormat) ? config('app.fallback_in_format') : $inFormat,
            'out_format' => is_null($outFormat) ? config('app.fallback_out_format') : $outFormat
        ];
    }
}

Then in my model I do the following

<?php

namespace App\Models;

use App\Models\BaseModel;

class Expense extends BaseModel
{
    /**
     * Datetime attributes that should be auto manages
     *
     * @var array
     */
    protected $formattedDates = [
                                    [
                                     'key'        => 'paid_at',
                                     'in_format'  => 'Y-m-d H:i:s',
                                     'out_format' => 'd/m/Y H:i A'
                                    ],
                                    'starts_at',
                                    'ends_at'
                                ];

}

As you can see, now all I need to do is define the fields in the $formattedDates property. I can also specify the a different format for each property if I wish.

I hope this helps someone out there.

jlrdw
jlrdw
3 months ago (156,620 XP)

You could have one getter / setter that would work for all datetime fields, use a custom helper / class not tied to a specific model.

malhayek

Are you saying that getter/setter is the best way to handle this?

How would I know the field type? How would create a global helper?

jlrdw
jlrdw
3 months ago (156,620 XP)

You said for datetime fields, right?

One to turn friendly, one to store to DB.

malhayek

Correct. If I understand you correctly, your saying to create a global getter and setter. this mean, I would need to hook into each model and for each field of the type datetime, apply a getter and a setter.

jlrdw
jlrdw
3 months ago (156,620 XP)
malhayek

@jlrdw that link explains how to create helper methods. Are you saying to create a a global function two function to handle the conversion but still create a getter and setter for each field in each model?

if (!function_exists('dateTimeToString')) 
{ 
    function dateTimeToString($value)
    {
        return date('d/m/Y  H:i A', strtotime($value));
    }

}

if (!function_exists('stringToDateTime')) 
{ 
    function stringToDateTime($value)
    {
        return date('Y-m-d H:i:s', strtotime($value));
    }
}

Than my model will look like this

    /**
     * Mutate the paid_at to a valid date time
     *
     * @param  string  $value
     * @return void
     */
    public function setPaidAtAttribute($value)
    {
        $this->attributes['paid_at'] = stringToDateTime($value);
    }


    /**
     * Get the paid_at value.
     * @param  string  $value
     * @return string
     */
    public function getPaidAtAttribute($value)
    {
        return dateTimeToString($value);
    }

jlrdw
jlrdw
3 months ago (156,620 XP)

You mentioned having various datetime fields to convert. Write one function somewhere to deal with it. Laravel is flexible enough to write custom classes, methods, etc. If you need better understanding of custom functions, Jeffrey has free intro php / pdo videos.

malhayek

@jlrdw I am trying to find a better way to handle the process of converting string to datetime and datetime to string on accessing or mutating the data without having to write 2 methods per field.

jlrdw
jlrdw
3 months ago (156,620 XP)

You said

What is the best way handle this process so I won't have to create a mutator and an accessor for each datetime field?

My answer, Write only one, that handles any datetime value you send to it, you do not understand what I mean, write a custom function.

If you need an example of custom function, let me know.

malhayek

I apologize, I am not following you. An example will be greatly appreciated.

jlrdw
jlrdw
3 months ago (156,620 XP)

Just example of a custom function: Here I don't want

0000-00-00

in my database for a date, if user does not enter a date I want null, so

$mydate = Cln::fixDate($_POST['mydate']);

And in the custom function

public static function fixDate($tonl)
    {
        $tonl = (is_null($tonl) || empty($tonl) || strlen($tonl) < 1 ? NULL : $tonl);
        return $tonl;
    }

Note, this example is not laravel, just example of returning a custom value, this case null. Note if a date is entered it just returns that. This is just from a custom helper class I use in another framework.

In your case, return the date formatted as you need. You need to practice custom functions.

malhayek

Thank you. What I ended up doing is creating a property that will auto manage this process for me. this is what I have done

in the config\app.php config file I added the following two lines.

    'fallback_in_format' => 'Y-m-d H:i:s',
    'fallback_out_format' => 'd/m/Y H:i A',

Then I created a new base class for my models like the following

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    protected $fixedFormattedFields = null;

    /**
     * Get a plain attribute (not a relationship).
     *
     * @param  string  $key
     * @return mixed
     */
    public function getAttributeValue($key)
    {
        if( ($field = $this->getFieldToFormat($key)) != null)
        {
            return date($field['out_format'], strtotime($this->getAttributeFromArray($key)));
        }

        return parent::getAttributeValue($key);
    }

    /**
     * Set a given attribute on the model.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return $this
     */
    public function setAttribute($key, $value)
    {
        if( ($field = $this->getFieldToFormat($key)) != null)
        {
            $this->attributes[$key] = date($field['in_format'], strtotime($value));

            return $this;
        }

        return parent::setAttribute($key, $value);
    }
    
    /**
     * Get the format property for a given attribute on the model.
     *
     * @param  string  $key
     * @return mix (null|array)
     */
    protected function getFieldToFormat($key)
    {
        foreach($this->getFixedFormattedFields() as $field)
        {
            if($field['key'] == $key)
            {
                return $field;
            }
        }

        return null;
    }

    /**
     * Gets a fields to auto format.
     *
     * @return array
     */
    protected function getFixedFormattedFields()
    {
        if( is_null($this->fixedFormattedFields))
        {
            $this->fixedFormattedFields = [];

            if( property_exists($this, 'formattedDates') && is_array($this->formattedDates))
            {
                foreach($this->formattedDates as $field)
                {
                    if(!is_array($field))
                    {
                        $this->fixedFormattedFields[] = $this->getFixedFormattedField($field);
                        continue;
                    }

                    if(isset($field['key']))
                    {
                        $inFormat = isset($field['in_format']) ? $field['in_format'] : null;
                        $outFormat = isset($field['out_format']) ? $field['out_format'] : null;

                        $this->fixedFormattedFields[] = $this->getFixedFormattedField($field['key'], $inFormat, $outFormat);
                    }
                }
            }
        }

        return $this->fixedFormattedFields;
    }

    /**
     * Get get a standard format for the fields to auto format.
     *
     * @param  string  $key
     * @param  string $inFormat
     * @param  string $outFormat
     * @return array
     */
    protected function getFixedFormattedField($key, $inFormat = null, $outFormat = null)
    {
        return 
        [   
            'key' => $key,
            'in_format' => is_null($inFormat) ? config('app.fallback_in_format') : $inFormat,
            'out_format' => is_null($outFormat) ? config('app.fallback_out_format') : $outFormat
        ];
    }
}

Then in my model I do the following

<?php

namespace App\Models;

use App\Models\BaseModel;

class Expense extends BaseModel
{
    /**
     * Datetime attributes that should be auto manages
     *
     * @var array
     */
    protected $formattedDates = [
                                    [
                                     'key'        => 'paid_at',
                                     'in_format'  => 'Y-m-d H:i:s',
                                     'out_format' => 'd/m/Y H:i A'
                                    ],
                                    'starts_at',
                                    'ends_at'
                                ];

}

As you can see, now all I need to do is define the fields in the $formattedDates property. I can also specify the a different format for each property if I wish.

I hope this helps someone out there.

Sign In or create a forum account to participate in this discussion.