My thoughts on Magento 2

It really has been a fun few months recently for me. 2015 saw me start working on Magento 2 and only recently have I had time to reflect on what Magento 2 really means to me. First up is a video interview I did for Session Digital on my involvment with Magento 2 and my initial thoughts:

Then I gave a talk on getting started with Magento 2 for a developer. The talk went down really well and I got lots of great feedback.

Magento 2 is a massive step forward in terms of technology for E-Commerce and the 2 projects I have deployed to production have been a great success thanks to the speed at which Magento 2 allows us to work.

If you have any questions or what to know more about my experiences with Magento 2 or Magento 2 in general, Post a question below and I will help out.

Describing the core domain and UI in Magento 2 with Behat

Previsoulsy I covered how to use PHPSpec to describe behaviour at a low level or Unit test level. Today lets take a look at what it takes to use Behat and actually describe user behaviour through the site.

Before we start I recomend reading Modelling By Example by Everzet. Its a really great article about BDD and DDD.

So the first thing we need to do is install Behat in our project. Yet again open up the main composer.json manifest file and this time in the require-dev section add in "behat/behat": "3.*", Save the file and run composer install. Assuming all went well we should be able to run ./bin/behat and get an error message about missing feature definitions. This is OK we will be fixing this soon.

As we are going to be describing both the core domain and the UI or CLI we want to setup some configuration for Behat. To do this we need to create a behat.yml file in our project with the following settings

default:  
    suites:  
        core_features:  
            paths:    [ %paths.base%/features/core ]()  
            contexts: [ CoreDomainContext ]()  
        cli_features:  
            paths:    [ %paths.base%/features/cli ]()  
            contexts: [ CliContext ]()  

Great so what are we doing here ? Well we want to enable suites so that we can have different feature files and context for our Core Domain and our CLI application. In the future we will also have a UI suite for when we are testing the website itself. Then for each of the suites we just want to specify where the feature files will be located and what context file to use.

Save this file and run ./bin/behat --init this will go ahead and create us all the required folders that we need.

Now in this guide we wont be writing any new code as we did this previously in the PHPSpec Guide so if you do not have the source files generated already I would go back and do this first. I know its bad form to write tests after the fact but meh this is my blog.

Lets create our first scenario. Under features/core create a new file for Hello-World-module.feature with the following content

Feature: The Hello World module should return Hello World when ran  
  In order to learn Magento 2  
  As a developer excited to learn more  
  I want to get feedback when I run my CLI module  
  
  Scenario: The hello World module will return a string  
    Given My module exists  
    When I run the Hello World module  
    Then I should be presented with "Hello World"

What we are saying here is that when we call our Core Domain objects directly then we should be returned the string values. Now we are not executing the CLI scripts in this scenario, We will be calling the objects directly and this is made possible because we are de coupling from Magento 2.

If you now open features/bootstrap/CoreDomainContext.php and replace the contents with

\<?php  
  
use Behat\Behat\Tester\Exception\PendingException;  
use Behat\Behat\Context\Context;  
use Behat\Behat\Context\SnippetAcceptingContext;  
use Behat\Gherkin\Node\PyStringNode;  
use Behat\Gherkin\Node\TableNode;  
  
/**  
 * Defines application features from the specific context.  
 */  
class CoreDomainContext implements Context, SnippetAcceptingContext  
{  
    private $helloWorldModule;  
  
    private $output;  
  
    public function __construct()  
    {  
        $this-\>helloWorldModule = new \Jcowie\HelloWorld\Model\HelloWorld();  
    }  
  
    /**  
     [email protected] My module exists  
     */  
    public function myModuleExists()  
    {  
        return true;  
    }  
  
    /**  
     [email protected] I run the Hello World module  
     */  
    public function iRunTheHelloWorldModule()  
    {  
        $this-\>output = $this-\>helloWorldModule-\>sayHello();  
    }  
  
    /**  
     [email protected] I should be presented with :argument  
     */  
    public function iShouldBePresentedWith($argument)  
    {  
        expect($this-\>output)-\>toBe($argument);  
    }  
}

What are we doing here ? Well to start with we create a new instance of our HelloWorld model class as assign it to a private variable that we can inspect and use in future steps. In the When step we call the sayHello method and store the output in a variable again. Finally we make our assertion of the output to ensure the string returned is what we expect. Pretty simple but it shows how powerful PHPSpec and Behat can be when used in combination as well as how we now have the ubiquitous language that we used to describe the core domain with.

If you save the file and run ./bin/behat you will see that the tests are all green now and it ran really fast.

Writing a scenario for the CLI

Great so now we know that our core domain behaves as we expect lets create a functional test to make sure when we run the Magento 2 CLI command our new command is listed.

This time create a new file under features/Cli/Hello-world-cli-module.feature with the following content

Feature: The Hello World module should return Hello World when ran  
  In order to learn Magento 2  
  As a developer excited to learn more  
  I want to get feedback when I run my CLI module  
  
  Scenario: The hello World module will return a string  
    Given My module exists  
    When I run "bin/magento jcowie:helloworld" from the command line  
    Then I should see "Hello World" returned

You will notice it looks similar to the core domain senario but this time we are actually running bin/magento to invoke Magento 2 and our command.

Lets replace CliContext.php with the following:

\<?php  
  
use Behat\Behat\Tester\Exception\PendingException;  
use Behat\Behat\Context\Context;  
use Behat\Behat\Context\SnippetAcceptingContext;  
use Behat\Gherkin\Node\PyStringNode;  
use Behat\Gherkin\Node\TableNode;  
use Symfony\Component\Process\PhpExecutableFinder;  
use Symfony\Component\Process\Process;  
  
/**  
 * Defines application features from the specific context.  
 */  
class CliContext implements Context, SnippetAcceptingContext  
{  
    private $output;  
  
    /**  
     [email protected] My module exists  
     */  
    public function myModuleExists()  
    {  
        if (file_exists("src/Jcowie")) {  
            return true;  
        } else {  
            throw new PendingException("Folder not found for module");  
        }  
    }  
  
    /**  
     [email protected] I run :command from the command line  
     */  
    public function iRunFromTheCommandLine($command)  
    {  
        $process = new Process(getcwd() . "/".  $command);  
        $process-\>run();  
  
        $this-\>output = $process-\>getOutput();  
    }  
  
    /**  
     [email protected] I should see :output returned  
     */  
    public function iShouldSeeReturned($output)  
    {  
        expect(trim($this-\>output))-\>toBe($output);  
    }  
}  

Im using the Symfony component Process to actually run the CLI commands so in the When I create a new process and store the output in a private variable. Then in the Then step I use the expect lib to ensure the output is what I want.

Conclusion

Although the examples here were very simple, you can see how easy it is to use Behat to test both the core domain and the CLI. In the next guide I will go through how we can connect to the database with Behat and how we can setup and configure Selenium to start testing the web application.

Load modules and themes from src in Magento 2

One of the biggest changes for me in Magento 2 is the fact that we no longer have code pools app/code/local etc. This represents a massive shift forward and a great design decision by the Magento 2 core team to adopt the composer autoloader.

Now for shared modules the logic is easy, You create you composer.json manifest file and in the mappings specify via PSR-4 where you want the namespace to resolve to. Then when someone wants to use the module they updated the main projects composer.json manifest and the rest is magic.

But what about the Companies or Solution integrators that use a mixture of both community modules and also develop single use modules. How can a project setup look with PSR-4 support and one off module development.

Well I started to explore how this might look here I see this as being the starting point for any company working with Magento 2. Some points to note are that I am not adding Magento 2 to the repository. Why ? Well I don’t own this code. Its not my code. Im using composer to manage by dependencies for me and always committing the composer.lock file that has the versioned pinned to a specific release so really if I do add Magento 2 code to my repository I am duplicating effort.

Next up you will see that I am adding my code into a src folder. Why again ? Well I could use app/code/NAME if I wanted and this will just work out of the box ( as long as the module I work on has a registration.php file). Yet I wanted to try and follow the same process that other PHP frameworks use Symfony, Laravel etc and this is using the src folder. Now the first attempts add just adding a composer.json file in the module with a registration.php file failed. I even tried updating the PSR-4 and PSR-0 references in the main projects composer.json manifest to resolve the mappings but what I noticed was that a special file created by composer autoload_files did not have the mappings for my single use modules registration.php.

So how did I resolve this. Well:

"autoload": {
        "psr-4": {
            "Magento\\Framework\\": "lib/internal/Magento/Framework/",
            "Magento\\Setup\\": "setup/src/Magento/Setup/",
            "Magento\\": "app/code/Magento/",
            "Jcowie\\HelloWorldModule\\": "src/Jcowie/HelloWorldModule/"
        },
        "psr-0": {
            "Jcowie\\HelloWorldModule\\": "src/Jcowie/HelloWorldModule/"
            "": "app/code/"
        },
        "files": [
            "app/etc/NonComposerComponentRegistration.php",
            "src/Jcowie/HelloWorldModule/registration.php"
        ]
    },

I started by adding the namespace mappings to both the PSR-4 autoloader and the PSR-0 autoloader this is so that composer can find the source files for Jcowie\HelloWorldModule when called upon. Then the important part that I was missing is in the files section. This is how the autoload_files.php gets populated and for every custom ( non composer installed ) module you wish to write you need to add its reference in here. Then running composer dump-autoload checking the files and running my module from the src directory resulted in success.

So why is this such an important win ? Well if you look at the repository then it is a lot cleaner just having the files I own added to the project. Also it keeps the revision history nice and clean, When I upgrade Magento 2 versions I only see the difference in the composer.json and composer.lock files and not a large diff of all the Magento 2 core files that have changed. It also follows on from what other PHP frameworks are doing using the src folder so interoperability between projects can exist.

Getting started with a Magento 2 module

I’ve been using Magento 2 for a few months now. I was lucky to be involved in the merchant_beta program and at the developer meetings in Berlin early last year. Yet lots has changed in the way we develop modules since I started working on the framework almost a year ago. One of the big changes is that now modules can be loaded from any location and require more configuration to setup.

Lets start by creating a new folder within app/code called Jcowie. Think of this as our top namespace.

Now within Jcowie create a new folder for HelloWorldModule This is going to be our actual working Module code. Now I’m still playing with ideas here as there is no technical reason why we need the Module suffix. Yet there are times where I will also have a HelloWorld namespace that is framework agnostic code that I will inject into the Magento 2 code.

Next up we need to add a composer.json manifest file. This is new to Magento 2 but not new to PHP. If you don’t know what composer is don’t worry there are many good guides on the internet and it WILL make you happy.

{
  "name": "Jcowie/HelloWorldModule",
  "description": "A Magento 2 module that creates a new page",
  "type": "magento2-module",
  "version": "1.0.0",
  "license": [
    "OSL-3.0",
    "AFL-3.0"
  ],
  "require": {
    "php": "~5.5.0|~5.6.0|~7.0.0",
    "magento/framework": "~100.0"
  },
  "autoload": {
    "files": [ "registration.php" ],
    "psr-4": {
      "Jcowie\\HelloWorldModule\\": ""
    }
  }
}

Now there are some really important parts of this file to consider and make sure when you create your own modules you do the same. First key part is the type

"type": "magento2-module",

When Magento-Composer parses the files it looks for this type to ensure that both hard and soft dependencies can be read and processed. Ill look into this more in another post but for now just remember this is what makes a Magento 2 module different to a regular ol PHP module.

The final part of configuration that we need to add is the autoloading, this is what Magento 2 will parse when composer runs to ensure this module is added to the autoloader class and its functionality added into Magento 2:

"autoload": {
  "files": [ "registration.php" ],
  "psr-4": {
    "Jcowie\\HelloWorldModule\\": ""
  }
}

There is nothing too complex going on here. Using the composer standard we set a autoload node and define our namespace as Jcowie\\HelloWorldModule and where the src files should be loaded from. In this case this directory. We then tell composer that there is a special file registration.php that has a purpose and needs to be ran to complete the autoloading process.

<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Jcowie_HelloWorldModule',
    __DIR__
);

Now this file almost never changes for all of the modules you will write, Just remember to update the Module name with yours.

Finally to make sure that Magento 2 knows about our module and when running php bin/magento setup:upgrade we know that it will load and process our module we need to update our projects composer.json file. Now this step depends on how you are building a module. This guide assumes that you are working on what I call a ‘Client Project’ where there is no shareable code so it will be added as part of the project itself. If you think or are working on a Module for the community then the workflow will be different.

"autoload": {
        "psr-4": {
            "Jcowie\\": "app/code/Jcowie/"
        },
    },

All we are doing here is to register the Jcowie namespace and where the files for this are located. Running composer dump-autoload will ensure all of the class maps etc are re generated and ready for the application. Finally if we run php bin/magento setup:upgrade we will see in the output that our module is now registered and just to confirm open up app/etc/config.php and you will see a reference to our new module.

What im working on next is how we can put our code anywhere we want. With the power we get from PSR autoloading we should be able to load code directly from src or any location we specify.

How to use PHPSpec and Magento 2 a quick start

Using PHPSpec in Magento 1 was never an easy task. There was no Dependency Injection so it made it hard to actually inject dependencies into our classes. This in turn had the knock on effect of forcing us to actually test the classes our subjects were being used in. Whats the big deal I hear you say? Well. Why should we have to bootstrap the entire framework just to unit test a piece of behavior we are developing? Most of the time ( and if we are designing ) our code well we should be de coupled from the framework so there is a clear divide between business logic and framwework code. more on this in future posts

Now this has all changed, Setting up and using PHPSpec in our code is simples. Lets start by using composer :) Yes composer to install the dependencies into our project.

Open up your composer.json manifest for the project ( if you are using the docker images I am then the sources files are located in src folder.) Hunt down the line that defines require-dev and add in:

"phpspec/phpspec": "2"

Save the file and a quick composer update and we will have bin/phpspec at our disposal.

Now we have our unit testing tool installed lets have a talk about what we are going to build. Well I wanted to show something that is simple and as we are learning PHPSpec and de coupled design here I didn’t want to have to cover lots of Magento 2 concepts. So we are going to add a new command to bin/magento that will say Hello $name; not what you might call rocket science and probably violates all of the design principles for over engineering but meh.

Open up your terminal and run ./bin/phpspec describe Jcowie/HelloWorld/Model/HelloWorld what we are saying here is that we want to describe a new class called HelloWorld. Now if you followed the previous guide and you should then you will notice that I am describing code within HelloWorld yet the module we created previously was called HelloWorldModule. Now the reason is because I want to show how we can decouple framework agnostic code from code that is bound to the framework.

Lets run the spec ./bin/phpspec r select yes to get all the code generation. Lets look at the output.

~/P/M/m/src ❯❯❯ phpspec r                                                                                                                                                        master ✚ ✱ ◼

      Jcowie\HelloWorld\Model\HelloWorld

  10  ! is initializable
        class Jcowie\HelloWorld\Model\HelloWorld does not exist.

----  broken examples

        Jcowie/HelloWorld/Model/HelloWorld
  10  ! is initializable
        class Jcowie\HelloWorld\Model\HelloWorld does not exist.


1 specs
1 examples (1 broken)
19ms

  Do you want me to create `Jcowie\HelloWorld\Model\HelloWorld` for you?
                                                                         [Y/n]
Y
Class Jcowie\HelloWorld\Model\HelloWorld created in /Users/jamescowie/Projects/Magento2/magento2-docker-compose/src/app/code/Jcowie/HelloWorld/Model/HelloWorld.php.


      Jcowie\HelloWorld\Model\HelloWorld

  10  ✔ is initializable


1 specs
1 examples (1 passed)
16ms

Great so looking at the output PHPSpec has found that the class HelloWorld did not exist under app/code and it has created this for us. Lets write our first spec also known as a unit test. Open up the spec file and add in:

/**
<?php
class HelloWorldSpec extends ObjectBehavior
{
     * should return hello world
     */
    function it_should_return_hello_world()
    {
        $this->sayHello()->shouldReturn('Hello World');
    }
}

To find out more about PHPSpec and how we write specs Allan MacGregor has a great MageCast on this. But what we are saying is that when we call the method sayHello it should return us Hello World.

Running PHPSpec now ( ./bin/phpspec r) we should see some more information:

phpspec run                                                                                                                                                    ⏎ master ✚ ✱ ◼

      Jcowie\HelloWorld\Model\HelloWorld

  10  ✔ is initializable
  18  ! should return hello world
        method Jcowie\HelloWorld\Model\HelloWorld::sayHello not found.

----  broken examples

        Jcowie/HelloWorld/Model/HelloWorld
  18  ! should return hello world
        method Jcowie\HelloWorld\Model\HelloWorld::sayHello not found.


1 specs
2 examples (1 passed, 1 broken)
25ms

  Do you want me to create `Jcowie\HelloWorld\Model\HelloWorld::sayHello()`
  for you?
                                                                         [Y/n]
Y
  Method Jcowie\HelloWorld\Model\HelloWorld::sayHello() has been created.


      Jcowie\HelloWorld\Model\HelloWorld

  10  ✔ is initializable
  18  ✘ should return hello world
        expected "Hello World", but got null.

----  failed examples

        Jcowie/HelloWorld/Model/HelloWorld
  18  ✘ should return hello world
        expected "Hello World", but got null.


1 specs
2 examples (1 passed, 1 failed)
15ms

Great what is this saying. Well PHPSpec cant find the method in the class for sayHello so it has asked if we want PHPSpec to create it for us. Finally it ran the tests and all failed as the method expected us to return a string but got null. In the Red Green Refactor TDD loop its now time to write some code. Open up the Model class in HelloWorld namespace and to the method return 'Hello World'

Great so now running PHPSpec we have all passing tests but this is no good as its not actually being used in our class. Lets fix that by creating a new Command Class. Add a new folder to app/code/Jcowie/HelloWorldModule for Commands and create GenerateCommand class with the following contents:

<?php
namespace Jcowie\HelloWorldModule\Commands;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;

class GeneratorCommand extends Command
{
    /** @var \Jcowie\HelloWorld\Model\HelloWorld $helloWorldModel */
    private $helloWorldModel;

    /**
     * GeneratorCommand constructor.
     * @param \Jcowie\HelloWorld\Model\HelloWorld $helloWorld
     */
    public function __construct(\Jcowie\HelloWorld\Model\HelloWorld $helloWorld)
    {
        $this->helloWorldModel = $helloWorld;
        parent::__construct();
    }

    protected function configure()
    {
        $this->setName('jcowie:helloworld');
        $this->setDescription('Hello World');

        parent::configure();
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln($this->helloWorldModel->sayHello());
    }
}

Most of this is just boiler plate Symfony console command code. What is interesting to us is the __construct() function where we are injecting our dependency in

/** @var \Jcowie\HelloWorld\Model\HelloWorld $helloWorldModel */
private $helloWorldModel;

/**
 * GeneratorCommand constructor.
 * @param \Jcowie\HelloWorld\Model\HelloWorld $helloWorld
 */
public function __construct(\Jcowie\HelloWorld\Model\HelloWorld $helloWorld)
{
    $this->helloWorldModel = $helloWorld;
    parent::__construct();
}

Pretty simple stuff but we inject into our class a instance of our plain ol PHP class that has the single method and assign this into a variable so that when we output data to the CLI we can call sayHello without having to create a new instance of the class. This makes testing of classes so much easier as we can if wanted test this Command class by replacing what could be more complex in the dependency injected. E.g. if its a remote webservice that we are calling. Yet I would consider this to be more of a integration test that a unit test.

To complete the module we need to add a final piece of M2 configuration that is the DI.xml file and this is used to register our command on the command registration stack

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Console\CommandList">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="moduleCommand" xsi:type="object">Jcowie\HelloWorldModule\Commands\GeneratorCommand</item>
            </argument>
        </arguments>
    </type>
</config>

All that is worth noting here is that we inject into moduleCommand the reference to our GeneratorCommand so that when someone runs bin/magento our command can be added to this list. I will look at exploring the DI in lots more posts and videos soon.

What I want you to take from this most is that its really easy to de couple code from Magento 2 and create more testable objects that are not constrained by the limitations of the framework. This helps us think about Unit testing, Integration testing and Functional testing in new ways as we can have a clear seperation of our domain an inject that into smaller controllers and framework containers.