saschati / yii2-value-object
本扩展可以帮助您将数据库字段作为对象进行处理,通过将标量值提炼成对象,并将对象转换为标量值。
v2.1.0
2023-09-04 11:57 UTC
Requires
- php: ^8.1
- marc-mabe/php-enum: ^4.7.0
- ramsey/collection: ^1.3
- ramsey/uuid: ^4.0
- webmozart/assert: ^1.8.0
- yiisoft/yii2: ^2.0.0
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', ], ], ], ], ]; } ... }
属性映射是通过处理它们的手柄来完成的,有关可用手柄的文档是关于如何创建自定义
- 值对象数据类型,使用 ValueObjectHandler
- 扁平数据类型,使用 FlatTypeHandler
- 嵌入数据类型,使用 EmbeddedHandler
- 映射属性,使用 MapperHandler
- 通过构造函数创建实例,使用 ConstructorHandler
- 通过 Yii::create 创建实例,使用 YiiCreateHandler
- 每个项,使用 EachHandler
- 自定义处理程序,使用 AbstractHandler 或 HandlerInterface
属性/虚拟属性/数据库属性
此外,映射还有几个用于定义属性的功能,例如
@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', ], ], ], ],
此映射遵循以下原则
嵌套属性
该库的一个特点是它可以处理嵌套属性,对象数组和对象数组。
示例
数据库中的 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()); } ] ... ], ],
然后库本身将将其分解为键或属性,并找到需要更改的必要嵌套属性。