Categories


Archives


Recent Posts


Categories


Pestle 1.4.1 and the Merits of Inheritance

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

We interrupt this Modern Javascript series to let you know there’s a new release (v1.4.1) of pestle available. Pestle is both a simple PHP command line framework for sharing functions, as well as the world’s preeminent collection of Magento 2 code generation routines. It’s an integral part of our PHP for MVC Developers series, and the missing toolkit for working programmers wrestling with Magento 2.

You can find details on the 12 issues solved in 1.4.1 release over in the closed GitHub Milestone list. This release was a combination of some low hanging fruit, some wrapper/helper commands for the various Magento migration tools, and a new command for easy constructor copying in M2. We’ll discuss the later today, but keep your eyes on Magento Quickies in the coming days/weeks for some examples of the migration helper commands.

Existing users can run

$ pestle.phar selfupdate

to grab the latest version. New users can find more information in the GitHub README.

Constructor Inheritance

The stand out command in this release is magento2:generate:class-child. This command will generate a new PHP class that inherits from an existing class and it copies the closest constructor in the inheritance chain. Running the following command

pestle.phar magento2:generate:class-child  "Pulsestorm\Helloworld\Block\Template" "Magento\Framework\View\Element\Template"

will generate the following class.

<?php
namespace Pulsestorm\Helloworld\Block;

class Template extends \Magento\Framework\View\Element\Template
{
    function __construct(Template\Context $context, array $data = [])
    {
        parent::__construct($context,$data);
    }
}

That is — we have our own class that extends Magento’s Magento\Framework\View\Element\Template class, with a compatible constructor. We’re now free to inject dependencies as needed.

One of the more annoying parts of Magento 2 is suddenly realizing you need a new dependency in your class and having to dig back through the core source code for a parent’s constructor in order to grab all of its dependencies. With magento2:generate:class-child, you can easily generate constructor parameters that are identical to those in the parent class, and then start adding your own.

For folks reading some of the surface level commentary around M2 it may seem strange that I’ve spent time on a tool that assumes the importance of class inheritance. Magento Inc’s been making a lot of noise recently about moving towards everything private/final by default and adopting patterns that (in theory) prefer class composition over inheritance.

However, as a working Magento developer, these choices seem — strange at best. It’s not that there’s not sound software architecture thinking behind them, it’s that those architecture principles seem misplaced for the code base we have, as well as for the sorts of things Magento’s been traditionally good for.

The Case Against Inheritance

It’s probably a good idea to start with the case against inheritance. Lets consider the above example

<?php
namespace Pulsestorm\Helloworld\Block;

class Template extends \Magento\Framework\View\Element\Template
{
    function __construct(Template\Context $context, array $data = [])
    {
        parent::__construct($context,$data);
    }
}

Here we’ve extended Magento’s base template class. This means my template gets access to all the public and protected methods inside of Magento\Framework\View\Element\Template. This is the power of inheritance. I have a new class type that will behave the same as previous class types. Anywhere Magento core code uses a Magento\Framework\View\Element\Template object, I can use my class without needing to write a single additional line of code. If I want to do something that Magento\Framework\View\Element\Template objects already do, I get to do it without writing a single additional line of code (except, of course, the code that calls those base class methods)

The problem with this? The developer responsible for maintaining Magento\Framework\View\Element\Template now needs to make sure that any changes they make are compatible with what I’ve done. This means every public and protected method needs to act the same when used with any inputs, and that the state of the public and protected properties needs to remain the same when those public and protected methods manipulate that state.

This can be challenging enough when everyone works inside the same building and draws a paycheck from the same organization. Add the challenge of a distributed work force, with often unclear/murky business/employment relationships around all parties? Under those sorts of circumstances it can be a maddening, seemingly impossible affair.

One solution for the maintainer of Magento\Framework\View\Element\Template? Discourage inheritance. Make all methods and data properties private (save one public method), mark your classes as final, and tell developers they should instantiate a new instance of your class and use the object instead of inheriting from its class. This is composition. We, as third party developers, use objects provided by the system owner to do things.

The maintainer of this theoretical Magento\Framework\View\Element\Template class still needs to make sure their one public method works the same from version to version, but they’re free to change any implementation detail they need to, because all those implementation details are in private methods that only the class itself can call.

The Problem with Composition

Class composition is a fine architecture approach. However — it’s not without tradeoffs. It reduces the ability of the third party developers (or first party team members of the Magento\Framework\View\Element\Template maintainer) from re-using any code in those private methods. It operates from an assumption that either the system owner will be able to anticipate everything a third party developer needs, or that the third party team has the resources and bandwidth to write this functionality for themselves.

In an open source project, the practical effect is resource strapped third party developers will copy and paste those private methods into their own classes. This makes for massive code duplication, and it means when that code no longer works in a future version (which remember, is the point of private methods), the third party code will no longer work.

In an open source system, the practical effect of shifting to class composition is the burden of maintenance shifts from the system owner to the third party developers using the system. In a world of infinite resources this wouldn’t be a problem. If any of you have discovered this world, please get in touch with your literature.

Inheritance, Class Composition, and Magento 2

We have one last wrinkle to consider with Magento 2. Magento 2 is, still, a PHP MVC framework. Web oriented MVC frameworks have, for over a decade, worked with an inheritance centric world view. Consider what I said above

One solution for the maintainer of Magento\Framework\View\Element\Template? Discourage inheritance. Make all methods and data properties private (save one public method), mark your classes as final, and tell developers they should instantiate a new instance of your class and use the object instead of inheriting from its class

This probably caused a lot of cognitive dissonance for Magento developers. The whole point of Magento’s block hierarchy is you inherit from a block, gets its powers, and make small changes to its functionality.

The same holds true for Magento’s (or any PHP MVC system’s) controller classes. Want a page that acts like the Catalog listing page? Create a new controller that inherits from the old one, and make small changes to enable your new functionality.

Putting aside the whole first-party/third-party dynamic, trying to shift Magento 2’s codebase over to a methodology that favors composition over inheritance seems like a boil-the-ocean sort of problem that will, at best, meet with mixed and tepid results.

Practical Steps Forward

So, for a working Magento developer? We’re stuck in a world where we need to rely on inheritance techniques in a codebase that’s becoming increasingly hostile towards them. This is where commands like magento2:generate:class-child can offer you small time savings everyday which will add up to overall improved productivity. I’m knee deep in a number of migration projects and we’re often dealing with blocks that have a significant number of dependencies.

<?php
namespace Pulsestorm\Helloworld\Block;

class Page extends \Magento\Cms\Block\Page
{
    function __construct(
        \Magento\Framework\View\Element\Context $context,
        \Magento\Cms\Model\Page $page,
        \Magento\Cms\Model\Template\FilterProvider $filterProvider,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Cms\Model\PageFactory $pageFactory,
        \Magento\Framework\View\Page\Config $pageConfig,
        array $data = []
    ) {
        parent::__construct($context,$page,$filterProvider,$storeManager,$pageFactory,$pageConfig,$data);
    }
}

Not needing to manually copy these constructor parameters from the parent class and then pull together a parent::__construct parameter string may seem like a small thing — but it’s a fiddly and error prone process that can add hours to a project over a week. Like so many developer tools, magento2:generate:class-child does something tedious for us so we can focus our limited attention elsewhere.

Series Navigation<< Pestle 1.3 and AbstractModel UI GenerationPestle 1.4.4 Released >>

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 13th June 2017

email hidden; JavaScript is required