Widget classes dependency injection 01

Experimenting with dependency injection in widget classes.

How the flow works

WordPress will instantiate widgets on the widgets_init action.
That action is called from the wp_widgets_init function defined in the widgets.php file.
Plugins and themes usually hook into such an action to call the register_widget function, this is the function body

function register_widget($widget_class) {
    global $wp_widget_factory;

    $wp_widget_factory->register($widget_class);
}

The register_widget function takes one parameter only: the fully qualified name of a class extending the WP_Widget that will in turn be used to create a new widget instance by the WP_Widget_Factory::register method.
The body of that method is this

public function register( $widget_class ) {

    $this->widgets[$widget_class] = new $widget_class();

}

It’s not a complicated flow.
What this flow lacks is the possibility to do any kind of injection: the way the widget class __construct method is called presumes no parameters will be needed by the constructor and no space for constructor or setter method based injection is available.

Why Dependency Injection again?

Dependency Injection means that anything the class instance depends upon to work must be supplied to it from the outside rather than being autonomously instantiated or “taken from the aether”.
Starting from the example widget class implementation shown on the codex I’ve modified it imagining a widget that needs to convey an action link (“cta” stands for “call to action”) associated with some kind of e-commerce plugin.

class myplugin_CTAWidget extends WP_Widget {

    /**
     * Sets up the widgets name etc
     */
    public function __construct() {
        $widget_ops = array( 
            'class_name' => 'my_widget',
            'description' => 'My Widget is awesome',
        );
        parent::__construct( 'my_widget', 'My Widget', $widget_ops );
    }

    /**
     * Outputs the content of the widget
     *
     * @param array $args
     * @param array $instance
     */
    public function widget( $args, $instance ) {

        if $this->user->is_connected(){
            $user_type = $this->user->get_type();
            $message = $this->user_messages->get_cta_for($user_type);
            $url = $this->user_urls->get_for($user_type);
        } elseif ($this->user->is_pending_registration()) {
            $message = $this->user_messages->get_pending_registration_cta();
            $url = $this->user_urls->get_pending_registration_url();
        } else {
            $message = $this->user_messages->get_register_cta();
            $url = $this->user_urls->get_register_url();
        }

        echo sprint_f('<a href="%s">%s</a>', $url, $message);
    }

    /**
     * Outputs the options form on admin
     *
     * @param array $instance The widget options
     */
    public function form( $instance ) {
        // outputs the options form on admin
    }

    /**
     * Processing widget options on save
     *
     * @param array $new_instance The new options
     * @param array $old_instance The previous options
     */
    public function update( $new_instance, $old_instance ) {
        // processes widget options to be saved
    }
}

The widget method will call on three dependencies:

  • user represents the current whose responsibility is to model the user visiting the page
  • user_messages represents a class whose responsibility is to return the correct user related messages for the site
  • user_urls represents a class whose responsibility is to return the correct user related urls for the site

The code above is not instantiating those instances in any way and will miserably fail; a first approach might be to build those dependencies in the widget __construct method

public function __construct() {
    $this->user = new myplugin_User();
    $this->user_messages = new myplugin_UserMessages();
    $this->user_urls = new myplugin_UserUrls();

    $widget_ops = array( 
        'class_name' => 'my_widget',
        'description' => 'My Widget is awesome',
    );
    parent::__construct( 'my_widget', 'My Widget', $widget_ops );
}

The Dependency Injection factor of the widget is none: there is no way to write a test for the widget class and control those three dependencies; any test aimed at the class will need to have knowledge of the class and of any of its dependencies.
Say I want to test that a not connected user will get a connect message and url I would need to write a test like this

public function tests_not_connected_user_get_connect_cta_and_url(){
    $sut = new myplugin_CTAWidget();
    $urls = new myplugin_UserUrls();
    $messages = new myplugin_UserMessages();

    $url = $urls->get_register_url();
    $message = $messages->get_register_cta();

    $anchor = $sut->widget();

    $expected = sprintf('<a href="%s">%s</a>', $url, $message);
    $this->assertEquals($expected, $anchor);
}

What’s wrong in this test is that I’m testing an output, the anchor, and know how it’s made exactly; my test has a strong coupling with the current code and does not test what I’m interested in: how the anchor is built, not the final anchor markup.

From the aether

When I wrote “taken from the aether” I mean this

public function __construct() {
    global $user, $user_messages, $user_urls;

    $this->user = $user;
    $this->user_messages = $user_messages;
    $this->user_urls = $user_urls;

    $widget_ops = array( 
        'class_name' => 'my_widget',
        'description' => 'My Widget is awesome',
    );
    parent::__construct( 'my_widget', 'My Widget', $widget_ops );
}

and this

public function __construct() {
    $this->user = myplugin_User::instance();
    $this->user_messages = myplugin_UserMessages::instance();
    $this->user_urls = myplugin_UserUrls::instance();

    $widget_ops = array( 
        'class_name' => 'my_widget',
        'description' => 'My Widget is awesome',
    );
    parent::__construct( 'my_widget', 'My Widget', $widget_ops );
}

and poses the same challenges as above being yet again not controllable dependencies (not easily and reliably).
Plus the widget class constructor tells nothing (as in “type-hints nothing”) about what it will need to work.
Default arguments for the constructor are a middle ground that’s immediately appliable

public function __construct(
        myplugin_UserInterface $user = null,
        myplugin_UserMessagesInterface $user_messages = null,
        myplugin_UserUrlsInterface $user_urls = null
    ) {
    $this->user = $user ? $user : new myplugin_User();
    $this->user_messages = $user_messages ? $user_messages : new myplugin_UserMessages();
    $this->user_urls = $user_urls ? $user_urls : new myplugin_UserUrls();

    $widget_ops = array( 
        'class_name' => 'my_widget',
        'description' => 'My Widget is awesome',
    );
    parent::__construct( 'my_widget', 'My Widget', $widget_ops );
}

and allows rewriting the test above

public function tests_not_connected_user_get_connect_cta_and_url(){
    $user = $this->prophesize('myplugin_User');
    $user->is_connected()->willReturn(false);

    $messages = $this->prophesize('myplugin_UserMessages');
    $messages->get_register_cta()->shouldBeCalled();

    $urls = $this->prophesize('myplugin_UserUrls');
    $urls->get_register_url()->shouldBeCalled();

    $sut = new myplugin_CTAWidget($user->reveal(), $messages->reveal(), $urls->reveal());
    $sut->widget();
}

Next

Hacking the widget instantiation process to have it my way.

I appreciate your input