Problem/Motivation

DependencySerializationTrait sets values on properties. As on PHP 8.1 these properties can be marked readonly. This can cause problems.

Steps to reproduce

Add a readonly service property to a form.

Proposed resolution

Do something like what is suggested on https://github.com/Ocramius/GeneratedHydrator/issues/656

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3424344

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

alexpott created an issue. See original summary.

joelpittet’s picture

lslalbai’s picture

Using Drupal\Core\DependencyInjection\DependencySerializationTrait should not make it impossible to have readonly properties that refer to services on a class or classed that inherit from it.

Recommended changes to Drupal\Core\DependencyInjection\DependencySerializationTrait:

  1. Change line 81 to (new \ReflectionProperty($this, $key))->setValue($this, $container->get($service_id));
  2. Change line 96 to (new \ReflectionProperty($this, $key))->setValue($this, $entity_type_manager->getStorage($entity_type_id));
jepster_’s picture

Status: Active » Closed (cannot reproduce)

> Add a readonly service property to a form.

Cannot reproduce an issue here. I've modified web/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php, to have readonly properties in the constructor:

  public function __construct(
    protected readonly string $root,
    protected readonly string $sitePath,
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly ModuleInstallerInterface $moduleInstaller,
    protected readonly UserNameValidator $userNameValidator,
    protected readonly bool $superUserAccessPolicy,
  ) {}

After flushing the cache, I could update the form at /admin/config/system/site-information without issues or errors in my logs. I could also prove that \Drupal\Core\DependencyInjection\DependencySerializationTrait was involved, since \Drupal\Core\Form\FormBase uses the trait.

Also I do not see, where services are modified via DependencySerializationTrait. The injected services are readonly by intent.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

ericgsmith’s picture

Still active as far as I can see - have linked a few other issues when contrib has encountered this problem.

Also I do not see, where services are modified via DependencySerializationTrait. The injected services are readonly by intent.

They are set in __wakeup - potentially with your test in #4 the form was not being serialized - I encountered this in #2975863: Add an exposed filter to the Taxonomy overview view page when a form was being submitted a second time and failed as dependencies were not set. Likely this needs updates to the steps to reproduce as you were right in just following that.

When I also found is the documentation in https://www.drupal.org/docs/drupal-apis/services-and-dependency-injectio... includes examples where readonly is used.

godotislate’s picture

Re-populating readonly class properties in __wakeup() results in fatal errors, whether set directly as $this->property = 'value'; or using reflection (new \ReflectionProperty($this, 'property'))->setValue($this, 'value');. See https://3v4l.org/OPTLj and https://3v4l.org/3mViB

It is possible to set readonly properties in __unserialize() though, which is probably another reason to do #3548971: Replace PHP soft-deprecated __sleep()/__wakeup() with __serialize()/__unserialize().
See https://3v4l.org/G0WCa

ishani patel made their first commit to this issue’s fork.

andypost’s picture

ishani patel’s picture

I've updated the changes below and raised an MR.

Removed:
- __sleep() — mutated $this->_serviceIds / $this->_entityStorages as a side-effect during serialization.
- __wakeup() — direct cause of Cannot initialize readonly property fatal error.

Added:
- __serialize() — same logic as __sleep() but returns a full key => value array without mutating '$this'.
- __unserialize() — same re-injection logic as __wakeup() but PHP allows readonly property assignment here as it is treated as an initialization context (confirmed at https://3v4l.org/G0WCa).
- Both old methods are kept as deprecated stubs for BC, delegating to the new ones.

Kindly check and review it.

Thanks!