Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BaseActiveRecordTrait Date Mutators for dates columns #96

Open
mohavee opened this issue Sep 15, 2020 · 2 comments
Open

BaseActiveRecordTrait Date Mutators for dates columns #96

mohavee opened this issue Sep 15, 2020 · 2 comments
Labels
type:feature New feature

Comments

@mohavee
Copy link

mohavee commented Sep 15, 2020

Feature request to add the ability to convert dates columns to instances of DateTime inspired by [laravel].(https://laravel.com/docs/7.x/eloquent-mutators#date-mutators).

It would be useful to work with dates column like that:

class Order extends ActiveRecord
{
    use MutateDatesAttributes;

    protected $dates = [
        'is_send_scheduled', 'created_at', 'updated_at'
    ];
}

$order->is_send_scheduled = (new \DateTime)->add(new DateInterval('PT4H'))

I implemented in trait that logic on my own like that:

trait MutateDatesAttributes
{
    /**
     * The attributes that should be mutated to dates.
     *
     * @var array
     */
    protected $dates = [];

    /**
     * The storage format of the model's date columns.
     *
     * @var string
     */
    protected $dateFormat;


    /**
     * @param $name
     * @return DateTime
     * @throws \Exception
     */
    public function __get($name)
    {
        $value = parent::__get($name);

        // If the attribute is listed as a date, we will convert it to a DateTime
        // instance on retrieval, which makes it quite convenient to work with
        // date fields without having to create a mutator for each property.
        if ($this->isDateAttribute($name) &&
            ! is_null($value)) {
            return $this->asDateTime($value);
        }

        return $value;
    }

    /**
     * @param $name
     * @return string
     * @throws \Exception
     */
    public function getAttribute($name)
    {
        $value = parent::getAttribute($name);

        // If the attribute is listed as a date, we will convert it to a DateTime
        // instance on retrieval, which makes it quite convenient to work with
        // date fields without having to create a mutator for each property.
        if ($this->isDateAttribute($name) &&
            ! is_null($value)) {
            return $this->asDateTime($value);
        }

        return $value;
    }

    /**
     * Return a timestamp as DateTime object.
     *
     * @param mixed $value
     * @return DateTime
     * @throws \Exception
     */
    protected function asDateTime($value)
    {
        // If this value is already a DateTime instance, we shall just return it as is.
        // This prevents us having to re-instantiate a DateTime instance when we know
        // it already is one, which wouldn't be fulfilled by the DateTime check.
        if ($value instanceof DateTime) {
            return $value;
        }

        // If this value is an integer, we will assume it is a UNIX timestamp's value
        // and format a \DateTime object from this timestamp. This allows flexibility
        // when defining your date fields as they might be UNIX timestamps here.
        if (is_numeric($value)) {
            return (new DateTime)->setTimestamp($value);
        }

        // If the value is in simply year, month, day format, we will instantiate the
        // DateTime instances from that format. Again, this provides for simple date
        // fields on the database, while still supporting DateTime conversion.
        if ($this->isStandardDateFormat($value)) {
            return DateTime::createFromFormat('Y-m-d', $value)->setTime(0, 0);
        }

        // Finally, we will just assume this date is in the format used by default on
        // the database connection and use that format to create the DateTime object
        // that is returned back out to the developers after we convert it here.
        return DateTime::createFromFormat(
            str_replace('.v', '.u', $this->getDateFormat()), $value
        );
    }

    /**
     * Determine if the given value is a standard date format.
     *
     * @param  string  $value
     * @return bool
     */
    protected function isStandardDateFormat($value)
    {
        return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
    }

    /**
     * Get the format for database stored dates.
     *
     * @return string
     */
    public function getDateFormat()
    {
        /** @var Connection $db */
        $db = static::getDb();
        switch ($db->getDriverName()) {
            case 'mysql':
            default:
                return $this->dateFormat ?: 'Y-m-d H:i:s';
        }
    }

    /**
     * Determine if the given attribute is a date.
     *
     * @param  string  $name
     * @return bool
     */
    protected function isDateAttribute($name)
    {
        return in_array($name, $this->dates);
    }

    /**
     * @param $name
     * @param $value
     * @throws \Exception
     */
    public function __set($name, $value)
    {
        if ($this->isDateAttribute($name)) {
            $value = $this->fromDateTime($value);
        }

        return parent::__set($name, $value);
    }

    /**
     * @param $name
     * @param $value
     * @throws \Exception
     */
    public function setAttribute($name, $value)
    {
        if ($this->isDateAttribute($name)) {
            $value = $this->fromDateTime($value);
        }

        return parent::setAttribute($name, $value);
    }

    /**
     * Convert a DateTime to a storable string.
     *
     * @param mixed $value
     * @return string|null
     * @throws \Exception
     */
    public function fromDateTime($value)
    {
        return empty($value) ? $value : $this->asDateTime($value)->format(
            $this->getDateFormat()
        );
    }
}

If that feature is ok i would have create pr.

@samdark samdark added the type:feature New feature label Sep 19, 2020
@samdark
Copy link
Member

samdark commented Sep 19, 2020

Data mapper overall is a good idea.

@Tigrov
Copy link
Member

Tigrov commented May 3, 2024

Can be done inside db package by typecasting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:feature New feature
Projects
None yet
Development

No branches or pull requests

3 participants