Categories


Archives


Recent Posts


Categories


N98-magerun: Creating Hello World

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!

This article is part of a longer series covering the n98-magerun power tool

Now that we’ve got a build environment up and running, we can get to work creating our first n98-magerun command. Our end goal for today is to add a helloworld command to n98-magrun

$ ./n98-magerun.phar list

//...

Available commands:
  helloworld                       Displays a Hello World message. (pedagogical

Development Stub

So far, all our examples have used the n98-magerun.phar bundled application. Therefore, our first step today is to run the application from source code. Every phar archive can have an optional “stub” file. This file is meant to encourage clean coding practices in your phar archive, with the stub acting as a main entry-point into your phar based application. Think of it like you would your index.php bootstrap file in a web application, or a main function in a c program.

The n98-magerun.phar bootstrap file is _cli_stub.php. You might think we could run this ourselves. However, running

$ php _cli_stub.php

will produce the error

PHP Fatal error:  Uncaught exception 'PharException' with message 'internal corruption of phar

Let’s look at the contents of the phar

#!/usr/bin/env php
<?php

Phar::mapPhar('n98-magerun.phar');

$application = require_once 'phar://n98-magerun.phar/src/bootstrap.php';
$application->setPharMode(true);
$application->run();

__HALT_COMPILER();

It turns out that phar stub files are meant to be used from phar archives only. The call to mapPhar, the require using a phar:// url scheme, and the __HALT_COMPILER() call all produce errors if used outside of a phar archive.

We’re going to need to create our own stub file. In the root of your repository, create the following file with the following contents

<?php
$application = require_once realpath(dirname(__FILE__)) . '/src/bootstrap.php';
$application->setPharMode(false);
$application->run();    

This is almost the same code that’s the the phar stub, but written with generic PHP.

Let’s try running this from the command line.

$ php dev-stub.php

     ___ ___
 _ _/ _ ( _ )___ _ __  __ _ __ _ ___ _ _ _  _ _ _
| ' _, / _ ___| '  / _` / _` / -_) '_| || | ' 
|_||_/_/___/   |_|_|___,___, ___|_|  _,_|_||_|
                           |___/
n98-magerun version 1.63.0 by netz98 new media GmbH

Usage:
  [options] command [arguments]

...

Eureka! We’re now running n98-magerun strictly from code. You can make sure everything’s working correctly by navigating to your Magento folder and running a simple command like sys:info.

$ cd /path/to/magento
$ php /path/to/n98-magerun/dev-stub.php sys:info

  Magento System Information  

Version                  : 1.7.0.1
Edition                  : Community
Cache Backend            : Zend_Cache_Backend_File
Cache Directory          : /Users/alanstorm/Sites2012/magento1point7pointzeropoint1.dev/var/cache
Session                  : files
Crypt Key                : 18cea96f92f35a540d83a2fe7f33c005
Install Date             : Sun, 01 Jul 2012 17:06:09 +0000

We’re now ready to start with our helloworld command.

Hello World

To create a new n98-magerun command there’s five basic steps

  1. Register the command

  2. Create the command class

  3. Configure the command name

  4. Implement the command

  5. Build our new command into the phar

By the end of the article, you’ll know how to do all five.

Registering the Command Class and Namespaces

Our first task is to register the command class. Open up the main application file and look at the registerCommands method

#File: src/N98/Magento/Application.php
//...    
class Application extends BaseApplication
{    
    //...
    protected function registerCommands()
    {
        $this->add(new GenerateLocalXmlConfigCommand());
        $this->add(new DatabaseDumpCommand());
        $this->add(new DatabaseDropCommand());        
        //...
    }
    //...    
}    

The n98-magerun team made use of Symfony’s console package as the basis for their application. A Symfony console application’s add method allows client programmers to tell their application about a command, which in turn exposes it to end users of the console application.

A few of you may be wondering why this code uses such generic class names. The name Application, and BaseApplication seem like things that could be used in other code bases — so why did n98-magerun choose Application and why did Symfony choose BaseApplication?

The short answer is namespaces. If you look at the top of this file

#File: src/N98/Magento/Application.php
namespace N98Magento;
//...

you’ll see we’re working in the N98Magerun namespace. That means this application class’s full name is actually

N98MagentoApplication

Similarly, further down in the file, you can see (nestled in among many similar calls)

use SymfonyComponentConsoleApplication as BaseApplication;

This aliases the class with the full name of

SymfonyComponentConsoleApplication

as the class BaseApplication in this file.

PHP namespace programming can be confusing if you’re not used to it. Even if you are used to it, the lack of a gold standard in how to approach namespaces can leave you feeling as though you’re flailing about. While this isn’t necessarily a namespace tutorial, we’ll try to explain how and why each classes interacts in this brave new namespaced world.

The namespace detour completed, let’s add our command! Add the following line to the registerCommands method.

#File: src/N98/Magento/Application.php
//...    
class Application extends BaseApplication
{    
    //...
    protected function registerCommands()
    {
        $this->add(
            new HelloWorldCommand()
        );
        //...
    }
}

What we’ve done here is pass in an object, instantiated from the class HelloWorldCommand. Let’s run our command with this in place

$ php /path/to/n98-magerun/dev-stub.php    
PHP Fatal error:  Class 'N98MagentoHelloWorldCommand' not found    

A fatal error! PHP is complaining it can’t find a class named HelloWorldCommand. Actually, it’s complaining it can’t find a class named N98MagentoHelloWorldCommand since we’re working in the N98Magento namespace. Regardless, this make sense since we haven’t actually created our class yet. Let’s get to that.

Creating the Class

Each command in n98-magerun corresponds to a single PHP class file. These files are, by convention, created in the src/N98/Magento/Command folder.

If you list out the contents of this folder, you’ll see a number of classes and directories

$ ls -1 src/N98/Magento/Command
AbstractMagentoCommand.php
AbstractMagentoStoreConfigCommand.php
Admin
Cache
Cms
Config
ConfigurationLoader.php
Customer
Database
Design
Developer
Indexer
Installer
LocalConfig
MagentoConnect
OpenBrowserCommand.php
PHPUnit
ScriptCommand.php
SelfUpdateCommand.php
ShellCommand.php
System

We’re going to create a new class file in this folder. The n98-magerun autoloader expects file names and class names to match, so create the following file at the following location

#File: src/N98/Magento/Command/HelloWorldCommand.php
<?php
namespace N98MagentoCommand;    
use N98MagentoCommandAbstractMagentoCommand;

class HelloWorldCommand extends AbstractMagentoCommand
{
}    

The first line of this file puts us in the

N98MagentoCommand

namespace. That means the full name for the HelloWorldCommand class we’ve declared is actually N98MagentoCommandHelloWorldCommand. You’ll notice the namespace convention matches the file path convention.

N98/Magento/Command/HelloWorldCommand.php
N98MagentoCommandHelloWorldCommand

Without getting too deeply into it, the autoloader depends on this, so make sure you stick to this convention.

Our HelloWorldCommand class extends the AbstractMagentoCommand class. This is the abstract class that all n98-magerun commands inherit from. The full name of this class is actually

N98MagentoCommandAbstractMagentoCommand

We’re able to refer to it as AbstractMagentoCommand since the following code was used at the top of our file

use N98MagentoCommandAbstractMagentoCommand;

If we run our application with the above class in place

$ php /path/to/dev-stub.php
Class 'N98MagentoHelloWorldCommand' not found

We’re still getting the same error. That’s because our register function is trying to load a class in the current namespace of the Application file. If we change our add method call so it uses the full class name

#File: src/N98/Magento/Application.php
//...    
class Application extends BaseApplication
{    
    //...
    protected function registerCommands()
    {
        $this->add(
            new N98MagentoCommandHelloWorldCommand()
        );
    }
}

we should be good to go. Notice the leading \ on the class name. This tells PHP to start looking from the global namespace, (vs. looking for a deeper namespace, starting at the current one) .

If we run our stub with the above in place

$ php /path/to/dev-stub.php
PHP Fatal error:  Uncaught exception 'LogicException' with message 'The command name cannot be empty.

Sort of Eureka! We’ve gotten rid of the class not found error, and replaced with with a new “the command name cannot be empty” error. That’s progress of a sort, and brings us to step 3, configure the command name.

Update: Alexander Menk got in touch to point out the project ships with a stub file that works in a PHP (vs. phar) environment. We regret the error — feel free to substitute this file for your own.

Configuring the Command

Every command has a configure method which is called automatically. This is where we’ll want to assign our command a name. Add the following method to your command class

#File: src/N98/Magento/Command/HelloWorldCommand.php
<?php
namespace N98MagentoCommand;    
use N98MagentoCommandAbstractMagentoCommand;

class HelloWorldCommand extends AbstractMagentoCommand
{
    protected function configure()
    {
        $this->setName('helloworld');

    }    
}    

With the above in place, lets run our command again

$ php /path/to/dev-stub.php
     ___ ___
 _ _/ _ ( _ )___ _ __  __ _ __ _ ___ _ _ _  _ _ _
| ' _, / _ ___| '  / _` / _` / -_) '_| || | ' 
|_||_/_/___/   |_|_|___,___, ___|_|  _,_|_||_|
                           |___/
n98-magerun version 1.63.0 by netz98 new media GmbH

Usage:
  [options] command [arguments]

Options:
  --help           -h Display this help message.
  --quiet          -q Do not output any message.
  --verbose        -v Increase verbosity of messages.
  --version        -V Display this application version.
  --ansi              Force ANSI output.
  --no-ansi           Disable ANSI output.
  --no-interaction -n Do not ask any interactive question.

Available commands:
  helloworld                       
  help                             Displays help for a command

Finally! We’ve got a clean run. Also, if you’re playing close attention, you’ll notice that helloworld is now listed as an available command.

Available commands:
  helloworld                       
  help                             Displays help for a command

If you want to give your command a description, just call the setDescription method in configure

#File: src/N98/Magento/Command/HelloWorldCommand.php
protected function configure()
{
    $this->setName('helloworld');
    $this->setDescription('Displays a Hello World message. (pedagogical)');
}

Give the stub another run, and you should see your command description.

$ php /path/to/dev-stub.php
...
Available commands:
  helloworld                       Displays a Hello World message. (pedagogical)

Notice that the text we included in the “ tag has been automatically made into a parenthetical.

Implementing our Command

We’ve got our stub running clean again — lets press our luck and try calling our helloworld command.

$ php /path/to/dev-stub.php helloworld

  [LogicException]                                                       
  You must override the execute() method in the concrete command class.  

Drats! At least we’ we’ve replaced our gross PHP errors with pretty Symfony errors.

Every command in a Symfony console application needs an execute method. The execute method is where we implement our command logic. To create this method, add the following to your HelloWorldCommand class

#File: src/N98/Magento/Command/HelloWorldCommand.php
<?php
//...
class HelloWorldCommand extends AbstractMagentoCommand
{
    //...        

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln("Hello World!");
    }        
}    

As you can see, we’ve added an execute method to our class. The method has two arguments. The first, $input, allows you to access arguments and options passed to your command. The second, $output, allows you to send feedback to the users of your command. You can also see we’ve called the writeln method of the output object to print out our ubiquitous Hello World! message. Let’s give the command a final run and call it a day.

$ php /path/to/dev-stub.php helloworld
PHP Catchable fatal error:  Argument 1 passed to N98MagentoCommandHelloWorldCommand::execute() must be an instance of N98MagentoCommandInputInterface, instance of SymfonyComponentConsoleInputArgvInput given    

Crud. That would have been too easy, wouldn’t it have? PHP is complaining that the first argument to execute failed to match the type safety check. PHP was expecting a N98MagentoCommandInputInterface, but was provided with a SymfonyComponentConsoleInputArgvInput. Again, namespaces rear their hydra like heads.

The InputInterface interface we want is actually SymfonyComponentConsoleInputInputInterface. Instead of changing the type check in front of the parameter, let’s import SymfonyComponentConsoleInputInputInterface and SymfonyComponentConsoleOutputOutputInterface into the current namespace. Add the following lines just below the namespace declaration.

#File: src/N98/Magento/Command/HelloWorldCommand.php
namespace N98MagentoCommand;

use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;    
//...

With the above in place, we should be able to run our command.

$ php ~/Documents/github_netz98/n98-magerun/dev-stub.php helloworld
Hello World!

There we have it, another Hello World example program added to the world.

Following Conventions

Before we finish things off by compiling our new code into a phar, let’s jump back to our add method call.

#File: src/N98/Magento/Application.php
//...
$this->add(
    new N98MagentoCommandHelloWorldCommand()
);

$this->add(new GenerateLocalXmlConfigCommand());    

While this code works, it’s not keeping with the conventions setup by the netz98 team. When they add a command object, they’re not using the full qualified namespace, they’re just using simple class names like GenerateLocalXmlConfigCommand. How do they get away with this?

If you search this file for GenerateLocalXmlConfigCommand, you’ll find your answer. Near the top of the file is the following line

#File: src/N98/Magento/Application.php
use N98MagentoCommandLocalConfigGenerateCommand as GenerateLocalXmlConfigCommand;    

Using the use operator, the fully qualified N98MagentoCommandLocalConfigGenerateCommand class is being imported into the local namespace as GenerateLocalXmlConfigCommand. Let’s bring our class into the local namespace. Add the following right above the GenerateLocalXmlConfigCommand line

#File: src/N98/Magento/Application.php
//our code
use N98MagentoCommandHelloWorldCommand;

//their code
use N98MagentoCommandLocalConfigGenerateCommand as GenerateLocalXmlConfigCommand;    

and then change your call to add

$this->add(new HelloWorldCommand());

Your command should behave exactly the same, but now you’re matching the current coding conventions used in the project. Some people might write this off as bike shedding, but a consistant codebase helps with team cohesion, and allows new programmers a stable base to learn the fundamentals of a project without having to worry about parsing multiple different coding styles. This is particularly true in the PHP world, since "idiomatic PHP” varies from project to project.

Building a New phar

The only thing left is to build a new phar archive. As we learned last time, our build script is just a single call to

$ phing
...
BUILD FINISHED

Total time: 8.2973 seconds away.  

After running phing, make sure a new phar was created with today’s date and time (May XX XX:XX below)

$ ls -l n98-magerun.phar 
-rwxrwxr-x  1 alanstorm  staff  3617497 May XX XX:XX n98-magerun.phar

Then, try running your command

$ ./n98-magerun.phar helloworld
Hello World!

Congratulations, you’ve just created and built your first n98-magerun command.

Copyright © Alan Storm 1975 – 2019 All Rights Reserved

Originally Posted: 1st May 2013