Using Closure::BindTo as an Alternative to Rewriting Classes

Using Closure::BindTo as an Alternative to Rewriting Classes

One more trick in the bag to prevent rewriting Magento classes.

Scroll Down for more info

Most Magento developers are aware of the means to change default functionality in Magento:

  • Event observer
  • Extending Magento class and instantiating that extended class
  • Reflection
  • Class rewrite
  • The purpose of this blog post so keep reading
  • Other methods, such as copying a Magento file, but they are not recommended

Our team strives to use observers wherever possible as they are a powerful and capable tool to working with data. However, there are situations where it is simply not possible to use an observer.

Another way of saying this is that we avoid the use of class rewrites at all costs. This helps to prevent rewrite conflicts and other such problems.

You might be surprised to see the reflection method in the list. This is a seldom-used way, but it is important in that we do not have to rewrite a class. Reflection is used to update a property that is protected and does not have a setter method.

Case in point. We wrote a model that provides extra functionality for rendering an image. The primary function, getImage(), loads an instance of Mage_Catalog_Helper_Image.

However, one piece of additional functionality that this class provides is the ability to load in another image, one that is not specified in the product. This image is stored in the file key in $this->data.

Using Reflection, we update the accessibility of the _image property:

if ($this->getFile()) {
    $reflection = new ReflectionObject($this->_image);
    $property = $reflection->getProperty('_model');
    $property->setAccessible(true);

    $model = $property->getValue($this->_image);

    if (method_exists($model, 'setBaseFile')) {
        $model->setBaseFile($this->getFile());
    }
}

As I was preparing for my talk on "Writing Better Code with the New PHP7," I discovered that one of the new features in PHP7 is the Closure::call function. This allows us to take a closure or anonymous function and inject it into an object, executing it within that object’s context (meaning $this === $model).

PHP 5.4+ has a semi-similar method: Closure::bindTo. I would like to discuss that as PHP7 has not been widely adopted as of this writing.

Closure::bindTo is a way to bind a closure to an object. The difference between this and Closure::call in PHP7 is that Closure::bindTo binds the closure to be executed later, whereas Closure::call binds and executes the closure in one function, returning the value returned from the closure.

Instead of having to use Reflection in the previous example, we can rewrite it as:

$updateImage = function($file) {
    if ($this->_image) {
        $this->_image->setBaseFile($file);
    }
};

$updateImage = $updateImage->bindTo($this->_image, $this->_image);
$updateImage();

If you will notice on line 7, I pass the $this->_image object twice. The first time is to set the $this variable in the closure (scope), and the second time is to set the context of where the function will be executed. This defaults to static, which would mean that the closure can normally only access public members. By setting the context to be the object, we can access protected members as well, giving us the freedom to make the modification.

What are the caveats?

  • This is not true rewriting in that this isn’t modifying the output or logic of a function. This is simply updating values in an object via a closure.
  • It must be called by your code.

What are the benefits?

  • Much less code to perform the same action.
  • Easier to understand.
  • Faster than using Reflection (which is notoriously slow).

I hope you can see how this is a handy means to update the value of protected members of an object. We have seen quite a few times where we can refactor our code to take advantage of this.

SwiftOtter, Inc.
It relates to Magento 2, Magento 1, Back end development and Productivity.
Joseph Maxwell - president / senior developer at Swift Otter

President / senior developer at SwiftOtter - @josephmaxs