saschati/yii2-value-object

本扩展可以帮助您将数据库字段作为对象进行处理,通过将标量值提炼成对象,并将对象转换为标量值。

安装: 132

依赖关系: 0

建议者: 0

安全: 0

星标: 3

关注者: 2

分支: 0

开放问题: 0

类型:yii2-extension

v2.1.0 2023-09-04 11:57 UTC

This package is auto-updated.

Last update: 2024-09-04 13:58:42 UTC


README

本扩展可以帮助您将数据库字段作为对象进行处理,从对象中的标量值选择它们,反之亦然。您还可以为类型json和数组创建扁平类型,并将如tinyint等标量数据转换为布尔值。

安装

安装此扩展的首选方式是通过 composer

运行以下命令

composer require saschati/yii2-value-object "*"

或添加以下内容到您的 composer.json 文件的 require 部分。

"saschati/yii2-value-object": "*"

使用方法

您只需将其中一个行为变体连接到您的 ActiveRecord 模型

use Saschati\ValueObject\Behaviors\ORMBehavior;
use Saschati\ValueObject\Types\ValueObjects\EmailType;
use Saschati\ValueObject\Types\ValueObjects\UuidType;
use Saschati\ValueObject\Types\Flats\BooleanType;
use Saschati\ValueObject\Types\Flats\TimestampType;
use Saschati\ValueObject\Helpers\TypeScope;
...

class User extends ActiveRecord
{
    private Name $name;

    ...
    public function behaviors(): array
    {
        return [
            'vo' => [
                'class' => ORMBehavior::class,
                'attributes' => [
                    'id' => UuidType::class,
                    'email' => EmailType::class,
                    'active' => 'boolean', // BooleanType::class
                    'created_at' => 'timestamp', // TimestampType::class,
                    'name' => [
                        'scope' => TypeScope::EMBEDDED,
                        'type' => Name::class,
                        'map' => [
                            'firstname' => 'firstname',
                            'lastname' => 'lastname',
                            'middlename' => 'middlename',
                        ],
                    ],
                ],
            ],
        ];
    }
    ...
}

属性映射是通过处理它们的手柄来完成的,有关可用手柄的文档是关于如何创建自定义

属性/虚拟属性/数据库属性

此外,映射还有几个用于定义属性的功能,例如

  • @property - "@" 总是指示我们交互的属性必须是一个属性,即我们映射到属性并从属性映射。
  • #buildOrVirtualProperty - "#" 这个前缀指向对象中不存在的属性,这是一个简单的中间构建,将简单地存储在单独的位置。
  • property - 这表示属性在实体类中可用,即如果您直接在类中声明这样的属性,库将与之交互。

示例

use Saschati\ValueObject\Helpers\TypeScope;
use Saschati\ValueObject\Behaviors\ORMBehavior;

...
'vo' => [
    'class' => ORMBehavior::class,
    'attributes' => [
        // Step: 1
        // Cast the attribute in the Value Object
        '@id' => Id::class,
        // Step: 2
        // Create a virtual property into which we transfer other properties.
        '#build' => [
            'scope' => TypeScope::EMBEDDED,
            'type' => TypeOne::class,
            'map' => [
                'property1' => '@attribute1',
                'property2' => '@attribute2',
            ],
        ],
        // Step: 3
        // We create a class property in which the object with our virtual
        // property and the act record attribute will be embedded
        'propertyInClass' => [
            'scope' => TypeScope::EMBEDDED,
            'type' => TypeTwo::class,
            'map' => [
                // #build the virtual property from our step 2
                'property1' => '#build',
                'property2' => '@attribute3',
            ],
        ],
        // Step: 4
        'mapper' => [
            'scope' => TypeScope::MAPPER,
            'map' => [
                // We map the value from the attribute to the real property of the class,
                // now we can safely interact with this property, and the library itself
                // will transfer the value to the attribute when saved
                'id' => '@id',
            ],
        ],
    ],
],

此映射遵循以下原则

  • After Find - 从右到左,从上到下。
  • Before Save - 从左到右,从下到上。 Pipeline

嵌套属性

该库的一个特点是它可以处理嵌套属性,对象数组和对象数组。

示例

数据库中的 contact

{
    "phone": "0987654321",
    "email": "user@user.user",
    "address": {
      "country": "Ukraine",
      "region": "Khmelnytskyi",
      "city": "Khmelnytskyi",
      "street": "Institute",
      "house": "11/3"
    }
}

映射

use Saschati\ValueObject\Behaviors\ORMBehavior;
use Saschati\ValueObject\Types\Flats\JsonType;
use Saschati\ValueObject\Helpers\TypeScope;

class User extends ActiveRecord
{
    /**
     * @var Contact 
     */
    private Contact $contact;
    
    /**
     * @var string 
     */
    private string $region;

    ...
    public function behaviors(): array
    {
        return [
            'vo' => [
                'class' => ORMBehavior::class,
                'attributes' => [
                    // STEP: 1
                    // We convert the contact attribute into an array
                    '@contact' => JsonType::class,
                    '#address' => [
                        'scope' => TypeScope::EMBEDDED,
                        'type' => Address::class,
                        'map' => [
                            // STEP: 2
                            // Assign the nested address from the contact attribute
                            // to the properties of the Address class
                            'country' => '@contact.address.country',
                            'region' => '@contact.address.region',
                            'city' => '@contact.address.city',
                            'street' => '@contact.address.street',
                            'house' => '@contact.address.house',
                        ]
                    ],
                    'contact' => [
                        'scope' => TypeScope::EMBEDDED,
                        'type' => Contact::class,
                        'map' => [
                            // STEP: 3
                            // We put other keys in properties of the Contact class
                            'phone' => '@contact.phone',
                            'email' => '@contact.email',
                            'address' => '#address',
                        ]
                    ]
                    'mapper' => [
                        'scope' => TypeScope::MAPPER,
                        'map' => [
                            // STEP: 4
                            // We take the property from the build and assign it to the class property.
                            // ===================
                            // It is better not to do this:)
                            // Try to change the properties through one VO, and not spread it throughout the model
                            'region' => '#address.region',
                        ],
                    ],
                ],
            ],
        ];
    }
    ...
}

class Address
{    
    /**
     * @var string 
     */
    private string $country;
    
    /**
     * @var string 
     */
    private string $region;
    
    /**
     * @var string 
     */
    private string $city;
    
    /**
     * @var string 
     */
    private string $street;
    
    /**
     * @var string 
     */
    private string $house;
    
    ...
}

class Contact
{
    /**
     * @var string 
     */
    private string $phone;
    
    /**
     * @var string 
     */
    private string $email;
    
    /**
     * @var Address 
     */
    private Address $address;
    
    ...
}

如果您为可以独立处理这些属性的处理程序定义了这些属性,那么库本身将根据提取时接收到的位置存储数据。但目前在列表中有一系列处理程序不支持反向属性映射器,这是

对于这些处理程序,您需要定义一个解析器,该解析器接受一个扩展为 AbstractHandler 的处理程序,该处理程序可以进一步处理这些属性。

上面的实现示例

use Saschati\ValueObject\Types\Flats\JsonType;
use Saschati\ValueObject\Helpers\TypeScope;
use Saschati\ValueObject\Scope\Handlers\ConstructorHandler;
use Saschati\ValueObject\Scope\Handlers\YiiCreateHandler;

'vo' => [
    'class' => ORMBehavior::class,
    'attributes' => [
        ...
        '#address' => [
            'scope' => TypeScope::CONSTRUCTOR,
            'type' => Address::class,
            'params' => [
                '@contact.address.country',
                '@contact.address.region',
                '@contact.address.city',
                '@contact.address.street',
                '@contact.address.house',
            ],
            'resolver' => static function (Address $address, User $user, ConstructorHandler $handler): void {
                $handler->setAttribute($user, '@contact.address.country', $address->getCountry());
                $handler->setAttribute($user, '@contact.address.region', $address->getRegion());
                $handler->setAttribute($user, '@contact.address.city', $address->getCity());
                $handler->setAttribute($user, '@contact.address.street', $address->getStreet());
                $handler->setAttribute($user, '@contact.address.house', $address->getHouse());
            }
        ],
        ...
        'contact' => [
            'scope' => TypeScope::YII_CREATE,
            'type' => Contact::class,
            'params' => [
                'phone' => '@contact.phone',
                'email' => '@contact.email',
                'address' => '#address',
            ],
            'resolver' => static function (Contact $contact, User $user, YiiCreateHandler $handler): void {
                $handler->setAttribute($user, '@contact.phone', $contact->getPhone());
                $handler->setAttribute($user, '@contact.email', $contact->getEmail());
                $handler->setAttribute($user, '#address', $contact->getAddress());
            }
        ]
        ...
    ],
],

然后库本身将将其分解为键或属性,并找到需要更改的必要嵌套属性。