PHP & Symfony About PHP and Symfony2 development

Experiences with PHP open source software in a Symfony-friendly environment

Posted on by Matthias Noback

There's a much more detailed chapter about this subject in my book Principles of PHP Package Design.

These days, good PHP object-oriented libraries are all around and easily available. To me, it is actually thrilling to be part of this flourishing community, while working with Symfony2 and blogging about the Framework, the Components and their neighbors (like Silex). It seems like everything is made for contributing to this nice and friendly environment, with tools like GitHub (online collaboration), Composer (dependency management), Packagist (package archive) and Travis CI (continuous integration).

Still, to me, contributing felt like too big a step to take right now. Until a few weeks ago, when I was looking for something I needed (a PHP client for the Microsoft Translator API) and could not find a decent solution. I decided to make it myself, and share it online. Below I've written down my steps. As you can see, they are very easy and would require just a bit of extra time. So, take from it what you need, and start contributing!

Write the code

First, write your software, but make it clean and clear: use not too long namespaces that speak for themselves. Remember, people will read this, and maybe even use this in production, so give it your best shot.

Initialize a Git repository

Once you have Git installed on your system, go to the root of your project's directory and run:

git init

This will initialize a Git repository. First add a .gitignore file, to which you can later add directories and files that should not be under version control.

Add a composer.json file

Make sure your project has a composer.json file, in which you give it a name, write down who you are, and what its dependencies are. Composer downloads project dependencies into the /vendor directory, and generates a file called autoload.php. When you require this file, you can start using your own classes and those in the vendor directory.

Don't forget to add vendor/* and composer.lock to the .gitignore file.

Add unit tests

Write unit tests in the /tests directory. Make them reflect the namespace hierarchy of your project's code: tests for Acme\Controller\BaseController are to be found in the Acme\Tests\Controller\BaseControllerTest class. Before running the tests, the class loader generated by Composer should be included. So create a bootstrap.php file in your /tests directory containing this code:

if (!is_file($autoloadFile = __DIR__.'/../vendor/autoload.php')) {
    throw new \LogicException('Could not find autoload.php in vendor/. Did you run "composer install --dev"?');
}

require $autoloadFile;

Next, create a phpunit.xml.dist file in the root of your project. Make sure PHPUnit uses /tests/bootstrap.php as a bootstrap file. Also point PHPUnit to the directory containing the tests. Add any PHP files that should be ignored when generating code coverage reports to the list of excludes:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
    <testsuites>
        <testsuite name="Test suite">
            <directory suffix="Test.php">./tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory>./</directory>
            <exclude>
                <directory>./tests</directory>
                <directory>./vendor</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>

Allow developers who want to run the test suite with their own configuration to create a phpunit.xml by adding "phpunit.xml" to .gitignore.

Make it open source and developer friendly

Don't forget to add a LICENSE file and a README.md or README.rst file (in Markdown or ReStructuredText). Below is the MIT license, which is the standard for projects in the Symfony ecosystem:

Copyright (c) [year] [your name]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Push your code to GitHub

Once you have an account on GitHub, you can create a new repository there. Give it a nice name and don't let GitHub pre-fill it for you. Now, copy the URL of your repository.

In your project directory run:

git add remote origin <url-to-your-repository>
git push -u origin master

You will probably be asked to provide your GitHub credentials.

This effectively makes your project available to the world.

Register your project at packagist.org

To allow other developers to load your project as a dependency using Composer, you should register it as a package. Go to Packagist and log in (easiest would be using your GitHub account). Then, click on "Submit a package" and paste the URL of the GitHub repository.

Register the Packagist Service Hook

To make sure Packagist will be update the information about your package each time you push changes to your GitHub repository, you should register the Packagist Service Hook. Copy your API key from your account page on packagist.org. Then go to the homepage of your repository on GitHub and select to the "Admin" tab. Next, select "Service hooks" and scroll down to "Packagist". Click on the link, paste the API key in the designated field and click on "Update Settings".

Versioning

The above steps will have made your project available as a dependency for other projects using Composer. But people will have to "require" the "dev-master" version in their composer.json file. This is not recommended, especially not for production software. You should give people the means to pick a stable version (by tagging it), or a stable branch, to which you will also apply any bug fixes. Branches also allow you to have different composer.json files. For instance when you are working on a Symfony bundle, your project should have branches corresponding to the available Symfony versions. When you only have a master branch, which requires the FrameworkBundle's "dev-master" version, people won't be able to use your bundle in a Symfony 2.1 project, even if the code would work perfectly well.

Continuous integration using Travis CI

Travis CI will run all your unit tests for free for every commit to your project's repository, once your source code is out in the open. The only thing you need to do is add a .travis.yml file to the root of your project containing a few lines:

language: php
php:
  - 5.4
  - 5.3

Travis allows you to log in using your GitHub account. Once you have done so, select the repository which should be continuously integrated by Travis. When it contains the .travis.yml file above, Travis will run all the tests on PHP 5.3 and 5.4. For more options, see the Travis CI documentation for PHP.

You should also install the service hook, so builds will be run (almost) immediately after you have pushed changes to your repository.

Probably skip some unit tests

One last suggestion: you will find that Travis will not be able to all your (be it sometimes slightly exotic) tests. This does not need to make your build fail. For example, when testing an external API, you should skip the functional tests making the real calls and only run true unit tests. One way to do this, is by using environment variables. Your test might look like this:

class ApiFunctionalTest extends \PHPUnit_Framework_TestCase
{
    public function testCall()
    {
        if (!isset($_ENV['API_KEY'])) {
            $this->markTestSkipped('No API key available');
        }

        // otherwise, just go!
    }
}

On your local machine, you can copy phpunit.xml.dist to phpunit.xml (and since you added this file to .gitignore it will not be under version control). Then add these lines:

<phpunit>
    <php>
        <env name="API_KEY" value="my-secret-api-key" />
    </php>
</phpunit>

Now, when you run the functional test again, it will not skip the test. But when Travis runs the tests, the environment variable "API_KEY" will not be available, and the test will be skipped.

By the way, to help you build a reputation (though its result can be the opposite), you can add the current Travis build status as an image to for instance your project's README file, using one of the formats Travis supplies.

Categories: PHP

Tags: package design Composer Git GitHub open source package design PHPUnit Travis CI

Comments: Comments