drupal8 自定义实体

tech2022-09-03  109

drupal8 自定义实体

In the previous article of this series we’ve started our dive into the Entity Validation and Typed Data APIs. We’ve seen how DataType plugins interact with data definitions and how various constraints can be added to the latter at multiple levels and extension points.

在本系列的上一篇文章中,我们已开始深入研究实体验证和类型化数据API。 我们已经看到了DataType插件如何与数据定义交互以及如何在多个级别和扩展点向后者添加各种约束。

In this part, we will cover the aspect of actual validation and violation handling. In addition, we will write our own constraint and validator so that we can use custom behaviors in the data validation process.

在这一部分中,我们将介绍实际的验证和违规处理方面。 另外,我们将编写我们自己的约束和验证器,以便我们可以在数据验证过程中使用自定义行为。

验证和违规处理 (Validation and Violation Handling)

Even though we don’t yet know exactly how constraints are built, we’ve seen how they can be added to Typed Data definitions, including entity fields. Let us now see how we can validate the entities and handle possible violations we find.

即使我们还不完全了解约束的构建方式,我们仍然看到了如何将它们添加到Typed Data定义中,包括实体字段。 现在让我们看看如何验证实体并处理可能发现的违规行为。

When talking about Typed Data we’ve already seen how the validate() method can be called on the DataType plugin instance which holds a data definition. When it comes to entities, this can happen both at entity and field levels.

在讨论类型化数据时,我们已经了解了如何在保存数据定义的DataType插件实例上调用validate()方法。 当涉及实体时,这可以在实体和现场级别上发生。

For instance, we can validate the entire entity using the validate() method:

例如,我们可以使用validate()方法验证整个实体:

$entity->set('title', 'this is too long of a title'); $violations = $entity->validate();

In our previous article, we added the Length constraint to Node titles to prevent title strings longer than 5 characters. If that is still in place and we run the code above, the validation should obviously fail. The $violations object is now, however, an EntityConstraintViolationListInterface instance which provides some helper methods for accessing violation data specific to Drupal content entities. It’s worth looking into that interface for all the helper methods available.

在上一篇文章中,我们向Node标题添加了Length约束,以防止标题字符串超过5个字符。 如果仍然存在,并且我们运行上面的代码,则验证显然会失败。 现在, $violations对象是一个EntityConstraintViolationListInterface实例,该实例提供了一些辅助方法来访问特定于Drupal内容实体的违规数据。 值得在该界面中查找所有可用的辅助方法。

To get a list of Entity level violations we can use the getEntityViolations() method but we can also loop through all of them. Once we have our individual ConstraintViolationInterface instances, we can inspect them for what went wrong. For instance, we can get the error message with getMessage(), the property path that failed with getPropertyPath() and the invalid value with getInvalidValue(), among other useful things.

要获取实体级别违规的列表,我们可以使用getEntityViolations()方法,但我们也可以遍历所有它们。 一旦有了单独的ConstraintViolationInterface实例,我们就可以检查它们出了什么问题。 例如,我们可以使用getMessage()获取错误消息,使用getPropertyPath()失败的属性路径,以及使用getInvalidValue()失败的无效值,等等。

When it comes to fields, the property path is in the following format: title.0.value. This includes the field name, the key (delta) of the individual field item in the list and the actual property name. This represents the property path of our violation above.

对于字段,属性路径采用以下格式: title.0.value 。 这包括字段名称,列表中单个字段项目的键(增量)和实际属性名称。 这代表了我们上面违反行为的属性路径。

Apart from calling validation on the entire entity (which may be superfluous at times), we can also do so directly on each field:

除了在整个实体上调用验证(有时可能是多余的)之外,我们还可以直接在每个字段上执行验证:

$entity->set('title', 'this is too long of a title'); $violations = $entity->get('title')->validate();

In this case, $violations is again an instance of ConstraintViolationListInterface and can be looped over to inspect each violation. This time, though, the property path changes to no longer include the field name: 0.value.

在这种情况下, $violations还是ConstraintViolationListInterface一个实例,可以循环查看每个违例。 但是,这次属性路径更改为不再包含字段名称: 0.value 。

And lastly, we can even validate the individual items in the list:

最后,我们甚至可以验证列表中的各个项目:

$violations = $entity->get('title')->get(0)->validate();

As we can expect, the difference now is that the property path will only show value in the violation since we know exactly what we are validating: the first data definition in the list.

如我们所料,现在的区别是属性路径将仅在违例中显示value ,因为我们确切知道要验证的内容:列表中的第一个数据定义。

约束和验证器 (Constraints and Validators)

We only touched upon the aspect of constraints and validators but let us better understand how they work by creating one ourselves. We will create one single constraint and validator but that can be used for both Node entities and their fields. It’s always better to have constraints targeted to the data definition we want but for the sake of brevity, we’ll do it all in one constraint to see how all these options could be handled.

我们仅涉及约束和验证器方面,但让我们通过创建自己来更好地了解它们如何工作。 我们将创建一个约束和验证器,但可将其用于Node实体及其字段。 使约束针对我们想要的数据定义总是更好,但是为了简洁起见,我们将在一个约束中进行所有操作以查看如何处理所有这些选项。

The business case of our example is to have a constraint that we can apply to any string-based content entity field that would force the string to contain a certain alphanumeric code. The latter will be passed on as an option so it can be reused. If it’s applied to a Node entity, we want to make sure the title of the node contains the code. So let’s get started.

本示例的业务案例是要有一个约束,我们可以将其应用于任何基于字符串的内容实体字段,该字段将强制字符串包含特定的字母数字代码。 后者将作为选项传递,因此可以重用。 如果将其应用于Node实体,我们要确保该节点的标题包含代码。 因此,让我们开始吧。

First, inside our demo module, which can be found in this git repository, we need to create the Validation folder inside the Plugin folder of our namespace (the src/ directory). Inside that, we need the Constraint folder. This is because constraints are plugins expected to be defined in there.

首先,在我们的demo模块(可以在此git存储库中找到)中,我们需要在名称空间的Plugin文件夹( src/目录)内创建Validation文件夹。 在其中,我们需要Constraint文件夹。 这是因为约束是应该在此处定义的插件。

Second, inside Constraint/, we can create our constraint class:

其次,在Constraint/ ,我们可以创建约束类:

<?php /** * @file * Contains \Drupal\demo\Plugin\Validation\Constraint\HasCodeConstraint. */ namespace Drupal\demo\Plugin\Validation\Constraint; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Exception\MissingOptionsException; /** * Constraint for checking if a string contains a certain alphanumerical code. * * @Constraint( * id = "HasCode", * label = @Translation("Has code", context = "Validation"), * type = { "string", "entity:node" } * ) */ class HasCodeConstraint extends Constraint { /** * Message shown when the code is missing. * * @var string */ public $messageNoCode = 'The string <em>%string</em> does not contain the necessary code: %code.'; /** * The code this constraint is checking for. * * @var string */ public $code; /** * Constructs a HasCodeConstraint instance. * * @param null $options */ public function __construct($options = NULL) { if ($options !== NULL && is_string($options)) { parent::__construct(['code' => $options]); } if ($options !== NULL && is_array($options) && isset($options['code'])) { parent::__construct($options); } if ($this->code === NULL) { throw new MissingOptionsException('The code option is required', __CLASS__); } } }

As we mentioned earlier, the constraint plugin class is relatively simple. It has the expected annotation which, among boilerplate metadata, also specifies what type of data this constraint can be applied to. We chose both a simple string and the Node entity type. We then declare two public properties: a message to be used when the constraint fails (with placeholders being populated inside the validator) and the actual code to be checked for (populated by the constructor of the parent Constraint class). Inside our constructor, we check if the options passed are a string or array (just to be a bit flexible) and throw an exception if the code parameter is not passed as an option in any shape or form.

如前所述,约束插件类相对简单。 它具有预期的注释,在样板元数据中,该注释还指定了可以将此约束应用于哪种数据类型。 我们选择了一个简单的字符串和Node实体类型。 然后,我们声明两个公共属性:约束失败时使用的消息(在验证程序中填充占位符)和要检查的实际代码(由父Constraint类的构造函数填充)。 在构造函数内部,我们检查传递的选项是字符串还是数组(只是有点灵活),如果未以任何形状或形式将代码参数作为选项传递,则抛出异常。

One of the tasks of the parent Constraint class is to specify which class will be used to validate this constraint. By default, it is a class named like the constraint itself but with Validate appended at the end (HasCodeConstraintValidator). If we wanted to create a differently named and/or namespaced class, we would have to override the validatedBy() method and return the fully qualified name of the class we want.

父Constraint类的任务之一是指定将使用哪个类来验证此约束。 默认情况下,它是一个像约束本身一样命名的类,但在末尾附加了Validate ( HasCodeConstraintValidator )。 如果我们要创建一个不同名称和/或命名空间的类,则必须重写validatedBy()方法并返回所需类的完全限定名称。

Let’s now see the HasCodeConstraintValidator class since for us the default is fine:

现在让我们看一下HasCodeConstraintValidator类,因为对于我们而言,默认值很好:

<?php /** * @file * Contains \Drupal\demo\Plugin\Validation\Constraint\HasCodeConstraintValidator. */ namespace Drupal\demo\Plugin\Validation\Constraint; use Drupal\Core\Field\FieldItemListInterface; use Drupal\node\NodeInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface; /** * Validates the HasCodeConstraint. */ class HasCodeConstraintValidator extends ConstraintValidator { /** * Validator 2.5 and upwards compatible execution context. * * @var \Symfony\Component\Validator\Context\ExecutionContextInterface */ protected $context; /** * {@inheritdoc} */ public function validate($data, Constraint $constraint) { if (!$constraint instanceof HasCodeConstraint) { throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\HasCodeConstraint'); } // Node entity if ($data instanceof NodeInterface) { $this->validateNodeEntity($data, $constraint); return; } // FieldItemList if ($data instanceof FieldItemListInterface) { foreach ($data as $key => $item) { $violation = $this->validateString($item->value, $constraint); if ($violation instanceof ConstraintViolationBuilderInterface) { $violation->atPath('title')->addViolation(); } } return; } // Primitive data if (is_string($data)) { $violation = $this->validateString($data, $constraint); if ($violation instanceof ConstraintViolationBuilderInterface) { $violation->addViolation(); return; } } } /** * Handles validation of the title field of the Node entity. * * @param NodeInterface $node * @param HasCodeConstraint $constraint */ protected function validateNodeEntity($node, $constraint) { foreach ($node->title as $item) { $violation = $this->validateString($item->value, $constraint); if ($violation instanceof ConstraintViolationBuilderInterface) { $violation->atPath('title') ->addViolation(); } } } /** * Handles validation of a string * * @param $string * @param $constraint * * @return \Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface */ protected function validateString($string, $constraint) { if (strpos($string, $constraint->code) === FALSE) { return $this->context->buildViolation($constraint->messageNoCode, array('%string' => $string, '%code' => $constraint->code)); } } }

The main job of this class is to implement the validate() method and build violations onto the current execution context if the data passed to it is somehow invalid. The latter can be more than one type of data, depending on what the constraint is applied to.

该类的主要工作是实现validate()方法,并在传递给它的数据无效的情况下在当前执行上下文中构建违例。 后者可以是一种以上类型的数据,具体取决于将约束应用于什么类型。

In our example, we use the constraint for entities, field item lists and primitive data as well. This is just to save us some space. But the logic is that if a Node entity is passed, the code is checked in its title while for the the field items an iteration is performed to check the values of the fields. And of course, the constraint can also be added to individual field items in which case the $data variable would be the value of the data definition.

在我们的示例中,我们还将约束用于实体,字段项目列表和原始数据。 这只是为了节省我们一些空间。 但是逻辑是,如果传递了Node实体,则将在其标题中检查代码,而对于字段项则执行迭代以检查字段的值。 当然,也可以将约束添加到各个字段项,在这种情况下, $data变量将是数据定义的值。

The $context property has a handy buildViolation() method which allows us to specify a number of things related to what actually failed (path, message, etc).

$context属性有一个方便的buildViolation()方法,它使我们可以指定许多与实际失败(路径,消息等)有关的事物。

So if we want to make use of this constraint, we can apply what we learned earlier and do one of the following 3 things:

因此,如果我们想利用此约束,则可以应用我们先前学到的知识,并执行以下三件事之一:

Inside hook_entity_type_alter():

在hook_entity_type_alter()内部:

$node = $entity_types['node']; $node->addConstraint('HasCode', ['code' => 'UPK']);

This adds the constraint to the Node entities so all node titles now have to contain the code UPK.

这将约束添加到Node实体,因此所有节点标题现在都必须包含代码UPK 。

Inside hook_entity_base_field_info_alter() or hook_entity_bundle_field_info_alter(), one of the following two lines:

在hook_entity_base_field_info_alter()或hook_entity_bundle_field_info_alter() ,以下两行之一:

$title->addConstraint('HasCode', ['code' => 'UPK']); $title->addPropertyConstraints('value', ['HasCode' => ['code' => 'UPK']]);

Where $title is a field definition and the first case adds the constraint to the entire list of items while the latter adds it straight to the individual item.

其中$title是字段定义,第一种情况将约束添加到整个项目列表,而后者则将约束直接添加到单个项目。

These three possibilities cover the three cases the constraint validator handles in our example. And that is pretty much it. Not too difficult.

这三种可能性涵盖了示例中约束验证器处理的三种情况。 就是这样。 不太困难。

结论 (Conclusion)

In this article, we’ve continued our dive into the Entity Validation API by looking at two things: validation and violation handling, and the creation of custom constraints. We’ve seen that once applied onto entities or field data definitions, constraints can be very easily validated and inspected for violations. This API borrows a lot from Symfony and makes it a breeze to decouple validation from the Form API.

在本文中,我们通过查看两件事继续进行实体验证API的研究:验证和违规处理以及自定义约束的创建。 我们已经看到,一旦将约束应用于实体或字段数据定义,就可以非常容易地验证和检查约束是否违反。 该API从Symfony借来了很多东西,使验证与Form API的分离变得轻而易举。

Also easy, as we’ve seen, is to extend the existing pool of validation criteria available. This is done via constraint plugins, all coupled with their own validators and which can be written in very reusable ways. It’s very interesting to play around with these and see how all the pieces interact. And it is also a great learning experience.

正如我们所看到的,扩展现有的可用验证标准池也很容易。 这是通过约束插件完成的,所有约束插件都与它们自己的验证器一起使用,并且可以以非常可重用的方式编写。 玩弄这些,看看所有部分如何相互作用是非常有趣的。 这也是一次很棒的学习经历。

翻译自: https://www.sitepoint.com/drupal-8-entity-validation-and-typed-data-demonstration/

drupal8 自定义实体

最新回复(0)