This article (and the accompanying talk) exist to teach you enough about Composer to get a few things done.
This topic was a talk I gave at DrupalCamp LA 2014: https://2014.drupalcampla.com/sessions/just-enough-composer-be-dangerous Unfortunately, the recording didn't work. But don't despair, you can come to PNW Drupal Summit 2014 and see it again: http://2014.pnwdrupalsummit.org/pacific-nw-drupal-summit-2014/sessions/enough-composer-be-dangerous
This is a beginner level article for Composer, but you'll need to be able to follow along in bash to see what's happening.
There are plenty of circumstances where Composer can help us manage our dependencies... I'm going to walk through making a project with only one dependency. And one dev dependency, but don't let that confuse you just yet.
Some technical requirements for this article:
- Unix/bash: This article assumes you have a Unix-flavored bash shell environment. I'm not sure how to map any of this to Windows.
-
PHP: You should have PHP 5.3.2 or greater installed. I'm using a Mac running OS X 10.9, which comes with sufficient PHP. You should be able to open a terminal, type
which php
, and get a result. -
curl: You should have
curl
. We only use it once, but it's kind of important. - Internet: You'll need a connection to the internet. Duh.
-
Editor: I'm using
nano
as a text editor in this article. It's easy to use, and easy for people to see what's going on in a presentation. Useemacs
orvi
or NetBeans or Coda or PHPStorm or whatever if you'd like.
Let's begin.
Some Definitions
Composer
Composer is a command-line tool to help you manage dependencies for PHP projects.
You need to add some frameworks to your project? Use Composer.
Composer also manages autoloading for the various dependencies, as well as for namespaces your project.
You can get it here: http://getcomposer.org/
Packages
The unit of measure for Composer is a 'package.'
Each package has a composer.json
file which contains all the information Composer will use to manage the package and its dependencies.
Packagist.org
You can list your package on packagist.org. This site is where Composer looks to find packages you'll use in your project.
Install Composer
Composer installs as a single file which can be anywhere in your filesystem.
$ curl -sS https://getcomposer.org/installer | php
This downloads a file called composer.phar
which you can run like this:
$ php ./composer.phar
We want a more global installation, so let's make a ~/bin
directory and put Composer there.
$ mkdir ~/bin
$ mv composer.phar ~/bin/composer
bash still can't find it though, which we can prove by typing which composer
.
Let's tell bash about our new path:
$ nano .profile
Add PATH=/Users/[user]/bin:$PATH
to .profile
. A similar line might already exist in your .profile
file, and you'll have to figure out where to add your new path. Also, this is how you do it on Mac OS X; you'll need to learn how to add a directory to your search path on other systems.
$ source .profile
We have to tell bash to use this new .profile
, and the source
command is how we do it.
After we do all this, which composer
should show us the location where we just put Composer.
Spec Our Project
Before we go much further we really should spec out our project. We always do that first, right? Design first, code second? Yes? :-)
We want a simple CLI tool that does this:
- Says 'hello' back to us.
We'll use Symfony/Console for various reasons:
- Easy to demo.
- I repeat: Really easy to demo. We'll be using the GreetCommand source code from this page: http://symfony.com/doc/current/components/console/introduction.html
- Allows us to demo PSR-4 autoloading for a single file.
So let's start:
$ mkdir ~/composer_demo
$ cd ~/composer_demo
Start Using Composer Already!
So let's prove to ourselves that the project directory really is empty:
$ ls -al
Let's add the console framework:
$ composer require symfony/console:~2.5
Watch Composer do its thing...
$ composer require symfony/console:~2.5
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing symfony/console (v2.5.3)
Loading from cache
symfony/console suggests installing symfony/event-dispatcher ()
symfony/console suggests installing psr/log (For using the console logger)
Writing lock file
Generating autoload files
Look at directory contents. Not empty any more.
$ ls -al
total 16
drwxr-xr-x 5 paulmitchum staff 170 Sep 1 12:37 .
drwxr-xr-x 23 paulmitchum staff 782 Sep 1 10:47 ..
-rw-r--r-- 1 paulmitchum staff 61 Sep 1 12:37 composer.json
-rw-r--r-- 1 paulmitchum staff 2438 Sep 1 12:37 composer.lock
drwxr-xr-x 5 paulmitchum staff 170 Sep 1 12:37 vendor
What do all these files do?
composer.json
Look at composer.json
:
$ more composer.json
{
"require": {
"symfony/console": "~2.5"
}
}
We see that Composer has added symfony/console as a requirement.
composer.lock
What's that composer.lock
file?
$ more composer.lock
While it's working out all the web of dependencies for all the requirements, Composer collects a bunch of information. It stores that information in the lock file, so it doesn't have to look it all up again.
If you say composer update
, it will work through the requirements gathering again.
If you say composer install
, however, it will use the lock file in order to avoid having to re-discover the dependency tree.
vendor/
What's in the vendor/
directory?
$ cd ~/composer_demo/vendor
$ ls
You'll see autoload.php
which is the second most important file in your Composer-based project. We'll talk about it later.
You'll see the composer
directory, which is where Composer stores some stuff, such as the classmap autoload implementation.
You'll also see vendor directories. In our case, symfony/
. If you drill down into symfony/
you'll see console/
. Note that we required symfony/console
, so the directory structure matches the vendor/package naming.
Packagist.org: Santa Claus For PHP
Where does Composer find these packages, such as symfony/console?
Here's the listing for symfony/console: https://packagist.org/packages/symfony/console
All of the packages on packagist.org are based on their composer.json
file. There are some considerations for distributing a package this way, but that's outside the scope of this article.
Suffice it to say it's very easy to list your framework on Packagist.org, and it's also very easy to find what you need there, too.
Validating Our Composer.json File
Back to our project...
$ cd ~/composer_demo
$ composer validate
Composer barks at us for not having a name or license.
We could just edit composer.json
, but let's get radical:
$ rm -rf *
For the purposes of this demo, deleting all your work is a good idea. It really isn't. :-)
$ composer init
This walks us through various options for our composer.json
file.
Enter 'GPL-2.0+' for license. Specify stable
for minimum stability.
Answer 'no' on entering our requirements and dev-requirements; we'll do that later.
Answer 'yes' to generate a new composer.json
file.
$ composer validate
Now composer validate
is happy with us.
Let's Make A Console App
Now we can do proper app coding. Note that if we were at all knowledgeable about Composer, all the preceding would have taken about just a few seconds.
Since we totally emptied the directory, we have to re-require Symfony Console:
$ composer require symfony/console:~2.5
Let's talk about the ~
.
~2.5
means any version of the minimum stability, 2.5 or better. Even 2.6 or 3.0 or 10.0.1. You could also say ~2
to specify 2.0 or later.
2.5.*
means any version of the minimum stability that fits the pattern, so 2.5.0 or 2.5.999, but not 2.6.
Anyway... Composer re-initializes console from its cache. Yes, it has a cache. You can poke around in it at ~/.composer
, but save that for later.
Now let's make our top-level CLI application script:
$ nano hello.php
Here's what we'll put in that file:
<?php
use Symfony\Component\Console\Application;
$app = new Application('Hello, world!', '0.0.1-alpha');
$app->run();
This is just three lines of code, but it should show us a working CLI application, due to the power of the framework.
So let's try:
$ php hello.php
It doesn't work yet, does it? :-)
We use a namespaced class (Symfony\Component\Console\Application
), but how will PHP know where to find it?
Good question...
Autoloading I: Loading Classes From The Framework
Now we have directory that looks like this:
.
├── composer.json
├── composer.lock
├── hello.php
└── vendor
├── autoload.php <-- NOTE THIS FILE
├── composer
│ ├── ClassLoader.php
│ ├── autoload_classmap.php
│ ├── autoload_namespaces.php
│ ├── autoload_psr4.php
│ ├── autoload_real.php
│ └── installed.json
└── symfony
└── console
└── .....etc.....
Do you see ./vendor/autoload.php
?
Let's require
that into our CLI app:
$ nano hello.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Console\Application;
$app = new Application('Hello, world!', '0.0.1-alpha');
$app->run();
Now PHP can find Application
, because Composer's autoloading capabilities have enabled that. If everyone plays by the same autoloading rules, Composer can sort it so you don't have to.
Let's try it and see if it works:
$ php hello.php
Hello, world! version 0.0.1-alpha
Usage:
[options] command [arguments]
[.. etc ..]
Yay! Tentative, marginal, first-steps success!
If we poke around in vendor/autoload.php
, we see that it's including a class file and then instantiating it. That class file is auto-generated when we update or install using Composer.
Autoloading, Part II: Loading Our Own Classes
This section is easily the most complicated part, and the easiest place to get tripped up. It's still pretty quick and easy once you get the hang of it though.
In the console framework, we have to define commands that the console can perform.
We'll do this by creating a class to represent our command. This class will be autoloaded into our project by Composer, using PSR-4.
What the heck is PSR-4? It's a strategy for mapping a PHP namespace to a directory hierarchy to enable autoloading. Keep reading... :-)
In The Application Script...
So to begin, let's modify our CLI application script to reflect what we hope to accomplish. We want to make a new instantiation of a class we'll call GreetCommand
, and then add that to the application object.
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Mile23\Command\GreetCommand;
use Symfony\Component\Console\Application;
$app = new Application('Hello, world!', '0.0.1-alpha');
$app->add(new GreetCommand());
$app->run();
As you can see we've used the namespace Mile23\Command for our command class. We could have just used Mile23, but that wouldn't demo how to extend the namespace in the filesystem. There are some conventions for how to organize namespaces, but again... Out of scope for this article.
Add A Source File
Let's add a place to store our source files. This will be explained later. Just go with it, ok? :-)
$ mkdir src
$ mkdir src/Command
And let's add our command class file:
$ touch src/Command/GreetCommand.php
Let's edit the command class file:
$ nano src/Command/GreetCommand.php
<?php
namespace Mile23\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends Command
{
protected function configure()
{
$this
->setName('demo:greet')
->setDescription('Greet someone')
->addArgument(
'name',
InputArgument::OPTIONAL,
'Who do you want to greet?'
)
->addOption(
'yell',
null,
InputOption::VALUE_NONE,
'If set, the task will yell in uppercase letters'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
if ($name) {
$text = 'Hello '.$name;
} else {
$text = 'Hello';
}
if ($input->getOption('yell')) {
$text = strtoupper($text);
}
$output->writeln($text);
}
}
This is basically a copy-paste of the GreetCommand from http://symfony.com/doc/current/components/console/introduction.html with the namespace changed to Mile23\Command.
See where it says use Symfony\Component\Console\Command\Command
? We can say that in this file without loading vendor/autoload.php
because we assume that such loading occurs in the app script. The effects of including that file will continue throughout the app runtime. This is one of the major benefits of having an ordered, Composer-based project.
Tell Composer About Our Namespace
So we have a command class in src/Command/GreetCommand.php
, and we know we can have Composer autoload this for us. But how?
We get to edit composer.json
. Within composer.json
we say this:
"autoload": {
"psr-4": {
"Mile23\\": "src/"
}
}
Why do we have two backslashes after Mile23? We have to include the backslash at the end of Mile23\ for autoloading to work properly. And then we have to escape the backslash, because that's how JSON works.
A Brief Explanation Of PSR-4
PSR-4 autoloading just means that if Composer encounters a namespace starting in Mile23, it will look in our src
directory for the rest of the namespace.
In our case, that's Mile23\Command\GreetCommand, so Composer looks in src/Command
for GreetCommand.php
.
If we had specified PSR-0, Composer would have looked for src/Mile23/Command/GreetCommand.php
.
Choose whichever PSR system you prefer.
Regenerate The Classmap
OK... So we've told Composer about our namespace, so it should work right?
Wrong.
We still have to re-generate the autoload map:
$ composer dumpautoload
This will give Composer enough of a hint to find our new class. We have to do this whenever we change the 'autoload'
section of our composer.json
file. It also happens automatically when we install
or update
.
Did It Work??
So: The moment of truth:
$ php hello.php
Hello, world! version 0.0.1-alpha
Usage:
[options] command [arguments]
Options:
--help -h Display this help message.
--quiet -q Do not output any message.
--verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--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:
help Displays help for a command
list Lists commands
demo
demo:greet Greet someone
See where it says demo:greet
? That shows us that our command class was loaded and instantiated. Yay! Success!
We can now show that we met our design requirements:
$ php hello.php demo:greet world
Hello world
Yes, we are geniuses. :-)
$ php hello.php demo:greet --yell world
HELLO WORLD
Wait, Let's Test It Please
Now we need some testing infrastructure. Let's add PHPUnit.
$ composer require --dev phpunit/phpunit:~3.7
Composer does some stuff and now we have PHPUnit.
Note the --dev
argument. This specifies that PHPUnit is a requirement during development.
I'm not going to write a test here because the article is already too long. :-)
If we look at the composer.json
file now, we see this:
"require-dev": {
"phpunit/phpunit": "~3.7"
}
This looks exactly like the require
block, but with -dev
added, doesn't it?
We can now say the following:
$ composer install --no-dev
Loading composer repositories with package information
Installing dependencies from lock file
- Removing phpunit/phpunit (3.7.37)
- Removing phpunit/php-code-coverage (1.2.17)
- Removing phpunit/php-file-iterator (1.3.4)
- Removing phpunit/php-token-stream (1.3.0)
- Removing phpunit/php-timer (1.0.5)
- Removing phpunit/phpunit-mock-objects (1.2.3)
- Removing phpunit/php-text-template (1.2.0)
- Removing symfony/yaml (v2.5.3)
Generating autoload files
This tells Composer to do an installation without any of the development requirements, thanks to the --no-dev
argument. Composer removes the packages we just told it to add, because we told it --no-dev
.
By default, composer install
and composer update
will include dev requirements, but you can also explicitly require them by passing the --dev
argument.
The point here is that when it comes time to deploy your PHP app, you can just say composer install --no-dev
and Composer will do the work for you, without your testing infrastructure. Or whatever else you've relegated to dev status.
Not really the end, but just the beginning.
There's ten zillion other things to know about composer, but this is enough to get you started.