Starting a New PHP Package The Right Way

Bruno Skvorc
Share

Back when we covered Diffbot, the visual AI-enhanced machine-learning crawler, we also mentioned they have libraries for a wide array of programming languages, but those are often less than cutting edge – with so many to keep an eye on, there’s bound to be a few bad apples that slip through the cracks. One such apple is their PHP library, and we’ll be building an alternative in this series, in an effort to do it better.

Note that this tutorial will focus on writing a good package – the code we write will be real and production-ready, but you shouldn’t focus too much on Diffbot itself. Diffbot’s API is simple enough and Guzzle’s interface is smooth enough to just consume it outright without the need for a PHP library anyway. Rather, pay attention to the approaches we use to develop a high quality PHP package, so you can reuse them in your own project. Diffbot was selected as the subject of the package because I’d like to demonstrate best practices on a real world example, rather than yet another “Acme” package.

Good Package Design

In recent years, good standards for PHP package design have popped up, in no small part due to Composer, Packagist, The League and, most recently, The Checklist. Putting all these in a practical list we can follow here, but avoiding any tight coupling with The League (since our package won’t be submitted there – it’s specifically made for a third party API provider and as such very limited in context), the rules we’ll follow are:

  1. include a license
  2. be open source (well, duh!)
  3. exclude development stuff from dist
  4. use PSR-4 autoloading
  5. be hosted on Packagist for Composer installation
  6. be framework agnostic
  7. use PSR-2 coding standard
  8. have in depth code comments
  9. use semantic versioning
  10. use CI and Unit Tests

For a more detailed reading of these and more rules, see here.

Getting Started

Naturally, we’ll be using our trusty Homestead Improved box again, as it’s the quickest way to get started developing on a unified environment. For your reference, I chose the following vhosts, and will be using them throughout the rest of this tutorial:

sites:
    - map: test.app
      to: /home/vagrant/Code/diffbot_lib
    - map: test2.app
      to: /home/vagrant/Code/diffbot_test

OK, after getting into the VM, let’s start hacking away.

To hit the ground running, we’ll use the League Skeleton, which is a template package with League rules embedded, allowing for a head start. I’ve made my own fork with a better .gitignore and some minor tweaks if you’d like to use that – if not, just use theirs, the difference is truly trivial.

git clone https://github.com/Swader/php_package_skeleton diffbot_lib

We edit the composer.json file and end up with something like this:

{
    "name": "swader/diffbot_client",
    "description": "A PHP wrapper for using Diffbot's API",
    "keywords": [
        "diffbot", "api", "wrapper", "client"
    ],
    "homepage": "https://github.com/swader/diffbot_client",
    "license": "MIT",
    "authors": [
        {
            "name": "Bruno Skvorc",
            "email": "bruno@skvorc.me",
            "homepage": "http://bitfalls.com",
            "role": "Developer"
        }
    ],
    "require": {
        "php" : ">=5.5.0"
    },
    "require-dev": {
        "phpunit/phpunit" : "4.*"
    },
    "autoload": {
        "psr-4": {
            "Swader\\Diffbot\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Swader\\Diffbot\\Test\\": "tests"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}

We set some common meta data, define the requirements, and set up PSR-4 autoloading. This, along with the fact that we’re using the League Skeleton, takes care of points 1-6 on our to-do list from above. While we’re here, we can also add Guzzle to our requirements, as it’s the HTTP client library we’ll be using to make all calls to Diffbot API points.

"require": {
        "php" : ">=5.5.0",
        "guzzlehttp/guzzle": "~5.0"
    },

After running composer install, which will pull in all the dependencies, including PHPUnit we’ll need for testing, we can check if everything is working by changing the contents of src/SkeletonClass.php to:

<?php

namespace Swader\Diffbot;

class SkeletonClass
{

    /**
     * Create a new Skeleton Instance
     */
    public function __construct()
    {
    }

    /**
     * Friendly welcome
     *
     * @param string $phrase Phrase to return
     *
     * @return string Returns the phrase passed in
     */
    public function echoPhrase($phrase)
    {
        return $phrase;
    }
}

and creating an index.php file in the root of the project:

<?php

require_once "vendor/autoload.php";

$class = new \Swader\Diffbot\SkeletonClass();

echo $class->echoPhrase("It's working");

Visiting test.app:8000 in the browser should now yield the “It’s working” message.

Don’t worry about not having a public directory or anything of the sort – this is not important when building a package. When building a library, all the focus should be on the package, and only on the package – no need to deal with frameworks or MVC. We’ll use the index.php file for testing some stuff out from time to time, but mostly, we’ll be using PHPUnit to develop our library. For now, let’s add index.php to .gitignore to make sure we don’t accidentally send it upstream.

PSR-2

To keep in sync with modern standards, we’d do best to implement PSR-2 from the get-go. I use PhpStorm, so this is dead easy to do. You can either choose the built-in PSR1/PSR2 standards, like so, or you can install and activate CodeSniffer and use it as a PhpStorm inspection, like so. I opted for the former, because remote execution of PHPCS via PhpStorm is not yet supported (and a Vagrant VM is, for all intents and purposes, remote), but if you’d like to help out with adding this feature to PhpStorm, please vote here.

You can still require CodeSniffer in your project as usual via Composer and run it from the VM’s command line, though:

You can also opt to only install PHP on your host machine (as opposed to additional nonsense that comes with a standard XAMPP/WAMP installation), download CodeSniffer there, and use it like that. You’d use your host machine only for code inspection, while developing and running your package logic from the VM. It’s a bit awkward, but helps when using IDEs like PhpStorm, at least until the aforementioned issue is implemented.

If you’re not using PhpStorm, look for alternatives on how to accomplish this, but make sure you do – we need PSR2.

Planning

With our bootstrapping out of the way, we can start developing. Let’s think about everything we need.

Entry point

No matter what the use case for Diffbot’s API, a user will want to create an instance of the API client – there’s nothing you can do with Diffbot other than query the pre-made APIs. Each API use also needs a developer token which is to be passed in the request as a query param, in the form of ?token=xxxxxx. My reasoning is as follows: a single developer will usually be using a single token, so aside from allowing developers to create new API client instances and passing in a token (say, in the constructor), we should also have a way of defining a global token to be used in all future instantiations. In other words, we want both of these approaches to be valid:

$token = xxxx;

// approach 1
$api1 = new Diffbot($token);
$api2 = new Diffbot($token);

// approach 2
Diffbot::setToken($token);
$api1 = new Diffbot();
$api2 = new Diffbot();

The former approach helps when you’re creating a single API client instance, or you’re using several tokens (maybe you have one for Crawlbot, and one for regular APIs). The latter approach works well when you’ve defined many API endpoints for your application to consume and will be needing several, but don’t want to re-inject the token every time.

With that in mind, let’s go ahead and make our package’s first class. Create the file src/Diffbot.php.

<?php

namespace Swader\Diffbot;

use Swader\Diffbot\Exceptions\DiffbotException;

/**
 * Class Diffbot
 *
 * The main class for API consumption
 *
 * @package Swader\Diffbot
 */
class Diffbot
{
    /** @var string The API access token */
    private static $token = null;

    /** @var string The instance token, settable once per new instance */
    private $instanceToken;

    /**
     * @param string|null $token The API access token, as obtained on diffbot.com/dev
     * @throws DiffbotException When no token is provided
     */
    public function __construct($token = null)
    {
        if ($token === null) {
            if (self::$token === null) {
                $msg = 'No token provided, and none is globally set. ';
                $msg .= 'Use Diffbot::setToken, or instantiate the Diffbot class with a $token parameter.';
                throw new DiffbotException($msg);
            }
        } else {
            self::validateToken($token);
            $this->instanceToken = $token;
        }
    }

    /**
     * Sets the token for all future new instances
     * @param $token string The API access token, as obtained on diffbot.com/dev
     * @return void
     */
    public static function setToken($token)
    {
        self::validateToken($token);
        self::$token = $token;
    }

    private static function validateToken($token)
    {
        if (!is_string($token)) {
            throw new \InvalidArgumentException('Token is not a string.');
        }
        if (strlen($token) < 4) {
            throw new \InvalidArgumentException('Token "' . $token . '" is too short, and thus invalid.');
        }
        return true;
    }
}

The method also references a DiffbotException, so real quick, just make the file src/exceptions/DiffbotException.php with the contents:

<?php

namespace Swader\Diffbot\Exceptions;

/**
 * Class DiffbotException
 * @package Swader\Diffbot\Exceptions
 */
class DiffbotException extends \Exception
{

}

Let’s quickly explain the Diffbot class.

The token static property will serve as the default which Diffbot will use if no token is provided in the constructor while building a new instance. In that case, it gets copied into the instanceToken property which is bound to instances.

The constructor checks if a token was passed. If it wasn’t, it uses the predefined default token, or throws a DiffbotException if none was set – that’s what our Exception code above was for. If the token is OK, it gets set as the token of the instance. On the other hand, if the token was passed in, then that one gets copied into instanceToken. Note that in both cases, the token must be validated with the validateToken static method. This private method for now simply checks if the token is a string of a length of more than three characters – if not, it throws an invalid argument exception.

Finally, there’s the setToken static method, which lets us set the aforementioned global token. Naturally, this one needs to get validated, too.

Seeing as a Diffbot token is bound to its set of APIs, being able to change a token on an already existing instance of Diffbot would be crazy. As such, I’ve opted to allow the setting of a token only on instantiation, or globally for all future instances of Diffbot. Of course, if the token gets set globally, an instance can still override this setting. Also, the global token is mutable, because we want to be able to change the spawning condition of future instances, and changing it when instances already exist doesn’t affect them in any way.

Notice also how everything is documented with docblocks – not overdocumented, but just enough to make it easy to understand for everyone else coming in.

Conclusion

In this part, we got started with PHP package development by setting up a skeleton project with some basic functionality, and by configuring our environment. You can download the end result of part 1 here. In part 2, we’ll start writing some tests and some actual functionality, and we’ll get started with proper test driven development. Before we move on, are there any questions or comments regarding the current process? Leave them below!