hyperf/docs/en/db/mutators.md

15 KiB

Modifier

This document borrows heavily from LearnKu Many thanks to LearnKu for contributing to the PHP community.

Accessors and modifiers allow you to format model property values when you get or set certain property values on a model instance.

Accessors & Modifiers

Define an accessor

To define an accessor, you need to create a getFooAttribute method on the model, and the Foo field to be accessed needs to be named in "camel case". In this example, we will define an accessor for the first_name property. This accessor is automatically called when the model tries to get the first_name property:

<?php

namespace App;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * Get the user's name.
     *
     * @param  string  $value
     * @return string
     */
    public function getFirstNameAttribute($value)
    {
        return ucfirst($value);
    }
}

As you can see, the raw value of the field is passed into the accessor, allowing you to process it and return the result. To get the modified value, you can access the first_name property on the model instance:

$user = App\User::find(1);

$firstName = $user->first_name;

Of course, you can also pass an existing property value and use an accessor to return a new computed value:

namespace App;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * Get the user's name.
     *
     * @return string
     */
    public function getFullNameAttribute()
    {
        return "{$this->first_name} {$this->last_name}";
    }
}

Define a modifier

To define a modifier, define the setFooAttribute method on the model. The Foo fields to be accessed are named using "camel case". Let's define a modifier for the first_name property again. This modifier will be called automatically when we try to set the value of the first_name property on the schema:

<?php

namespace App;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * 设置用户的姓名.
     *
     * @param  string  $value
     * @return void
     */
    public function setFirstNameAttribute($value)
    {
        $this->attributes['first_name'] = strtolower($value);
    }
}

Modifiers take the value of an attribute that has already been set, allowing you to modify and set its value to the $attributes property inside the model. For example, if we try to set the value of the first_name property to Sally:

$user = App\User::find(1);

$user->first_name = 'Sally';

In this example, the setFirstNameAttribute method is called with the value Sally as a parameter. The modifier then applies the strtolower function and sets the result of the processing to the internal $attributes array.

date converter

By default, the model converts the created_at and updated_at fields to Carbon instances, which inherit the PHP native DateTime class and provide various useful methods. You can add other date properties by setting the model's $dates property:

<?php

namespace App;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * Properties that should be converted to date format.
     *
     * @var array
     */
    protected $dates = [
        'seen_at',
    ];
}

Tip: You can disable the default created_at and updated_at timestamps by setting the model's public $timestamps value to false.

When a field is in date format, you can set the value to a UNIX timestamp, a datetime (Y-m-d) string, or a DateTime / Carbon instance. The date value will be properly formatted and saved to your database:

As mentioned above, when the fetched property is contained in the $dates property, it is automatically converted to a Carbon instance, allowing you to use any Carbon method on the property:

$user = App\User::find(1);

return $user->deleted_at->getTimestamp();

Time format

Timestamps will all be formatted as Y-m-d H:i:s. If you need a custom timestamp format, set the $dateFormat property in the model. This property determines how the date property will be stored in the database, and the format when the model is serialized into an array or JSON:

<?php

namespace App;

use Hyperf\DbConnection\Model\Model;

class Flight extends Model
{
    /**
     * This property should be cast to the native type.
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

Attribute type conversion

The $casts property on the model provides a convenience method to cast properties to common data types. The $casts property should be an array whose keys are the names of the properties to be cast, and the values are the data types you wish to cast. The supported data types are: integer, real, float, double, decimal:<digits>, string, boolean, object, array, collection , date, datetime and timestamp. When converting to decimal type, you need to define the number of decimal places, such as: decimal:2.

As an example, let's convert the is_admin property stored in the database as an integer ( 0 or 1 ) to a boolean value:

<?php

namespace App;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'is_admin' => 'boolean',
    ];
}

Now when you access the is_admin property, although the value stored in the database is an integer type, the return value is always converted to a boolean type:

$user = App\User::find(1);

if ($user->is_admin) {
    //
}

Custom type conversion

Models have several common type conversions built into them. However, users occasionally need to convert data into custom types. Now, this requirement can be accomplished by defining a class that implements the CastsAttributes interface

Classes that implement this interface must define a get and set method in advance. The get method is responsible for converting the raw data obtained from the database into the corresponding type, while the set method converts the data into the corresponding database type for storing in the database. For example, let's reimplement the built-in json type conversion as a custom type conversion:

<?php

namespace App\Casts;

use Hyperf\Contract\CastsAttributes;

class Json implements CastsAttributes
{
    /**
     * Convert the extracted data
     */
    public function get($model, $key, $value, $attributes)
    {
        return json_decode($value, true);
    }

    /**
     * Convert to the value to be stored
     */
    public function set($model, $key, $value, $attributes)
    {
        return json_encode($value);
    }
}

Once a custom type cast is defined, it can be attached to a model property using its class name:

<?php

namespace App;

use App\Casts\Json;
use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * Properties that should be typecast
     *
     * @var array
     */
    protected $casts = [
        'options' => Json::class,
    ];
}

Value object type conversion

Not only can you convert data to native data types, but you can also convert data to objects. The two custom type conversions are defined in a very similar way. But the set method in the custom conversion class that converts the data to an object needs to return an array of key-value pairs, which are used to set the original, storable value into the corresponding model.

As an example, define a custom type conversion class to convert multiple model property values into a single Address value object, assuming that the Address object has two public properties lineOne and lineTwo:

<?php

namespace App\Casts;

use App\Address;
use Hyperf\Contract\CastsAttributes;

class AddressCaster implements CastsAttributes
{
    /**
     * Convert the extracted data
     */
    public function get($model, $key, $value, $attributes): Address
    {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two']
        );
    }

    /**
     * Convert to the value to be stored
     */
    public function set($model, $key, $value, $attributes)
    {
        return [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ];
    }
}

After the value object type conversion, any data changes to the value object will be automatically synced back to the model before the model is saved:

<?php
$user = App\User::find(1);

$user->address->lineOne = 'Updated Address Value';
$user->address->lineTwo = '#10000';

$user->save();

var_dump($user->getAttributes());
//[
//    'address_line_one' => 'Updated Address Value',
//    'address_line_two' => '#10000'
//];

The implementation here is different from Laravel, if the following usage occurs, please pay special attention

$user = App\User::find(1);

var_dump($user->getAttributes());
//[
//    'address_line_one' => 'Address Value',
//    'address_line_two' => '#10000'
//];

$user->address->lineOne = 'Updated Address Value';
$user->address->lineTwo = '#20000';

// After directly modifying the field of address, it cannot take effect in attributes immediately, but you can get the modified data directly through $user->address.
var_dump($user->getAttributes());
//[
//    'address_line_one' => 'Address Value',
//    'address_line_two' => '#10000'
//];

// When we save the data or delete the data, the attributes will be changed to the modified data.
$user->save();
var_dump($user->getAttributes());
//[
//    'address_line_one' => 'Updated Address Value',
//    'address_line_two' => '#20000'
//];

If after modifying address, you don't want to save it or get the data of address_line_one through address->lineOne, you can also use the following method

$user = App\User::find(1);
$user->address->lineOne = 'Updated Address Value';
$user->syncAttributes();
var_dump($user->getAttributes());

Of course, if you still need to modify the function of attributes synchronously after modifying the corresponding value, you can try the following methods. First, we implement a UserInfo and inherit CastsValue.

namespace App\Caster;

use Hyperf\Database\Model\CastsValue;

/**
 * @property string $name
 * @property int $gender
 */
class UserInfo extends CastsValue
{
}

Then implement the corresponding UserInfoCaster

<?php

declare(strict_types=1);

namespace App\Caster;

use Hyperf\Contract\CastsAttributes;
use Hyperf\Collection\Arr;

class UserInfoCaster implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): UserInfo
    {
        return new UserInfo($model, Arr::only($attributes, ['name', 'gender']));
    }

    public function set($model, string $key, $value, array $attributes)
    {
        return [
            'name' => $value->name,
            'gender' => $value->gender,
        ];
    }
}

When we modify UserInfo in the following way, we can synchronize the data modified to attributes.

/** @var User $user */
$user = User::query()->find(100);
$user->userInfo->name = 'John1';
var_dump($user->getAttributes()); // ['name' => 'John1']

Inbound type conversion

Sometimes, you may only need to typecast property values written to the model without doing any processing on property values fetched from the model. A typical example of inbound type conversion is "hashing". Inbound type conversion classes need to implement the CastsInboundAttributes interface, and only need to implement the set method.

<?php

namespace App\Casts;

use Hyperf\Contract\CastsInboundAttributes;

class Hash implements CastsInboundAttributes
{
    /**
     * hash algorithm
     *
     * @var string
     */
    protected $algorithm;

    /**
     * Create a new instance of the typecast class
     */
    public function __construct($algorithm = 'md5')
    {
        $this->algorithm = $algorithm;
    }

    /**
     * Convert to the value to be stored
     */
    public function set($model, $key, $value, $attributes)
    {
        return hash($this->algorithm, $value);
    }
}

Type conversion parameters

When attaching a custom cast to a model, you can specify the cast parameter passed in. To pass in type conversion parameters, use : to separate the parameters from the class name, and use commas to separate multiple parameters. These parameters will be passed to the constructor of the type conversion class:

<?php
namespace App;

use App\Casts\Json;
use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * Properties that should be typecast
     *
     * @var array
     */
    protected $casts = [
        'secret' => Hash::class.':sha256',
    ];
}

Array & JSON conversion

array type conversions are very useful when you store serialized JSON data in the database. For example: if your database has a JSON or TEXT field type that is serialized to JSON, and you add an array type conversion to the model, it will be automatically converted to when you access it PHP array:

<?php

namespace App;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * Properties that should be typecast
     *
     * @var array
     */
    protected $casts = [
        'options' => 'array',
    ];
}

一Once the conversion is defined, it will be automatically deserialized from the JSON type to a PHP array when you access the options property. When you set the value of the options property, the given array is also automatically serialized to JSON type storage:

$user = App\User::find(1);

$options = $user->options;

$options['key'] = 'value';

$user->options = $options;

$user->save();

Date type conversion

When using the date or datetime attributes, you can specify the format of the date. This format will be used when models are serialized as arrays or JSON:

<?php

namespace App;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    /**
     * Properties that should be typecast
     *
     * @var array
     */
    protected $casts = [
         'created_at' => 'datetime:Y-m-d',
    ];
}

Query-time type conversion

There are times when you need to typecast specific properties during query execution, such as when you need to fetch data from a database table. As an example, consider the following query:

use App\Post;
use App\User;

$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
            ->whereColumn('user_id', 'users.id')
])->get();

In the result set obtained by this query, the last_posted_at attribute will be a string. It would be more convenient if we did a date type conversion when executing the query. You can do this by using the withCasts method:

$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
            ->whereColumn('user_id', 'users.id')
])->withCasts([
    'last_posted_at' => 'date'
])->get();