Problem/Motivation

The lack of any kind of validation for the 'read-only' status of a field definition came up in #2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior.

Proposed resolution

Add a ReadOnly constraint and use it automatically for all field definitions.

Remaining tasks

Agree, review, commit.

User interface changes

Nope.

API changes

API addition: a new constraint is added.

Data model changes

Nope.

Comments

amateescu created an issue. See original summary.

amateescu’s picture

StatusFileSize
new4.81 KB

Something like this.

amateescu’s picture

Status: Active » Needs review
wim leers’s picture

Looking great!

  1. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReadOnlyConstraint.php
    @@ -0,0 +1,24 @@
    +  public $message = 'This field is read-only and can not be changed.';
    

    This message must list the field name. Otherwise it'll be infuriatingly confusing.

  2. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReadOnlyConstraintValidator.php
    @@ -0,0 +1,64 @@
    +   * Constructs a ValidReferenceConstraintValidator object.
    

    c/p remnant

  3. +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReadOnlyConstraintValidator.php
    @@ -0,0 +1,64 @@
    +    // We only care about pre-existing entities.
    

    s/pre-existing/updating/

  4. Most importantly: because we have no forms that allow you to modify read-only fields, I think this change is entirely transparent to Drupal UI users? Hence no BC break? :)

Status: Needs review » Needs work

The last submitted patch, 2: 2826101.patch, failed testing.

wim leers’s picture

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new5.11 KB
new3.84 KB

Yup, but in general we shouldn't add this validation to computed fields, so here's a general fix for it.

Thanks for the review, fixed all those points.

Most importantly: because we have no forms that allow you to modify read-only fields, I think this change is entirely transparent to Drupal UI users? Hence no BC break? :)

I would surely hope we aren't able to change the value of read-only fields in the UI, so yes, this shouldn't introduce any change in the UI.

wim leers’s picture

Yup, but in general we shouldn't add this validation to computed fields, so here's a general fix for it.

We shouldn't? Shouldn't we disallow sending values for computed fields? Aren't all computed fields read-only by definition too? I guess that for something like the "path" field, a default value can be computed, but it's still modifiable — is that why not all computed fields are read-only too?


Besides that last question needing a solid answer, and probably needing documentation in the constraint's code, this looks ready to me. Although I think that this does need sign-off from somebody with deep Entity system knowledge: I don't think I can RTBC this.

Status: Needs review » Needs work

The last submitted patch, 7: 2826101-6.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new6.15 KB
new1.04 KB

Shouldn't we disallow sending values for computed fields?

I don't think we should, maybe we just want to compute the value somewhere else and set it? :) Or.. what do you mean by "sending"?

I guess that for something like the "path" field, a default value can be computed, but it's still modifiable — is that why not all computed fields are read-only too?

Let me introduce you to #2392845: Add a trait to standardize handling of computed item lists :D

Here's the fix for the last test fail, we just need to update the test expectation since we're receiving an additional validation error now.

wim leers’s picture

Let me introduce you to #2392845: Add a trait to standardize handling of computed item lists :D

Oh dear lord.

Here's the fix for the last test fail, we just need to update the test expectation since we're receiving an additional validation error now.

Oh, this makes sense!


+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -284,6 +284,13 @@ public function getConstraints() {
+    // Add the read-only constraint automatically for fields that need it.
+    if (!$this->getFieldDefinition()->isComputed() && $this->getFieldDefinition()->isReadOnly()) {

If I read the comment, then I expect only the second condition to be applied. Because that first condition is also there, this needs a clarifying comment. If we should link to https://www.drupal.org/node/2392845 to improve this in the future, then that's fine.

wim leers’s picture

Status: Needs review » Needs work

NW just for that comment. Other than that, this looks ready IMO.

amateescu’s picture

Status: Needs work » Closed (won't fix)

Berdir points out that this would be quite a performance regression.

mglaman’s picture

Status: Closed (won't fix) » Active

I asked @amateescu about berdir's concerns.

I can't really remember the exact discussion, but it was about the loadUnchanged() call, which is very costly because it bypasses all caches, and it would be called a lot of each field that was marked as required

The constraint loads the unchanged entity and compares if the value changed.

In my opinion, the constraint would always throw a violation if !$entity->isNew(). The value should not be considered valid if you're providing the value after the entity is created. Read-only values, as I have come to take them, are things you set when creating the entity.

For instance: bundles are read-only.

In Drupal Commerce a payment method's payment gateway reference is read-only. A payment transaction can reference a payment method and its payment gateway. These are read-only. Each of these fields is populated when the entity is created and does not change.

mglaman’s picture

Version: 8.3.x-dev » 8.9.x-dev
Assigned: Unassigned » mglaman

Rolling an updated patch.

mglaman’s picture

Assigned: mglaman » Unassigned
Status: Active » Needs review
StatusFileSize
new3.95 KB

Here is a reroll of #10. It takes into consideration #11 and doesn't check computed fields, as those wouldn't be receiving a value to be validated.

The constraint validator now merely checks if the entity is not new.

mglaman’s picture

Status: Needs review » Needs work

🤦‍♂️ I canceled the build.

+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ReadOnlyConstraintValidator.php
@@ -0,0 +1,31 @@
+    if (!$value->getEntity()->isNew()) {
+      $this->context
+        ->buildViolation($constraint->message, ['%field_name' => $value->getName()])
+        ->atPath($value->getName())
+        ->setInvalidValue($value)
+        ->addViolation();
+    }

I was thinking too simplistically and this causes any later save to mark those fields as invalid.

This would be somewhat easier if we could access the $values property on fieldable entities.

  /**
   * The plain data values of the contained fields.
   *
   * This always holds the original, unchanged values of the entity. The values
   * are keyed by language code, whereas LanguageInterface::LANGCODE_DEFAULT
   * is used for values in default language.
   *
   * @todo: Add methods for getting original fields and for determining
   * changes.
   * @todo: Provide a better way for defining default values.
   *
   * @var array
   */
  protected $values = [];

  /**
   * The array of fields, each being an instance of FieldItemListInterface.
   *
   * @var array
   */
  protected $fields = [];

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

darvanen’s picture

Could this be tackled with $entity->original or by loading the entity from storage to compare to?

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.