top-think / think-orm

Think ORM——the PHP Database&ORM Framework
Apache License 2.0
413 stars 171 forks source link

[特性] 提供模型字段类型转换的统一接口 #587

Closed NHZEX closed 1 month ago

NHZEX commented 1 month ago

重提这个请求:https://github.com/top-think/think-orm/pull/210

解决的痛点:

  1. 实例反序列化创建时不在是只能是通过__construct作为入口,那能使实例构建更加灵活。
  2. 目前社区内优秀的反序列化工具都是构建新类时注入值,不能对一个已经存在的实例进行赋值,所以更推荐静态方法实现序列和反序列化。
  3. 能友好复用当前各种DTO类,一般都实现了fromXXXtoXXX静态方法作为入口,当前__construct__toString的接入组合灵活性太差了。
  4. 类型转换时还能获得当前Model作为上下文参数,这个能实现更加底层的需求,比如更加字典状态在实例化DTO时处理一些历史遗留问题。当然这个是有风险了,只推荐访问getData,getOrigin这种无副作用的访问方式,不然容易造成死循环的情况。

提到的优秀反序列化工具库,基本都是提供类名重新实例化的。

NHZEX commented 1 month ago

实际用例

/**
 * 产品模型.
 *
 * @property int    $id
 * @property int    $status
 * @property string $name
 * @property int    $createdAt
 * @property int    $createdBy
 * @property int    $updatedAt
 * @property int    $updatedBy
 * @property int    $lockVersion
 * @property PrintParameters $printParams
 */
final class ProductModel extends ModelBaseV2
{
    protected $table = 'product';
    protected $pk = 'id';
    protected $convertNameToCamel = true;

    protected $type = [
        'print_params' => PrintParameters::class,
    ];
}

/**
 * DTO 类
 */
class PrintParameters extends DTOBase implements FieldTypeTransform
{
    public function __construct(
        public readonly int      $printXxxId,
        public readonly int      $printAbcId,
        public readonly Vector2d $printShift,
    ) {
    }

    public static function modelReadValue($value, $model): ?static
    {
        if (empty($value)) {
            return null;
        }
        $result = json_decode($value, true);
        if (empty($result)) {
            return null;
        }

        return static::fromData($result);
    }

    public static function modelWriteValue($value, $model): ?string
    {
        if (empty($value)) {
            return null;
        }

        return json_encode_ex($value);
    }
}

abstract class DTOBase implements JsonSerializable
{
    public function toArray(): array
    {
        return get_object_vars($this);
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }

    /**
     * @throws JsonException
     */
    public function __toString(): string
    {
        return json_encode($this, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR);
    }

    public static function fromData(array|string $arrOrJson): static
    {
        $source = \is_string($arrOrJson)
            ? Source::json($arrOrJson)
            : Source::array($arrOrJson);

        $cache = thinkApp::getInstance()->cache;
        $cache = new FileWatchingCache($cache);

        $builder = (new MapperBuilder())
            ->withCache($cache)
            ->supportDateFormats(\DATE_ATOM)
            ->allowPermissiveTypes()
            ->allowSuperfluousKeys()
            ->enableFlexibleCasting();

        return $builder->mapper()->map(static::class, $source);
    }
}

/**
 * 声明接口
 */
interface FieldTypeTransform
{
    /**
     * @param Model $model
     */
    public static function modelReadValue($value, $model);

    /**
     * @param Model $model
     */
    public static function modelWriteValue($value, $model);
}