bluegrass / metadata
用于 Symfony2 应用程序的工具
This package is not auto-updated.
Last update: 2024-09-28 15:03:20 UTC
README
Bluegrass::Metadata 是一个针对 Doctrine2 的 Symfony2 Bundle,旨在管理具有动态属性的实体。
这些动态属性可以透明地与实体中的其他部分一起持久化。
动态属性的概念源于允许系统用户添加其工作领域实体附加信息的需要。
例如,根据用户对其系统的使用情况,用户可能需要在“国家”列表中添加一个额外的“州长”属性,而其他用户可能不需要或不希望添加其他属性。
因此,提出了一种机制,允许动态地向现有系统实体添加新属性,而不会改变已经定义的域结构。
Bluegrass::Metadata 提供了一个平台,以解决这些需要与 Doctrine2 简单集成的需求。
通用概念
MetadataBundle 提供了一个元数据模型,允许为现有实体定义动态属性。
元数据模型由以下组件组成
元数据管理模型
在一个实体中使用动态属性意味着与该实体关联的设置器/获取器不存在。因此,访问这些属性的机制与传统机制不同。
当希望域实体包含动态属性时,需要实现 IMetadataEntity 接口。
该接口声明了两个方法
IMetadataEntity::getMetadata(Metadata $metadata)
IMetadataEntity::setMetadata(Metadata $metadata, $value)
通过这两个方法,实体动态属性的用户可以分配或获取存储在这些属性中的值。
Metadata 类表示特定实体的动态属性。它不表示元数据的定义/声明,而是“是”特定属性。
因此,获取元数据的值仅意味着调用实例化实体(与 Metadata 实例(属性)一起工作)的 getMetadata 方法。
在实际中,以下是一个具体的例子
$gobernador = $pais->getMetadata($gobernadorMetadata); //Obtiene el valor del atributo dinámico "gobernador" de la instancia de la clase Pais.
$gobernador = new Gobernador('Nuevo gobernador'); //Instancia un nuevo gobernador.
$pais->setMetadata($gobernadorMetadata, $gobernador); //Asigna el nuevo gobernador como valor del atributo dinámico "gobernador" de la instancia de la clase Pais.
$em->persist($pais); //Se persiste la entidad junto con todos sus valores de atributos dinámicos asociados.
可以看出示例代码显示了如何从正确初始化的 Metadata 实例开始操作 IMetadataEntity。
动态属性的持久化
首先,为了使实体能够包含可持久化的动态属性,需要该实体实现 IMetadataEntity 接口。
以下是一个传统实现
class Pais : IMetadataEntity {
/**
* @ORM\OneToMany(targetEntity="PaisMetadataValue", mappedBy="pais", cascade={"ALL"}, indexBy="metadataName", orphanRemoval=true)
*/
private $metadataValues;
public function __construct() {
$this->metadataValues = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getMetadata(Metadata $metadata) {
return $metadata->getValue($this->metadataValues[$metadata->getName()]);
}
public function setMetadata(Metadata $metadata, $value) {
$metadataValue = $metadata->createValue($this, $value);
$this->metadataValues[$metadata->getName()] = $metadataValue;
}
}
请注意,这种实现假设存在一个名为 "PaisMetadataValue" 的实体。同样重要的是要注意,“metadataValues”根据 "metadataName"(ORM 注解中的 indexBy 指令)索引到一个关联数组中。
这种具有动态属性的传统实体定义定义了一个索引数组 "MetadataValue"。
"MetadataValue" 是动态属性值的容器。
元数据模型定义了接口 IMetadataValue,该接口声明了 getValue() 方法,作为整个元数据模型中值容器的同质化表示。
持久化与动态实体关联的动态属性值,意味着需要声明一个新的类,该类实现了IMetadataValue
接口。由于要持久化该容器中的信息,因此这个新类必须是Doctrine实体。
一个传统的IMetadataValue
实现如下
/**
* @ORM\Entity
*/
class PaisMetadataValue : IMetadataValue {
/** @ORM\Column(type="string") */
private $value;
/** @ORM\Column(type="string") */
private $metadataName;
/** @ORM\ManyToOne(targetEntity="Pais") */
private $pais;
public function __construct(Pais $pais, $metadataValue, $value) {
$this->pais = $pais;
$this->metadataValue = $metadataValue;
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
public function getPais()
{
return $this->pais;
}
public function getMetadataName()
{
return $this->metadataName;
}
}
可以看到,PaisMetadataValue
定义了一个包含对拥有这些值的相关实体的引用的值容器。
属性$value
被映射为字符串,假设任何要存储在容器中的值都可以被序列化为字符串。
请注意,还定义了一个属性$metadataName
,它可以识别所包含的值对应哪个动态属性。正因为如此,动态实体通过metadataName
索引其“MetadataValue”列表。
最后,我们需要定义一个能够实例化我们的“MetadataValue”的类。这个类将作为我们的“MetadataValue”的工厂,并需要实现IMetadataValueFactory
接口。
class PaisMetadataValueFactory : IMetadataValueFactory {
public function createMetadataValue(IMetadataEntity $entity, $metadataName, $value) {
return new PaisMetadataValue($entity, $metadataValue, $value);
}
}
通过定义动态实体(Pais)及其动态属性值容器(PaisMetadataValue),我们已经具备了将动态属性关联到Pais并持久化的条件。
以下是一个简单的示例,展示了如何使用“string”类型的动态属性。
$paisMetadataValueFactory = new PaisMetadataValueFactory();
$pais = new Pais();
$metadata = new Metadata("reseña_historica", new RawMetadataValueProvider($paisMetadataValueFactory));
$pais->setMetadata($metadata, "Constituido en 1679");
$metadata = new Metadata("codigo_iso", new RawMetadataValueProvider($paisMetadataValueFactory));
$pais->setMetadata($metadata, "AR");
$em->persist($pais);
$pais = $paisesRepository->find(1);
$metadata = new Metadata("reseña_historica", new RawMetadataValueProvider($paisMetadataValueFactory));
$resena_historica = $pais->getMetadata($metadata);
$metadata = new Metadata("codigo_iso", new RawMetadataValueProvider($paisMetadataValueFactory));
$codigo_iso = $pais->getMetadata($metadata);
可以看到,在这种情况下,由于正在持久化类型为“string”的动态属性(原始值),使用了RawMetadataValueProvider
的实例,这是一个IMetadataValueProvider
的实现。
IMetadataValueProvider
的实现是一个负责解析存储在“MetadataValue”中的值的类。
对于字符串,通常不需要进行任何类型的处理,因为值会以原始形式持久化到数据库中。然而,如果要存储的值需要进行某种类型的处理(例如,序列化),那么“MetadataValueProvider”将负责执行该操作。
实际上,“MetadataValueProvider”确定了与动态属性关联的数据类型。
因此,实例化“Metadata”需要了解两个关于动态属性的要素
- 属性名($name)。
- 属性类型($metadataValueProvider)
在下文中将展示如何定义模板或元数据结构,以指定动态实体可以处理哪些属性。
元数据声明模型
在上文中展示了如何在运行时给实体分配新属性。这些属性可以通过Doctrine持久化,其值可以在以后获取。
然而,在大多数情况下,需要预先定义动态结构,使其成为半动态的。也就是说,系统用户预先定义每个动态实体的动态属性(及其数据类型),然后使用它们,而无需定义新的ORM映射或修改现有的静态类。
以这种方式定义结构,可以方便地获取特定的“Metadata”,因为除了“Metadata”的名称(动态属性的名称)外,无需知道其他任何信息即可在动态实体上获取/修改其值。
MetadataBundle为此定义了一系列可持久化的实体,旨在表示与领域实体关联的动态属性的特征。
定义元数据结构最重要的两个实体是
AttributeMetadata
:定义动态属性的特征。EntityTableMetadata
:是AttributeMetadata
的聚合器,与系统中的可持久化实体相关联,并代表其动态部分。
到目前为止,我们了解到实例化一个 Metadata
并非易事。 Metadata
的构造函数需要两个参数
IMetadataValueProvider $provider
:元数据值容器的提供者。$name
:元数据标识符。
Metadata
的一个实例代表了一个特定实体实例中动态属性相关联的“管理器”。
例如,如果想让“国家”实体有一个动态属性“州长”,那么我们需要处理名为“州长”的 Metadata
。
然而,仅仅知道我们想要与之工作的动态属性名称还不够,我们还需要指定该属性将包含哪种类型的数据以及如何访问这些值。
IMetadataValueProvider
的实现负责了解获取或生成元数据值的策略。它定义了两个方法
createValue( IMetadataEntity $entity, Metadata $metadata, $value )
getValue( IMetadataValue $metadataValue )
这两个方法都返回一个 IMetadataValue
,这是一个动态属性值的容器。
IMetadataValueProvider
最简单的实现是 RawMetadataValueProvider
- MetadataValue:表示存储在动态属性中的值的容器。为了获取包含的值或使用一个值来构建容器,需要使用“Metadata”。
- MetadataValueProvider:每个“Metadata”都与一个“MetadataValueProvider”相关联。所有的“Metadata”都关联到一个“MetadataValueProvider”的特化,以便能够执行创建“MetadataValue”或返回“MetadataValue”中包含的值的操作。