Comments

xano’s picture

Status: Active » Needs review
StatusFileSize
new7.2 KB
berdir’s picture

  1. +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTraitUnitTest.php
    @@ -2,39 +2,39 @@
     /**
      * @file
    - * Contains \Drupal\Tests\Core\DependencyInjection\DependencySerializationTest.
    + * Contains
    + * \Drupal\Tests\Core\DependencyInjection\DependencySerializationTraitUnitTest.
      */
    

    I really think we shouldn't move this on two lines. 3 characters more and it would be too long anyway ;)

  2. +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTraitUnitTest.php
    @@ -2,39 +2,39 @@
    -   * Tests serialization and unserialization.
    +   * @covers ::__sleep
    +   * @covers ::__wakeup
        */
    -  public function testSerialization() {
    +  public function test__WakeUp() {
    

    not sure why the method needs to be renamed? We can't name it after both methods and __sleep() is just as important ;)

  3. +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTraitUnitTest.php
    @@ -43,22 +43,12 @@ public function testSerialization() {
    -    // The original object got _serviceIds added so removing it to check
    -    // equality.
    -    unset($dependencySerialization->_serviceIds);
    -
    -    // Ensure dependency injected object remains the same after serialization.
    -    $this->assertEquals($dependencySerialization, $object);
    -
    -    // Ensure that _serviceIds does not exist on the object anymore.
    -    $this->assertFalse(isset($object->_serviceIds));
    -
    

    The reason it is declared on the class is that it can be public, so that this assertion can work.

    I don't think it should be removed. Maybe changed to assert with an empty array. or do = NULL instead of array() ?

xano’s picture

StatusFileSize
new9.2 KB
new6.87 KB

What about this? By splitting the tests for __sleep() and __wakeup(), we make debugging easier.

Status: Needs review » Needs work

The last submitted patch, 3: drupal_2208115_3.patch, failed testing.

jhodgdon’s picture

There's an issue
#2206175: Document traits that use methods on interfaces
which is proposing a standard for how to document Traits.

The suggestions there (not official standards yet but probably a good idea):

a) Add a @see in the main Trait doc block referencing the interface whose methods it is implementing.

b) Document each method as:

/**
* Implements \Full\Namespaced\Interface\Name::methodName().
*/
function methodName() { }
xano’s picture

Status: Needs work » Needs review
StatusFileSize
new9.46 KB

@jhodgdon: those documentation standards do not apply here, as we are not implementing interface methods.

jhodgdon’s picture

Yeah, we updated the standards for cases like this where it's not implementing an interface. Discussion is ongoing...

Meanwhile, a minor issue to fix in the current patch, and a question:

a)DependencySerialization.php

+ * @deprecated As of Drupal 8.0, use
+ * \Drupal\Core\DependencyInjection\DependencySerializationTrait instead.
  */
 abstract class DependencySerialization {

After @deprecated the next line should be indented two spaces. (like after any other tag)

b) So you are replacing two different abstract classes with the same trait? How come we had two?

berdir’s picture

Status: Needs review » Needs work

I don't see a second class, the other one is a test that's changed so much that it doesn't show up as renamed anymore apparently.

However, I would expect a second removal as mentioned in IRC, and that's in \Drupal\Core\Plugin\PluginBase.

jhodgdon’s picture

My mistake on the second class. Not sure what I was looking at. Maybe I should put my glasses on. :)

sun’s picture

Coming from #2210873: Let plugin bags use the DependencySerializationTrait (was: EditorAdminTest fails on PHP 5.4 silently with unicorn editor)

  1. AFAICS, this trait will have to be used pretty much by every service, controller, form, plugin, and generally all classes that get services injected, because no class can know whether it is going to be serialized somewhere else...?

  2. This trait introduces a dependency on the global state construct of \Drupal::getContainer() to every class that uses it...?

  3. Usage of this trait means that a class cannot implement __sleep() + __wakeup() on its own anymore, unless it implements the following fugliness:

    class MyClass {
      use DependencyInjectionTrait {
        __sleep as __sleepDI;
        __wakeup as __wakeupDI;
      }
    
      public function __sleep() {
        // Perform necessary customizations to class members.
        ...
        return $this->__sleepDI();
      }
    
      public function __wakeup() {
        $this->__wakeupDI();
        // Restore class members.
        ...
      }
    
    }
    

    cf. http://php.net/manual/en/language.oop5.traits.php#language.oop5.traits.c...

Hrm. All of that sounds very concerning.

I wonder whether we shouldn't change this trait implementation to use the \Serializable interface instead?

→ Apply the trait to the injected services themselves, instead of each and every single consuming class?

trait DependencyInjectionTrait implements \Serializable {

  public function serialize() {
    if (isset($this->_serviceId) {
      return 'service:' . $this->_serviceId;
    }
    return serialize($this);
  }

  public function unserialize($data) {
    $data = unserialize($data);
    if (strpos($data, 'service:') === 0) {
      $this = \Drupal::service(substr($data, 8));
    }
  }

}

(untested pseudo code)


Or perhaps we have to rethink entirely — if the state of injected dependencies cannot be serialized/unserialized with other objects (because the state may differ in the context in which unserialization happens), then why are we storing those stateful objects on the serializable object in the first place?

That appears to be the root cause, and this entire futzing with serialization only exists to prevent serialization of class members that shouldn't be class members to begin with?

For example, why can't we do this instead?

class MyClass {

  public __construct(ModuleHandlerInterface $module_handler, ...) {
    $this->setModuleHandler($module_handler);
  }

  protected function moduleHandler(ModuleHandlerInterface $module_handler = NULL) {
    static $instance;
    if (isset($module_handler)) {
      $instance = $module_handler;
    }
    return $instance;
  }

  public function doSomething() {
    return $this->moduleHandler()->invokeAll('blah');
  }

}

→ No more stateful services in class members of the consuming class.

serialize() works as-is.

Only unserialize() needs to re-inject the services from the container.

berdir’s picture

I wonder whether we shouldn't change this trait implementation to use the
\Serializable interface instead?

\Serializable is messed up on 5.4+, or maybe we messed up but at least right now, we have to use __unserialize() in a lot of places instead of the interface.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new11.57 KB
new5.25 KB

However, I would expect a second removal as mentioned in IRC, and that's in \Drupal\Core\Plugin\PluginBase.

That was added to #3, but lost in #6. It's back in this one again.

Status: Needs review » Needs work

The last submitted patch, 12: drupal_2208115_12.patch, failed testing.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new11.65 KB
new672 bytes

Duh.

Crell’s picture

sun: The root problem here is that we're serializing objects that we have no business serializing, ie, those that have big service chains. The base class this trait is replacing is already a disgusting hack. The real answer is to adjust our key serializable objects to not have direct dependencies on services.

effulgentsia’s picture

@Crell: How do we do that? Forms are one of our key serializable objects, and very often have a dependency on a plugin manager and/or other services provided by the module. In fact isn't that precisely what a good UI/API separation within a module would always lead to?

effulgentsia’s picture

Forms are one of our key serializable objects

Perhaps we can fix that: #2183275-17: Use cache for $form, kv for $form_state.

Crell’s picture

I totally forgot this issue was here and just redid it. :-/ However, I went ahead and converted all usages to the trait outright. Let's see if the bot likes it or not...

This patch JUST does a straight conversion; it doesn't change anything else about the base class/trait. Its existence is a code smell but that's a separate matter from just making it a trait.

Crell’s picture

14: drupal_2208115_14.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 18: 2208115-dependency-serialization-trait.patch, failed testing.

The last submitted patch, 14: drupal_2208115_14.patch, failed testing.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new678 bytes
new16.67 KB

Status: Needs review » Needs work

The last submitted patch, 22: drupal_2208115_22.patch, failed testing.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new4.35 KB
new19.39 KB

I totally forgot this issue was here and just redid it.

I just realized this meant that you didn't re-roll and created a completely different patch, which is not the most helpful way to file another patch in an existing issue, TBH.

Status: Needs review » Needs work

The last submitted patch, 24: drupal_2208115_24.patch, failed testing.

xano’s picture

We just ran into

It is not possible for __sleep() to return names of private properties in parent classes. Doing this will result in an E_NOTICE level error. Instead you may use the Serializable interface.

(source php.net)

yesct’s picture

Issue tags: +Traits
tim.plunkett’s picture

Priority: Normal » Major

I'm having to copy this code into yet another class, :\

damiankloip’s picture

Status: Needs work » Needs review
StatusFileSize
new17.49 KB
new685 bytes

So we basically have to not have configFactory private on FormBase, which is not the end of the world by any means IMO.. OR, not have this issue. Using Serializable is not an option as we can't implement an interface from a trait. So then in that case we are back to what we have now anyway :)

Status: Needs review » Needs work

The last submitted patch, 29: 2208115-29.patch, failed testing.

damiankloip’s picture

Status: Needs work » Needs review
StatusFileSize
new17.51 KB

Sorry, branch rebasing fail.

Status: Needs review » Needs work

The last submitted patch, 31: 2208115-31.patch, failed testing.

damiankloip’s picture

StatusFileSize
new18.29 KB
new808 bytes

Another one crept in.

yched’s picture

+++ b/core/lib/Drupal/Core/Plugin/PluginBase.php
@@ -57,7 +57,7 @@ public function __sleep() {
-   * @todo Remove when Drupal\Core\DependencyInjection\DependencySerialization
+   * @todo Remove when Drupal\Core\DependencyInjection\DependencySerializationTrait
    * is converted to a trait.

Should be pretty close now ;-)

damiankloip’s picture

Status: Needs work » Needs review
StatusFileSize
new19.39 KB
new2.24 KB

hehe

berdir’s picture

+++ b/core/lib/Drupal/Core/Form/FormBase.php
@@ -38,14 +39,9 @@
   /**
    * The config factory.
    *
-   * This is marked private in order to force subclasses to use the
-   * self::config() method, which may be overridden to address specific needs
-   * when loading config. See \Drupal\Core\Form\ConfigFormBase::config() for an
-   * example of this.
-   *
    * @var \Drupal\Core\Config\ConfigFactoryInterface
    */
-  private $configFactory;
+  protected $configFactory;
 

Should we instead update the comment to state something like "Never use this directly, use self::config() instead"? It's still correct even if it's protected again.

Looks great otherwise, if there ever was a class that's destined to become a trait then it's this ;)

Note that I just RTBC'd #2283929: Clean up condition plugins to have schema support and to allow them to be optional, if that gets in first, we need to update that and add the trait there as well.

damiankloip’s picture

StatusFileSize
new19.64 KB
new618 bytes

Thanks. Good point - added a comment to that effect back in.

berdir’s picture

Status: Needs review » Needs work

Works for me, the linked issue above was committed, so we need to update the class there as well.

damiankloip’s picture

Status: Needs work » Needs review
StatusFileSize
new22.37 KB
new2.73 KB

Nice, there we go.

xano’s picture

berdir’s picture

Status: Needs review » Reviewed & tested by the community

Great, I think this is ready :)

dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to 8.x. Thanks.

  • Commit f033742 on 8.x by Dries:
    Issue #2208115 by Xano, damiankloip, Crell: Add...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

andypost’s picture