Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

When All Else Fails, Reflect on the Fail

Parent Feed: 

While coding the MongoDB integration for Drupal 8 I hit a wall first with the InstallerKernel which was easy to remedy with a simple core patch but then a similar problem occurred with the TestRunnerKernel and that one is not so simple to fix: these things were not made with extensibility in mind. You might hit some other walls -- the code below is not MongoDB specific. But note how unusual this is: you won’t hit similar problems often. Drupal 8 very extensible but it has its limits. But when you need to bypass those limits, PHP has a solution for you to extend classes (to some extent) that were not meant to be extended. Yes, it’s a hack. But when all else fails...

Be very careful because the next version of Drupal core or other software you are commando-extending might not work the same internally and so your hack will break.

With that caveat, let me introduce you to reflection. While some might think it was only meant to investigate objects, already in PHP 5 there was a ReflectionProperty::setValue method. In PHP 5.3 ReflectionProperty::setAccessible was added.

$r = new \ReflectionObject($kernel);
$services = $r->getProperty('serviceYamls');
$services->setAccessible(TRUE);
$value = $services->getValue($kernel);
$value['app'][] = 'modules/mongodb/mongodb.services.yml';
$services->setValue($kernel, $value);

Let’s investigate carefully what happens here because it is not evident and the documentation is lacking. $services is a ReflectionProperty object and while it was created by a getProperty call on a ReflectionObject encapsulating the $kernel object, the $services object is completely ignorant that it belongs to the $kernel object. We could’ve created it from the InstallerKernel class for the same result, it doesn’t matter.

Once this is understood, there are few surprises in this block of code: the property is protected so we need to make it accessible. The setAccessible() call will not make $kernel->serviceYamls public because, again, the $services ReflectionProperty object is ignorant of the $kernel object it was created from. It will, however, enable us to call getValue and setValue on it. But again, we need to pass $kernel to both the getValue and the setValue call.

So this way and only this way we can change a protected object property.

Even better, this works for private properties as well. So if you need to extend a class which has private properties (many Symfony classes do this) then instead of extending, wrap the object (this is called the delegate pattern) and use this method to access its private properties. As an example, let’s extend Symfony\Component\DependencyInjection\Scope (this is the shortest class I could find with a private property and an interface, the example is totally nonsensical and has nothing to do with the Scope class, it’s just an example):

class MyScope implements ScopeInterface {
  public function __construct($name, $parentName = ContainerInterface::SCOPE_CONTAINER) {
    $this->scope = new Scope($name, $parentName);
  }
  public function getName() {
    // Awful hack breaking encapsulation.
    $reflection_scope = new \ReflectionObject($this->scope);
    $name = $reflection_scope->getProperty('name');
    $name->setAccessible(TRUE);
    $value = ‘my’ . $name->getValue($this->scope);
    $name->setValue($this->scope, $value);
    return $value;
  }
  public function getParentName() {
    // This is proper: delegate the call.
    return $this->scope->getParentName();
  }
}
Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web