joachim-n/mutable-typed-data

可变类型数据系统

1.2.0 2024-09-19 08:12 UTC

README

可变类型数据是一个类型化和结构化数据的系统。它基于Drupal的Typed Data API,主要区别在于数据结构可以基于设置的值动态更改。

免责声明

这个工作已经完成,有测试,但还需要更多润色。现在发布是因为我从五个月前的封锁开始就一直在做这个项目,现在我已经筋疲力尽了!

结构类型

每一份数据都由一个对象表示,该对象不仅包含数据,还包含其定义。

数据对象可以是简单的,只包含一个标量值

$simple_data->value = 42;
$simple_data->value = 'cake';
$simple_data->value = TRUE;

也可以是复杂的,具有子属性,这些子属性本身可以是简单的

$complex_data->alpha = 42;
$complex_data->beta = 'cake';
$complex_data->gamma = TRUE;

或也可以是复杂的

$complex_data->child->grandchild->alpha = 42;

简单或复杂的数据都可以是多个值

$multiple_simple_data[] = 'adding new value';
$multiple_simple_data[0] = 'changing existing value';

$multiple_complex_data[]->alpha = 'adding new value';
$multiple_complex_data[0]->beta = 'changing existing value';

复杂数据是可变的,这意味着它的子属性会根据控制属性的值而更改

$animal->type = 'mammal';
$animal->gestation_period = '12 months';
$animal->type = 'reptile';
// The gestation_period property is removed; other properties may now have been
added.

访问数据

简单数据始终通过'value'属性访问

$simple_data->value = 'cake';
print $simple_data->value;

复杂数据通过子属性名称访问,可以级联直到达到结构末端的简单值

$complex_data->alpha->value = 'cake';
print $complex_data->alpha->value;

$complex_data->also_complex->value = 'cake';
print $complex_data->also_complex->alpha->value;

作为简写,简单子值可以通过属性名直接设置

$complex_data->alpha = 'cake';

多个数据可以作为数值数组使用

$multiple_simple_data[0] = 'zero';
$multiple_simple_data[1] = 'one';
$multiple_simple_data[] = 'append';

注意,增量必须保持一致

$multiple_simple_data[0] = 'zero';
$multiple_simple_data[42] = 'delta too high'; // throws Exception

无论多个数据是简单还是复杂,访问数组都会产生一个数据对象

print $multiple_simple_data[0]->value;
print $multiple_complex_data[0]->alpha->value;

迭代

所有数据对象都可以迭代。

简单数据没有效果

foreach ($simple_data as $item) {
   // Nothing.
}

复杂数据迭代其子属性

foreach ($complex_data as $name => $item) {
   print $item->value;
}

多个数据迭代增量项

foreach ($multiple_data as $delta => $item) {
   print $item->value;
}

因为迭代简单项目没有效果,所以可以将数据项递归地迭代到数据结构中,这样做是安全的。

另外,数据对象上的items()方法允许与基数无关的迭代

foreach ($simple_data->items() as $item) {
   print $item->value; // Same as $simple_data->value
}

foreach ($multiple_simple_data->items() as $item) {
   print $item->value;
}

当处理可能为单个或多个的属性时,这很有用,因为它在两种情况下都给出了所有值。然而,不应递归使用,因为在迭代简单项目的值时将导致无限循环。

默认值

每个数据项都可以为其定义一个默认值。默认值可以有不同的用途

  • 当用户被提示输入数据时,向用户提出一个值
  • 当用户没有输入时提供值。

如果存在默认值,UI不应该强制用户输入必填值,因为MTB的验证会在数据为必填且为空时设置默认值。

默认值可以定义为

  • 一个字面值,如42或'cake'
  • 一个用Symfony表达式语言编写的表达式
  • 一个PHP可调用项,例如类方法或匿名函数。

表达式和可调用默认值可以使用其他数据项的值,因此具有依赖关系。

以下示例说明了如何在UI中使用默认值

  1. UI实例化了数据属性。默认值尚未应用。
  2. UI尝试获取数据的值。如果默认值的依赖得到满足,则应用它。
  3. 用户将看到空值或已应用的默认值。
  4. 用户提交数据。即使属性设置为必填,UI也应允许用户留空,因为默认值可以提供值。
  5. 数据消费者获取值。如果它仍然为空,则应用默认值。

表达式语言和JavaScript

MTD通过自定义函数扩展了Symfony表达式语言,以获取带有地址的数据值。可以通过继承\MutableTypedData\DataItemFactory添加更多自定义函数。

使用表达式特别适合在Web UI中展示的数据,因为可以将表达式语言的小部分解析为JavaScript,从而根据用户输入的其他值动态显示默认值。参见Drupal的Module Builder示例了解如何实现这一点。

数据定义

数据通过流畅的接口进行定义

$definition = \MutableTypedData\Definition\DataDefinition::create('string')
   ->setLabel('Label')
   ->setRequired(TRUE);

复杂数据通过嵌套属性进行定义

$definition = \MutableTypedData\Definition\DataDefinition::create('complex')
   ->setLabel('Label')
   ->setProperties([
      'child_property_alpha' => \MutableTypedData\Definition\DataDefinition::create('string')
         ->setLabel('Alpha')
         ->setRequired(TRUE),
      'child_property_beta' => \MutableTypedData\Definition\DataDefinition::create('string')
         ->setLabel('Beta'),
   ]);

选项

选项可以用数组或对象定义,这允许选项具有描述、权重以及标签

$definition = \MutableTypedData\Definition\DataDefinition::create('string')
   ->setLabel('Label')
   ->setOptionsArray([
      'green' => 'Emerald',
      'red' => 'Magenta',
      'grey' => 'Grey',
   ]);

$definition = \MutableTypedData\Definition\DataDefinition::create('string')
   ->setLabel('Label')
   ->setOptions(
      \MutableTypedData\Definition\OptionDefinition::create('green', 'Emerald', 'A lovely shade of green'),
      \MutableTypedData\Definition\OptionDefinition::create('red', 'Magenta', 'A deep red'),
      \MutableTypedData\Definition\OptionDefinition::create('grey', 'Grey', 'Not very colourful but shows at the top', -10)
   );

较高的权重“下沉”到选项列表的底部;较低的权重较轻,会“上升”。

可变数据

可变数据需要一个属性来控制变体,并为每个变体提供一个定义

$definition = \MutableTypedData\Definition\DataDefinition::create('mutable')
   ->setLabel('Label')
   ->setProperties([
   'type' => DataDefinition::create('string')
      ->setLabel('Type')
   ])
   ->setVariants([
    'alpha' => VariantDefinition::create()
      ->setLabel('Alpha')
      ->setProperties([
        'alpha_one' => DataDefinition::create('string')
        ->setLabel('A1'),
        'alpha_two' => DataDefinition::create('string')
        ->setLabel('A2'),
      ]),
    'beta' => VariantDefinition::create()
      ->setLabel('Beta')
      ->setProperties([
        'beta_one' => DataDefinition::create('string')
        ->setLabel('B1'),
        'beta_two' => DataDefinition::create('string')
        ->setLabel('B2'),
      ]),
   ]);

变体自动定义了类型属性的选项。

如果类型属性的值与变体名称不匹配(通常是因为类型属性的多个选项值对应同一个变体),则使用setOptions()定义选项并使用setVariantMapping()。