Categories


Archives


Recent Posts


Categories


N98-magerun: Development Environment

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

Today we’re going to run through setting up your own n98-magerun development environment. This will allow you to develop your own features and contribute bug fixes back to the main repository. This one’s a little longer than our normal Magento Quickies fare, so if you’re in a hurry you may want to save this for later.

There’s two (or maybe three) extra software packages you’ll need to install if you want to work with the n98-magerun source code. The first is PHP Composer, the second is the phing build system.

Composer

Composer is a software package for managing project dependencies. The n98-magerun program relies on many third party packages for its functionality. Rather than include these in the main source tree, n98-magerun includes a top level vendor/ folder. This is where third party dependencies are downloaded to.

This is a standard best practice in profesional software development. By separating your vendor packages from the main source tree, you make upgrading the packages easier. You also encourage/force a “no hacking the vendor package” discipline on your team.

There’s instructions for installing Composer on their website. The short version is there’s a composer.phar (similar to n98-magerun.phar) archive that needs to be in your shell path and needs to be executable. The composer team has a “download install script with curl and run it” one liner, or you may download the phar file directly and place it in your path.

You can test if composer is installed correctly by running the following

$ composer.phar --version
Composer version 0209bd31a0ac3aeb2a68fc81e2d03c71072bef33

Phing

Phing is a PHP build system based on apache ant. For The Kids™ in the audience, ant is a system that was created to give developers a “pure java” build system (as opposed to the powerful-but-tortuous options of the day like GNU make). Phing, in turn, is a PHP version of ant.

Phing implements an XML based build language that allows you to perform common build tasks. This XML format is an example of a Domain Specific Language (DSL). Folks using Magento’s layout system may get a sense of Déjà vu.

Installing phing is slightly trickier than installing PHP composer. The preferred way of installing phing is via PHP PEAR. PEAR (the third package we mentioned above) is PHP’s original dependencies and package management system. While powerful, it never became consistently adopted by the disperate PHP communities. A side effect of this is many PHP distributions don’t have PEAR properly installed, or have it only available to users with sudo permission.

If a fully operational PEAR isn’t available to you, phing is also available via a package download, as well as a stand alone phar file.

Regardless how you install it, at the end of the day you’ll have a phing (or phing.phar or phing-latest.phar) command executable from your shell.

$ phing -v
Phing 2.5.0

Downloading Dependencies with Composer

Now that our tools are in place, we’re ready to start. Step 1 for working with n98-magerun is to get the codebase from GitHub.

$ git clone https://github.com/netz98/n98-magerun.git

However, even after you’ve cloned the git source tree, you’re still missing a number of files n98-magerun needs to function. If you look in the vendor folder, you’ll see it’s empty

$ ls -la vendor/
total 16
drwxr-xr-x   3 alanstorm  staff   102 Apr 26 14:40 .
drwxr-xr-x  26 alanstorm  staff   884 Apr 25 23:24 ..

As mentioned earlier, the vendor folder is where third party code libraries are kept. To download these files, run the composer.phar install command from the root folder of your repository.

$ cd n98-magerun
$ composer.phar install
Loading composer repositories with package information
Installing dependencies from lock file
  - Installing symfony/process (v2.2.1)
    Loading from cache

  - Installing symfony/finder (v2.2.1)
    Loading from cache

  - Installing symfony/console (2.2.x-dev a10a2fb)
    Cloning a10a2fb156267b9b8f01d717a06979e6ebb69251

  - Installing seld/jsonlint (1.1.1)
    Loading from cache

  - Installing justinrainbow/json-schema (1.1.0)
    Loading from cache

  - Installing composer/composer (dev-master c655674)
    Cloning c65567447c84eb6a8c0f229c40b0865cd4dcecb5

  - Installing fzaninotto/faker (v1.1.0)
    Loading from cache

  - Installing n98/junit-xml (dev-master 6381433)
    Cloning 6381433a39498dc22bec60cebdb2576a9d2327b0

  - Installing symfony/translation (v2.2.1)
    Loading from cache

  - Installing symfony/validator (v2.2.1)
    Loading from cache

  - Installing symfony/yaml (v2.2.1)
    Loading from cache

symfony/translation suggests installing symfony/config (2.2.*)
symfony/validator suggests installing doctrine/common (~2.2)
symfony/validator suggests installing symfony/config (2.2.*)
symfony/validator suggests installing symfony/http-foundation (2.2.*)
symfony/validator suggests installing symfony/locale (2.2.*)
Generating autoload files

If you take a look at your vendor folder after running the install command, you’ll see the required vendor packages have been installed.

$ ls -1a vendor/
.
..    
autoload.php
bin
composer
fzaninotto
justinrainbow
n98
seld
symfony

Of course, this raises a few questions: How does composer.phar know which packages to download? And where are these packages coming from? While this knowledge isn’t strictly required to proceed, we’re going to cover it to put your (and our) minds at ease.

The composer.phar command looks for a file named composer.json. This file contains configuration information for a composer based project (which n98-magerun is). This configuration information will include a list of package dependencies

//File: composer.json
"require": {
    "php": ">=5.3.3",
    "symfony/console": "2.2.x-dev",
    "symfony/finder": "2.2.1",
    "symfony/yaml": "2.2.1",
    "symfony/process": "2.2.1",
    "symfony/validator": "2.2.1",
    "justinrainbow/json-schema": "1.1.*",
    "seld/jsonlint": "1.*",
    "n98/junit-xml": "dev-master",
    "fzaninotto/faker": "1.1.0",
    "composer/composer": "dev-master"
},

As you can see, the require key contains a list of packages and versions. These package names correspond with the downloaded packages from the install command. So this answers the “how does composer know” question. Before we move on the “where” question, it’s worth pointing out that composer is actually reading from the composer.lock file. This composer.lock file is generated with the information in composer.json, and is not meant to be human editable. If you want to get into the nitty gritty of this checkout the composer manual.

The “from where” question is a little trickier. There’s two grand arcs to the composer project. The first is the composer.phar application we downloaded, which contains the logic for dependency management. The second is a composer repository at packagist.org. Unless a different repository is listed in composer.json, packagist.org is where composer.phar will grab package information from.

Does that mean composer is downloading packages from packagist.org? No. Confused? Read on!

A composer repository only contains meta-data about a package. This meta-data includes a source code repository where the package source can be found. The packagist.org repository simply tells composer.phar where it should download the package source from.

Figuring out where composer.phar is grabbing packages from is a two step process. First, you can list out all the packages used in your project with the show command and and --self flag.

$ composer.phar show --self
name     : n98/magerun
descrip. : Tools for managing Magento projects and installations
keywords : magento, installer
versions : * dev-master
type     : library
license  : MIT
source   : []  
dist     : []  
names    : n98/magerun

support
issues : https://github.com/netz98/n98-magerun/issues

autoload
psr-0
N98 => src
Composer => src
Xanido => src

requires
php >=5.3.3
symfony/console 2.2.x-dev
symfony/finder 2.2.1
symfony/yaml 2.2.1
symfony/process 2.2.1
symfony/validator 2.2.1
justinrainbow/json-schema 1.1.*
seld/jsonlint 1.*
n98/junit-xml dev-master
fzaninotto/faker 1.1.0
composer/composer dev-master

requires (dev)
phpunit/phpunit 3.7.19
phpunit/phpunit-story 1.0.2
phpunit/phpunit-selenium 1.2.12
phpunit/dbunit 1.2.3
phpunit/php-invoker 1.1.2

Once you have a package name (such as symfony/yaml), you can see its repository by using the show command again, this time with the package name as an argument.

$ composer.phar show symfony/yaml
name     : symfony/yaml
descrip. : Symfony Yaml Component
keywords : 
versions : dev-master, 2.3.x-dev, 2.2.x-dev, * v2.2.1, v2.2.0, v2.2.0-RC3, v2.2.0-RC2, v2.2.0-RC1, v2.2.0-BETA2, v2.2.0-BETA1, 2.1.x-dev, v2.1.9, v2.1.8, v2.1.7, v2.1.6, v2.1.5, v2.1.4, v2.1.3, v2.1.2, v2.1.1, v2.1.0, v2.1.0-RC2, v2.1.0-RC1, v2.1.0-BETA4, v2.1.0-BETA3, v2.1.0-BETA2, v2.1.0-BETA1, 2.0.x-dev, v2.0.23, v2.0.22, v2.0.21, v2.0.20, v2.0.19, v2.0.18, v2.0.17, v2.0.16, v2.0.15, v2.0.14, v2.0.13, v2.0.12, v2.0.10, v2.0.9, 2.0.7, 2.0.6, 2.0.5, 2.0.4
type     : library
license  : MIT
source   : [git] https://github.com/symfony/Yaml.git v2.2.1
dist     : [zip] https://api.github.com/repos/symfony/Yaml/zipball/v2.2.1 v2.2.1
names    : symfony/yaml

autoload
psr-0
SymfonyComponentYaml => .

requires
php >=5.3.3

The two sections we’re interested in here are source and dist. The source lists the repository where the source code should be downloaded from, (git, svn, and hg repository types are supported). This is how composer.phar will grab the needed vendor packages.

The archive (as opposed to repository) at dist can be used to install a package instead, but only if the --prefer-dist flag is used with the install command.

If you’re paranoid about security, you’ll want to review these package locations — although at some point using a distributed packaging system like CPAN, ruby gems, or packagist requires a certain level of trust. That, however, is another subject better left to another time.

Composer Development Packages

There’s one last thing we should cover before moving on from composer. If you examined the composer.json file carefully, you may have noticed this section

//File: composer.json
"require-dev": {
    "phpunit/phpunit": "3.7.19",
    "phpunit/phpunit-story": "1.0.2",
    "phpunit/phpunit-selenium": "1.2.12",
    "phpunit/dbunit": "1.2.3",
    "phpunit/php-invoker": "1.1.2"
},

This appears to be a second list of packages. It turns out composer projects can have two sets of required packages. The first is the standard distribution you’ll need to use the package. The second, require-dev, is an additional set of packages you’ll need to work on (or develop) the project.

Since this whole article is about building our own version of n98-magerun.phar, we’ll want to install these development packages. Let’s run composer.phar with the --dev flag.

$ composer.phar install --dev
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for phpunit/php-invoker 1.1.2 -> satisfiable by phpunit/php-invoker 1.1.2.
    - phpunit/php-invoker 1.1.2 requires ext-pcntl * -> the requested PHP extension pcntl is missing from your system.

Uh oh. At this point, you may (as we did above) run into an intractable problem. The phpunit/php-invoker package couldn’t be installed because it requires that PHP have the process control (ext-pcntl) extension installed. This extension is omitted from many standard PHP distributions.

We’ve now run into what I like to call the Software is Hard problem. In order to install these dependencies, we’d need to recompile our version of PHP. That’s something many people can’t, or won’t do. Unless we’re willing to make a massive change to our environment, we won’t be able to install the full suite of required phpunit tools.

On the flip side, we could remove phpunit/php-invoker as a requirement, and then successfully install the other development packages. However, this would mean we’re veering off from what the main team is doing. Our system may start to behave differently than theirs, causing subtle and hard to track down compatibility problems in the future.

There’s going to be times in your career where you run into problems where there is no ideal solution. The only option is to raise your concerns, and then press on with the less than ideal conditions. The trick to a long career is finding a way to not let these small defeats sap your motivation for other, more important tasks.

Fortunately, it’s still possible to work on n98-magerun code without the full phpunit suite, so we’ll carry on without it.

Update: From the sounds of this GitHub thread, the phpunit/php-invoker requirement will be dropped in the 1.64 release, so the previous whinging may not apply if you’re reading this in the future.

Phing

Now that we’ve discussed composer, our next step is using phing to build the project. Take a look at the build.xml file in the root of the project

<!-- File: build.xml -->
<project name="n98-magerun" default="dist">

    <fileset dir="." id="root_folder"><!-- ... --></fileset>
    <fileset dir="src"  id="src_folder"><!-- ... --></fileset>
    <fileset dir="res" id="res_folder"><!-- ... --></fileset>
    <fileset dir="vendor" id="vendor_folder"><!-- ... --></fileset>

    <target name="dist">
        <!-- ... -->
    </target>

    <target name="dist_unix">
        <!-- ... -->
    </target>

    <target name="install">
        <!-- ... -->
    </target>

</project>

This is our phing build file. It contains all the build instructions for our package. We’re interested in the <target/> nodes. Each <target/> node defines a single build command. For example, the install command can be used to install the n98-magerun.phar from the repository to your local computer. This is a relatively simple build target.


<target name="install">
    <exec command="sudo cp ${project.basedir}/n98-magerun.phar /usr/local/bin/n98-magerun.phar;" />
    <exec command="sudo chmod a+x /usr/local/bin/n98-magerun.phar;" />
</target>

As you can see, there’s two <exec/> nodes within this target. These are command nodes — think of each one like you would a single line in a normal programming language or shell script. The <exec/> command is one of the simplest build commands — it simply runs a unix shell script.

Let’s try running the install target.

$ phing install
Buildfile: /path/to/n98-magerun/build.xml

n98-magerun > install:

Password:
Sorry, try again.
Password:

BUILD FINISHED

Total time: 5.9880 seconds

As you can see, to run a phing command you simply pass it the target name. The phing command will look for a build.xml file in the current directory, and then run the target you’ve passed in.

As for the command output itself, it prompts you for a password (since it’s running the sudo command), and the finishes the build. Unfortunately, there’s no indication of what the command actually did. If you’d like a little more feedback, try the verbose flag.

$ phing -verbose install
Buildfile: /path/to/n98-magerun/build.xml
Override ignored for user property phing.file
Override ignored for user property phing.dir
parsing buildfile build.xml
Project base dir set to: /path/to/n98-magerun
Build sequence for target 'install' is: install 
Complete build sequence is: install dist dist_unix 

n98-magerun > install:

Property ${project.basedir} => /path/to/n98-magerun
     [exec] Executing command: sudo cp /path/to/n98-magerun/n98-magerun.phar /usr/local/bin/n98-magerun.phar; 2>&1
     [exec] Executing command: sudo chmod a+x /usr/local/bin/n98-magerun.phar; 2>&1

BUILD FINISHED

Total time: 0.1772 seconds

With the verbose flag, phing tells you everything it’s doing. From this we can figure out a file has been copied to /usr/local/bin/n98-magerun.phar. This places the phar in our *nix path, and is another way to install the n98-magerun.phar command.

Building the Project

Most phing build files have a default target which builds the project.

If you run phing with the -l option

$ phing -l
Buildfile: /path/to/n98-magerun/build.xml
Default target:
-------------------------------------------------------------------------------
 dist

Subtargets:
-------------------------------------------------------------------------------
 dist
 dist_unix
 install

you’ll get a list of each target in the application, as well as the default target (in this case, dist). You can also see the default target by looking at the root node of the build.xml file.

<!-- build.xml -->
<project name="n98-magerun" default="dist">

We’re going to run through a default build of the n98-magerun tool, including several problems we ran into and how to work around them. You may not run into these specific problems — and you may run into different problems not mentioned here. Setting up a build environment is always a trial and error process, and things will rarely work out of the gate. Part of the reason teams use a tool like phing is to provide a single entry point into their build system and force developers to download and install the same dependencies. If you run into trouble getting this up and running, the Magento Stack Exchange is a great place to ask questions to help get your system up and running.

To run the default build step, just run phing from the project root directory with no arguments

$ phing

The first thing you’ll see is phing going through the project dependencies and downloading the needed files to the vendor folder.

$ phing
Buildfile: /path/to/n98-magerun/build.xml

n98-magerun > dist:

Loading composer repositories with package information
Installing dependencies from lock file
  - Installing symfony/process (v2.2.1)
    Loading from cache

  - Installing symfony/finder (v2.2.1)
    Loading from cache

... snipped ...

If you’ve already downloaded these files with the composer.phar install command, these downloads will be skipped. The output for that looks like this

$ phing
Buildfile: /private/tmp/building/n98-magerun/build.xml

n98-magerun > dist:

Loading composer repositories with package information
Installing dependencies from lock file
Nothing to install or update
Generating autoload files

If you look at the build.xml file, you can see all this happens because the command in the dist target runs the composer install command.

<target name="dist">
    <exec command="composer.phar install" dir="${project.basedir}" passthru="true" />

So, it would be more accurate to say phing still attempts to update your dependencies, but if the latest versions are already downloaded composer will skip re-downloading them.

Writeable PHAR Archives

After phing runs the composer.phar command, it moves on to the next build steps, and (for some of you) will hit another error.

Execution of target “dist_unix” failed for the following reason: /path/to/n98-magerun/build.xml:45:65: Problem creating package: creating archive “/path/to/n98-magerun/n98-magerun.phar” disabled by the php.ini setting phar.readonly

Here we’ve discovered a constraint of PHP’s phar implementation. By default, PHP won’t let you edit an existing phar archive. While annoying, if you consider that a phar archive is meant to “emulate jars and dlls”, this makes sense. Allowing a program to modify its libraries would be a recipe for disaster.

The problem, of course, is that it’s PHP code that creates a phar archive. In order to solve this chicken/egg problem, PHP has a phar.readonly flag for php.ini. If you set this flag to 0 or Off, PHP will allow you to access the methods which modify a phar archive.

This flag is one you’ll need to set in your php.ini file, it can’t be set at runtime with ini_set. If you’re not sure where your php.ini file is located, try running the following

$ php --ini
Configuration File (php.ini) Path: /etc
Loaded Configuration File:         /private/etc/php.ini
Scan for additional .ini files in: (none)
Additional .ini files parsed:      (none)

Remember, it’s not uncommon for the command line version of PHP to have a different ini file than the web server version of PHP.

BZip Compression Problems

With the above in place, try running the build again. If you’re like me, you’re about to run into another problem

Execution of target “dist_unix” failed for the following reason: /path/to/n98-magerun/build.xml:45:65: Problem creating package: unable to create temporary file [wrapped: unable to create temporary file]

This one is a head scratcher, but I finally tracked down the problem. In short: While PHP’s phar format works across a variety of platforms and environments, the methods for creating phar archives are still very beta-ish. The specific problem here is, when compressing phar archives PHP likes to open a temporary file — and from the sound of things it opens a temporary file for each file in the archive, and doesn’t close them until the command is complete. All that library code means n98-magerun has over 1,000 files.

This quickly hits the limits that a non-server operating system has in place for the number of files each process is allowed to have open. To solve this problem, you’ll need to edit build.xml. Find the following XML node

<!-- File: build.xml -->
<pharpackage basedir="./" stub="_cli_stub.php" signature="sha512" compression="bzip2" destfile="./n98-magerun.phar">    

The pharpackage node is a phing build command that allows you to automatically create a phar application from a PHP codebase. We’re interested in the compression="bzip2" attribute. This tells phing to run the phar archive through the compressFiles method, and is done to reduce the file size of the archive that’s distributed. If you change this attribute to

compression="none"

then phing will skip the compression step, and we should be able to get a complete build.

$ phing
Buildfile: /path/to/n98-magerun/build.xml

n98-magerun > dist:

Loading composer repositories with package information
Installing dependencies from lock file
Nothing to install or update
Generating autoload files
[phingcall] Calling Buildfile '/path/to/n98-magerun/build.xml' with target 'dist_unix'

n98-magerun > dist_unix:

[pharpackage] Building package: /path/to/n98-magerun/n98-magerun.phar
    [chmod] Changed file mode on '/path/to/n98-magerun/n98-magerun.phar' to 775
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file

BUILD FINISHED

Total time: 3.5711 seconds

That looks like a clean build, let’s test out the n98-magerun.phar file that was just created.

$ ./n98-magerun.phar --version
n98-magerun version 1.63.0 by netz98 new media GmbH

Success! However, our n98-magerun.phar file

$ ls -lh n98-magerun.phar
-rwxrwxr-x  1 alanstorm  staff   3.4M Apr 25 15:50 n98-magerun.phar

is over 3 MB in size. Compare that to the 1 MB file distributed in the main github archive. So, while we can’t build exactly what the core n98-magerun team does, we can get a clean build in place with a testable phar, which is good enough for our needs.

While frustrating, you’ll find many points in your career where your development environment won’t match up exactly with that of the team you’re working with. While it’s true (especially among non-distributed teams) that a chaotic build environment can lead to development delays, these delays are subtle and complicated in nature, and not the sort of thing that shows up in ROI metrics. Unfair as it may be, as an individual developer working with a group, it will often fall on you to solve these problems for yourself in a way that doesn’t disrupt team flow.

Wrap Up

Regardless of the subtle incompatibilities, we’ve successfully built the n98-magerun.phar file from source. This means we can start hacking on bugs, as well as adding new features to the tool. Next time we’ll create our very first n98-magerun command.

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 28th April 2013

email hidden; JavaScript is required