Category Archives: PHP

The beginner's guide to rebasing your PR

You've successfully created a PR and it's in the queue to be merged. A maintainer looks at the code and asks you to rebase your PR so that they can merge it.

Say what?

The maintainer means that there have been other code changes on the project since you branched which means that your branch cannot be merged without conflicts and they would like to you to sort this out.

These are the steps you should take.

TL;DR

Read the summary.

1. Update your target branch from upstream

I assume you already have an upstream repository as described in The beginner's guide to contributing to a GitHub project. The target branch you want to update can be found at the top of the PR on the GitHub site.

For example:

Pr title

The target branch in this example is develop, though I see master a lot too.

$ git checkout develop
$ git pull upstream develop && git push origin develop

2. Rebase your branch

The next step is to change to your branch which is the other branch listed in the PR details (feature/validator-result-interface in this example) and then perform a rebase:

$ git checkout feature/validator-result-interface
$ git rebase develop

This will rewind all your commits on this branch and then replay them against the tip of the branch you are rebasing against.

As you have been asked to do this, you'll get conflicts. Don't panic!

$ git status will show you which files are in conflict. For each one, follow this process:

  1. Open in editor and search for "<<<<<<" (that's 6 <) to find the conflict
  2. Inspect and fix so that you end up with the correct text. Remove the lines starting with <<<<<<, ====== and >>>>>>.
  3. Press find-next in your editor to find the next conflict in the file.
  4. Once all conflicts in the file have been fixed, exit your editor
  5. Add this file to the staging index: git add {filename}
  6. Repeat until git status shows that all conflicting files have been added to the index

Once all conflicting files are fixed you can continue the rebase:

$ git rebase --continue

3. Push your newly rebased branch to origin

Finally, all you need to do is push your branch back to origin. Note that this will require a force push and you've probably been told to never do such a thing. This is the one exception to that rule because the maintainer asked you to do it.

So go ahead:

$ git push -f origin {victim branch}

It's a good idea to leave a comment on the PR that you've done the rebase and the PR is ready for re-review.

All done

To summarise the steps required:

  1. $ git checkout {target branch}
  2. $ git pull upstream {target branch} && git push origin {target branch}
  3. $ git checkout {victim branch}
  4. $ git rebase {target branch}
  5. Fix conficts and continue rebasing
  6. $ git push -f origin {victim branch}

That's it. Being asked to rebase your PR isn't scary or (usually) difficult as long as you pay attention to what you're doing. However, if you're not fully comfortable with git, then I recommend buying the Git Workbook and working through all the exercises.

The beginner's guide to contributing to a GitHub project

This is a guide to contributing to an open source project that uses GitHub. It's mostly based on how I've seen Zend Framework, Slim Framework and joind.in operate. However, this is a general guide so check your project's README for specifics.

TL;DR

Skip to the summary.

Step 1: Set up a working copy on your computer

Firstly you need a local fork of the the project, so go ahead and press the "fork" button in GitHub. This will create a copy of the repository in your own GitHub account and you'll see a note that it's been forked underneath the project name:

Forked

Now you need a copy locally, so find the "SSH clone URL" in the right hand column and use that to clone locally using a terminal:

$ git clone git@github.com:akrabat/zend-validator.git

Which will do something like this:

Clone

Change into the new project's directory:

$ cd zend-validator

Finally, in this stage, you need to set up a new remote that points to the original project so that you can grab any changes and bring them into your local copy. Firstly clock on the link to the original repository – it's labeled "Forked from" at the top of the GitHub page. This takes you back to the projects main GitHub page, so you can find the "SSH clone URL" and use it to create the new remote, which we'll call upstream.

$ git remote add upstream git@github.com:zendframework/zend-validator.git

You now have two remotes for this project on disk:

  1. origin which points to your GitHub fork of the project. You can read and write to this remote.
  2. upstream which points to the main project's GitHub repository. You can only read from this remote.

Step 2: Do some work

This is the fun bit where you get to contribute to the project. It's usually best to start by fixing a bug that is either annoying you or you've found on the project's issue tracker. If you're looking for a place to start, a lot of projects use the "easy pick" label (or some variation) to indicate that this issue can be addressed by someone new to the project.

Branch!

The number one rule is to put each piece of work on its own branch. If the project is using git-flow, then it will have both a master and a develop branch. The general rule is that if you are bug fixing, then branch from master and if you are adding a new feature then branch from develop. If the project only has a master branch, the branch from that. Some projects, like Slim use branches named after the version number (2.x and 3.x in Slim's case). In this case, pick the branch that's relevant.

For this example, we'll assume we're fixing a bug in zend-validator, so we branch from master:

$ git checkout master
$ git pull upstream master && git push origin master
$ git checkout -b hotfix/readme-update

Firstly we ensure we're on the master branch. Then the git pull command will sync our local copy with the upstream project and the git push syncs it to our forked GitHub project. Finally we create our new branch. You can name your branch whatever you like, but it helps for it to be meaningful. Including the issue number is usually helpful. If the project uses git-flow as zend-validator does, then there are specific naming conventions where the branch is prefixed with "hotfix/" or "feature/".

Now you can do your work.

Ensure that you only fix the thing you're working on. Do not be tempted to fix some other things that you see along the way as your PR will probably be rejected. Make sure that you commit in logical blocks. Each commit message should be sane. Read Tim Pope's A Note About Git Commit Messages.

Step 3: Create the PR

To create a PR you need to push your branch to the origin remote and then press some buttons on GitHub.

To push a new branch:

$ git push -u origin hotfix/readme-update

This will create the branch on your GitHub project. The -u flag links this branch with the remote one, so that in the future, you can simply type git push origin.

Swap back to the browser and navigate to your fork of the project (https://github.com/akrabat/zend-validator in my case) and you'll see that your new branch is listed at the top with a handy "Compare & pull request" button:

Pr button

Go ahead and press the button!

If you see a yellow box like this:

Contributing

Click the link which will take you to the project's CONTRIBUTING file and read it! It contains valuable information on how to work with the project's code base and will help you get your contribution accepted.

On this page, ensure that the "base fork" points to the correct repository and branch. Then ensure that you provide a good, succinct title for your pull request and explain why you have created it in the description box. Add any relevant issue numbers if you have them.

Create pr

If you scroll down a bit, you'll see a diff of your changes. Double check that it contains what you expect.

Once you are happy, press the "Create pull request" button and you're done.

Step 4: Review by the maintainers

For your work to be integrated into the project, the maintainers will review your work and either request changes or merge it.

Lorna Mitchell's article Code Reviews: Before You Even Run The Code covers the things that the maintainers will look for, so read it and ensure you've made their lives as easy as possible.

To sum up

That's all there is to it. The fundamentals are:

  1. Fork the project & clone locally.
  2. Create an upstream remote and sync your local copy before you branch.
  3. Branch for each separate piece of work.
  4. Do the work, write good commit messages, and read the CONTRIBUTING file if there is one.
  5. Push to your origin repository.
  6. Create a new PR in GitHub.
  7. Respond to any code review feedback.

If you want to contribute to an open source project, consider joind.in!

Simple Ansible file for Z-Ray preview

Recently, Zend made available a Z-Ray Technology Preview which takes the Z-Ray feature of Zend Server and makes it stand-alone.

This is very interesting as it means that I can run it with the PHP 5.6 on Ubuntu 14.04 LTS Vagrant set up that I prefer. I decided to create an Ansible playbook to install Z-Ray into my VM. The Z-Ray instructions are clear enough, so it was simply a case of converting them to a set of YAML steps as who wants to do manual installation nowadays?!

This is what I came up with:

---

- name: Downloading Z-Ray for PHP 5.6
  get_url:
    url: "http://downloads.zend.com/zray/1208/zray-php5.6-Ubuntu-14.04-x86_64.tar.gz"
    dest: "/home/vagrant/zray-php5.6-Ubuntu-14.04-x86_64.tar.gz"

- name: Extract Z-Ray archive
  unarchive: src=/home/vagrant/zray-php5.6-Ubuntu-14.04-x86_64.tar.gz dest=/opt copy=no creates=/opt/zray/INSTALL_ZRAY.TXT

- name: Set Z-Ray ownership
  shell: chown -R vagrant:vagrant /opt/zray

- name: Add the Z-Ray UI virtual host
  file: src=/opt/zray/zray-ui.conf dest=/etc/apache2/sites-available/zray-ui.conf

- name: Enable the Z-Ray UI virtual host
  shell: a2ensite zray-ui.conf

- name: Install Z-Ray PHP extension (Apache)
  file: src=/opt/zray/zray.ini dest=/etc/php5/apache2/conf.d/zray.ini state=link

- name: Install Z-Ray PHP extension (CLI)
  file: src=/opt/zray/zray.ini dest=/etc/php5/cli/conf.d/zray.ini state=link

- name: Link Z-Ray PHP 5.6 extension
  file: src=/opt/zray/lib/zray.so dest=/usr/lib/php5/20131226/zray.so state=link

I've set this up for PHP 5.6 which is what I'm using nowadays. If you're using 5.5, then you need to download zray-php5.5-Ubuntu-14.04-x86_64.tar.gz and the link needs to be to /usr/lib/php5/20121212/zray.so.

Changing the file ownership to vagrant was important – I couldn't get it work without doing this step and I have no idea why its needed…

The standalone Z-Ray looks to be very similar to the one supplied as part of Zend Server and is equally as helpful in terms of getting a grip on what's going on with your application when developing it.

It has improved since I wrote about before and is even more useful now as it's been improved and has plugins!

With the standalone version of Z-Ray, there's really no excuse to not be using it.

Improved error handling in Slim 3 RC1

From RC1 of Slim 3, we have improved our error handling. We've always had error handling for HTML so that when an exception occurs, you get a nice error page that looks like this:

Slim3 error page

However, if you're writing an API that sends and expects JSON, then it still sends back HTML:

Slim3 old json error

At least we set the right Content-Type and status code!

However, this isn't really good enough. We should send back JSON if the client has asked for JSON. Until RC1, the only way to do this was to register your own error handler:

$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
  return function ($request, $response, $exception) use ($c) {
    $data = [
      'code' => $exception->getCode(),
      'message' => $exception->getMessage(),
      'file' => $exception->getFile(),
      'line' => $exception->getLine(),
      'trace' => explode("\n", $exception->getTraceAsString()),
    ];

    return $c->get('response')->withStatus(500)
             ->withHeader('Content-Type', 'application/json')
             ->write(json_encode($data));
  };
};

which provides the correct output:

Slim 3 custom json error

However, we can do better than this and do it for you. Starting with RC1, Slim will now output JSON (or XML) when an error occurs if the client's Accept header is application/json (or application/xml) and it will also provide all the previous exception too. This is much nicer and also works for the two other error handlers: notFound and notAllowed.

Slim 3 rc1 error json

Finally, Note that you should never use our default errorHandler in production as it leaks too much data! We'll try and fix this before 3.0 final, though.

random_bytes() in PHP 5.6 and 5.5

Last week, I needed some random data and using the power of the PHP manual, came across random_bytes which does exactly what I need. However, it's PHP7 only.

As I target both Linux and Windows, I needed to do a bit more work to get it working which was fine, but a minor nuisance given that I know that there's a better way in PHP7.

Talking on the #joind.in IRC channel a few days later, Anthony tells us about paragonie/random_compat which is a PHP 5.x polyfill for the PHP7 methods random_bytes() and random_int(). Usage is exactly the same, so when you switch to PHP7, you just remove the library from composer and all your code using these methods will continue to work.

I hadn't heard of it; I'm guessing that you haven't either, so now you know!

Slim-Csrf with Slim 3

In addition to the core Slim framework, we also ship a number of add-ons that are useful for specific types of problems. One of these is Slim-Csrf which provides CSRF protection.

This is middleware that sets a token in the session for every request that you can then set as an hidden input field on a form. When the form is submitted, the middleware checks that the value in the form field matches the value stored in the session. If they match, then the all is okay, but if they don't then an error is raised.

For the simplest use case, you need start the session and add the middleware:

session_start();
$app->add(new Slim\Csrf\Guard());

Then, from within a given route callable, you can create your form and add two hidden fields: one for the token's name and one for its value:

$app->get('/', function ($request, $response, $args) {
    // CSRF token name and value
    $name = $request->getAttribute('csrf_name');
    $value = $request->getAttribute('csrf_value');

    // Render a form
    $html = <<<EOT
<!DOCTYPE html>
<html>
<head><title>CSRF test</title></head>
<body>
    <form method="POST" action="/process">
        <input type="hidden" name="csrf_name" value="$name">
        <input type="hidden" name="csrf_value" value="$value">
        <input type="text" name="name" placeholder="Name">
        <input type="submit" value="Go">
    </form>
</body>
</html>
EOT;

    return $response->write($html);
});

If you run this in a browser and view the source, you'll see something like this:

Slim csrf view source

Refresh and you see different values for the csrf_name and csrf_value fields, which means that the user can have multiple tabs open and submit without any issues.

For testing, I created a simple route callable:

$app->post('/process', function ($request, $response, $args) {
    return $response->write("Passed CSRF check.");
});

Pressing form's submit button will result in the display of "Passed CSRF check.". If you then refresh and confirm the post, you'll see "Failed CSRF check!" and the HTTP status code will be 400.

Customising the CSRF failure

It's likely that you'll want to customise the CSRF failure display as a plaint text error message isn't very user friendly! To change this, supply a callable to the Guard class which has the same signature as middleware: `
function($request, $response, $next). The middleware must return a Response.

This allows you to supply a custom error page:

$guard = new Slim\Csrf\Guard();
$guard->setFailureCallable(function ($request, $response, $next) {
    return $response->write(<<<EOT
<!DOCTYPE html>
<html>
<head><title>CSRF test</title></head>
<body>
    <h1>Error</h1>
    <p>An error occurred with your form submission.
       Please start again.</p>
</body>
</html>
EOT);
});
$app->add($guard);

As the failure callable has the middleware signature, you can also set a flag into $request and then deal with the CSRF failure later. The failure callable would look something like this:

$guard->setFailureCallable(function ($request, $response, $next) {
    $request = $request->withAttribute("csrf_result", 'FAILED');
    return $next($request, $response);
});

Now, your route callable can decide what to do:

$app->post('/process', function ($request, $response, $args) {
    if (false === $request->getAttribute('csrf_result')) {
        // Deal with error here and update $response as appropriate
    } else {
        // successfully passed CSRF check
        $response->write("Passed CSRF check.");
    }
    return $response;
});

This is very powerful and remarkably easy to set up.

Summary

The flexibility of the failure callable allows you to handle a CSRF validation failure in the most appropriate way for your application and is a very powerful feature of this middleware.

As it's PSR-7 compliant, you can use the middleware independently of Slim with any PSR-7 middleware dispatch system that uses the middleware signature of function($request, $response, $next) where a Response is returned.

Using abstract factories with Slim 3

In my Slim 3 skeleton, I chose to put each action into its own class so that its dependencies are injected into the constructor. We then register each action with the DI Container like this:

$container['App\Action\HomeAction'] = function ($c) {
    return new App\Action\HomeAction($c['view'], $c['logger']);
};

In this case, HomeAction requires a view and a logger in order to operate. This is quite clear and easy. However, it requires you manually register each action class with the DI Container.

Bruno Skvorc said this on Twitter:

In short, this seems ULTRA wasteful if I want ALL my controllers to get view and logger.

Slim 3's default DI container is Pimple, which is one of the simpler DI Containers out there. To solve Bruno's problem, my initial though was to use Zend\ServiceManager's abstract factories feature.

Fortunately, Slim 3 supports container-interop and I've already written RKA\ZsmSlimContainer which integrates Zend\ServiceManager with Slim 3. Once we have ZSM in place it's all quite easy!

We need to register our own abstract factory with Zend\ServiceManager. An abstract factory requires two methods to be implemented: canCreateServiceWithName and createServiceWithName. The method names are fairly self-explantory.

In our case, canCreateServiceWithName needs to return true if the requested class name ends in "Action" and then createServiceWithName simply creates the class with the two required dependencies. It looks like this:

app/src/ActionAbstractFactory.php:

<?php
namespace App;

use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ActionAbstractFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName(
        ServiceLocatorInterface $locator, $name, $requestedName)
    {
        if (substr($requestedName, -6) == 'Action') {
            // This abstract factory will create any class that
            // ends with the word "Action"
            return true;
        }
        return false;
    }
 
    public function createServiceWithName(
        ServiceLocatorInterface $locator, $name, $requestedName)
    {
        $className = $requestedName;

        // This factory creates Actions that have precisely two
        // constructor parameters: $view & $logger
        $view = $locator->get('view');
        $logger = $locator->get('logger');
        
        return new $className($view, $logger);
    }
}

We then register the abstract factory with the DI container:

$container->addAbstractFactory(new App\ActionAbstractFactory());

The end result is that we don't need to register every Action with the DI Container individually as long as they all have the same constructor signature. This is obviously a simplistic example, but the principle applies to any situation where you need to create different classes in the same vein.

If you want to see this in action, I've created a sample project on GitHub.

Checking your code for PSR-2

Most of the projects that I work on follow the PSR-2 coding style guidelines. I prefer to ensure that my PRs pass before Travis or Jenkins tells me, so let's look at how to run PSR-2 checks locally.

PHP_CodeSniffer

My preferred tool for checking coding styles in PHP is PHP_CodeSniffer. This is command line tool, phpcs, that you can run against any file.

PHP_CodeSniffer can test against a number of standards. The default is PEAR, so you must use the command line switch --standard=PSR2 in order for it to check against the right one.

Let's take an example from joind.in's web2 project:

$ phpcs --standard=PSR2 BaseApi.php 

FILE: ...joind.in/joindin-vm/joindin-web2/app/src/Application/BaseApi.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 11 | ERROR | [x] Expected 1 space after closing parenthesis; found 9
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Time: 70ms; Memory: 6Mb

This is the code that's wrong:

if (isset($config['apiUrl']))
{
    $this->baseApiUrl = $config['apiUrl'];
}

The opening brace is in the wrong place.

If there is a note that PHPCBF can fix some validations automatically, then you can run the phpcbf command line tool to automatically fix your code:

$ phpcbf --standard=PSR2 BaseApi.php 
Changing into directory .../joindin-vm/joindin-web2/app/src/Application
Processing BaseApi.php [PHP => 1452 tokens in 186 lines]... DONE in 52ms (1 fixable violations)
        => Fixing file: 0/1 violations remaining [made 3 passes]... DONE in 163ms
Patched 1 file
Time: 246ms; Memory: 8Mb

And phpcbf has changed the code to be compliant:

if (isset($config['apiUrl'])) {
    $this->baseApiUrl = $config['apiUrl'];
}

Obviously, not all violations can be automatically fixed, but a good number can be.

Automating with Phing

Generally you want to be able to run phpcs across all your source files without too much effort and also get your CI tool to do the same. My preferred choice here is to use Phing.

Each command within a Phing build.xml file is called a target. The PHP_CodeSniffer target for the web2 project is:

<target name="phpcs">
 <phpcodesniffer standard="PSR2"
      description="Run PSR2 standards over the codebase"
      haltonerror="true">
   <fileset dir="${basedir}/app">
     <include name="**/*.php"/>
   </fileset>
   <fileset dir="${basedir}/tests">
     <include name="**/*.php"/>
   </fileset>
   <formatter type="full" usefile="false"/>
 </phpcodesniffer>
</target>

With this target we run phpcs over all PHP files in the `app` and `tests` directories. We run it like this:

$ phing phpcs

As Phing is simply a build tool, the error output is the same format as earlier.

Editor integration

I use Vim and Sublime Text 3, which both have plugins for checking syntax.

Syntastic for Vim

Syntastic is a great plugin for Vim that checks syntax for any language that you can think of. Install it using your preferred method and then for PHP linting and PSR-2 checking set up in your .vimrc like this:

" Syntastic
let g:syntastic_php_checkers=['php', 'phpcs']
let g:syntastic_php_phpcs_args='--standard=PSR2 -n'

You can also add Syntastic summary information to the status line using %{SyntasticStatuslineFlag()}.

The display looks like this (Yes, I like white backgrounds for editing, sorry!):

Syntastic PSR2 checking

When you save the file, Syntactic runs the checkers that you've set up, php -l and phpcs --standard=PSR2 -n in this case and displays any errors with a red S> in the gutter and if you've enabled it, a summary in the status line. When your cursor is on that line, then the error is displayed at the very bottom.

SublimeLinter for Sublime Text 3

The SublimeLinter project does for Sublime Text 3. You need to install "SublimeLinter", "SublimeLinter-php" and "SublimeLinter-phpcs" via Package Control as each linter is separate.

Configure by selecting the Preferences -> Package Settings -> SublimeLinter -> Settings - User menu item. My settings are:

{
    "user": {
        "delay": 0.5,
        "linters": {
            "php": {
                "@disable": false,
                "args": [],
                "excludes": []
            },
            "phpcs": {
                "@disable": false,
                "args": [
                    "-n"
                ],
                "excludes": [
                    "*.phtml",
                    "*.twig"
                ],
                "standard": "PSR2"
            }
        }
    }
}

This sets up SublimeLinter to check half a second after you stop typing and then it will highlight any errors:

SublimeLinter PSR2 checking

You get a marker in the left hand gutter and an outline around the detected error. Placing your cursor on that line displays the error in the status bar.

To sum up

Ensuring that your code matches the coding style for your project is very easy. If you run phpcs locally and fix any issues before you submit your PR, then the project maintainers will love you!

Custom OAuth2 authentication in Apiiglity

I have a client that's writing an Apigility API that needs to talk to a database that's already in place. This also includes the users table that is to be used with Apigility's OAuth2 authentication.

Getting Apigility's OAuth2 integration to talk to a specific table name is quite easy. Simply add this config:

'storage_settings' => array(
    'user_table' => 'user',
),

To the relevant adapter within zf-mvc-auth => authentication config.

However, if you want to use different column names, that's a bit trickier as they are hardcoded in the OAuth2\Storage\Pdo class. To get Apigility's OAuth2 components to look at the correct columns, you create your own OAuth2 Adapter. I chose to extend ZF\OAuth2\Adapter\PdoAdapter which extends OAuth2\Storage\Pdo and go from there.

ZF\OAuth2\Adapter\PdoAdapter extends the base class to add bcrypt hashing. This is good, so it's a good place to start from. I created a new module, MyAuth to hold my adapter and its factory. The adapter looks like this:

<?php
namespace MyAuth;

use ZF\OAuth2\Adapter\PdoAdapter;

/**
 * Custom extension of PdoAdapter to validate against the WEB_User table.
 */
class OAuth2Adapter extends PdoAdapter
{
    public function __construct($connection, $config = array())
    {
        $config = [
            'user_table' => 'legacy_user'
        ];

        return parent::__construct($connection, $config);
    }

    public function getUser($username)
    {
        $sql = sprintf(
            'SELECT * from %s where email_address=:username',
            $this->config['user_table']
        );
        $stmt = $this->db->prepare($sql);
        $stmt->execute(array('username' => $username));

        if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            return false;
        }

        // the default behavior is to use "username" as the user_id
        return array_merge(array(
            'user_id' => $username
        ), $userInfo);
    }

    public function setUser($username, $password, 
        $firstName = null, $lastName = null)
    {
        // do not store in plaintext, use bcrypt
        $this->createBcryptHash($password);

        // if it exists, update it.
        if ($this->getUser($username)) {
            $sql = sprintf(
                'UPDATE %s SET pwd=:password, firstname=:firstName,
                    surname=:lastName WHERE username=:username',
                $this->config['user_table']
            );
            $stmt = $this->db->prepare($sql);
        } else {
            $sql = sprintf(
                'INSERT INTO %s (email_address, pwd, firstname, surname)
                    VALUES (:username, :password, :firstName, :lastName)',
                $this->config['user_table']
            );
            $stmt = $this->db->prepare($sql);
        }

        return $stmt->execute(compact('username', 'password', 'firstName',
            'lastName'));
    }

    protected function checkPassword($user, $password)
    {
        return $this->verifyHash($password, $user['pwd']);
    }
}

This code for getUser and setUser() is lifted directly from OAuth2\Storage\Pdo and all I've done is changed the column names. In this case I have email_address for my username, and pwd for the password column. Similar, I wrote my own checkPassword based on ZF\OAuth2\Adapter\PdoAdapter, again changing the array key to check to 'pwd'.

Now that we have the actual work done, we need to wire it into Apigility.

Firstly we need a factory so that the DIC can instantiate our adapter:

<?php
namespace MyAuth;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Db\Adapter\Driver\Pdo\Pdo as PdoDriver;

class OAuth2AdapterFactory implements FactoryInterface
{
    /**
     * Create service
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @return OAuth2Adapter
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $connection = $serviceLocator->get('DB\Master');
        if (!$connection->getDriver() instanceof PdoDriver) {
            throw new \RuntimeException("Need a PDO connection!");
        }

        $pdo = $connection->getDriver()->getConnection()->getResource();
        return new OAuth2Adapter($pdo);
    }  
}

This is fairly standard code. Note that the DB\Master is the name of the database connection that is set up in the Apigility admin. I've been a bit lazy and assume that it's a PDO based adapter. If it isn't, it'll blow up, so if you're not using PDO, then it won't work as is!

To register your new authentication adapter with Apigility, create a config file in config/autoload and call it myauth.global.php or something:

<?php
return [
    'zf-mvc-auth' => [
        'authentication' => [
            'adapters' => [
                'MyAuth' => [
                    'adapter' => 'ZF\\MvcAuth\\Authentication\\OAuth2Adapter',
                    'storage' => [
                        'storage' => 'MyAuth\OAuth2Adapter',
                        'route' => '/oauth',
                    ],
                ],
            ],
        ],
    ],
];

The adapter is called MyAuth and is now available to select in the API configuration pages of the admin:

Myauth apigility

To sum up

All in all, it's really easy to write custom OAuth 2 authentication for Apigility as it's a very flexible platform. I've simply changed the column names here, but it would be easy enough to write an adapter against a different storage system altogether, though you would have to override more methods and possibly start from a more appropriate base class.

Debugging PHP SOAP over SSL using Charles

I'm currently integrating against a SOAP server using PHP which wasn't working as I expected, so I wanted to find out what was happening over the wire. I have Charles installed and use it regularly with OS X's system-wide proxy settings. However, PHP's SoapClient doesn't use these, so I had to work out how to do it manually.

Enabling SoapClient to send via a proxy is really easy and is documented by Lorna Mitchell in Using Charles To Debug PHP SOAP:

$options = [
    "cache_wsdl" => WSDL_CACHE_NONE,
    "soap_version" => SOAP_1_1,
    "trace" => 1,
    "proxy_host" => "localhost",
    "proxy_port" => 8888,
];

$client = new \SoapClient($wsdl, $options);

I did this and saw traffic in Charles. However, my service endpoint is SSL and I saw this error:

PHP Fatal error:  SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://example.com/Service.svc?singleWsdl' : failed to load external entity "https://example.com/Service.svc?singleWsdl" in SoapServiceProcessor.php on line 167

Looking in Charles, I saw the note:

You may need to configure your browser or application to trust the Charles Root Certificate. See SSL Proxying in the Help menu.

Aha!

Again, we turn back to Lorna for how to do sort this out. This time, we need Manipulating HTTP with Charles Proxy, that she wrote for TechPortal. Unhelpfully, that website doesn't use section links, so scroll all way down to the Charles and SSL section to find out the relevant information about how to set up Charles for SSL proxying.

On OS X, you simply do:

  • Help -> SSL Proxying -> Install Charles Root Certificate
  • Proxy -> SSL Proxying Settings:
    • Check Enable SSL Proxying
    • Add the endpoint's domain to the list of locations

Finally, I needed to tell SoapClient to trust Charles' root certificate so that it can decrypt the SSL traffic.

This is done by downloading the Charles root certificate (Help -> SSL Proxying -> Save Charles Root Certificate) and storing it somewhere. I chose to put it in /usr/local/etc/charles-ssl-proxying-certificate.crt.

Finally, configure a new stream_context that knows about this certificate and add it to the SoapClient:

$options = [
    "cache_wsdl" => WSDL_CACHE_NONE,
    "soap_version" => SOAP_1_1,
    "trace" => 1,
    "proxy_host" => "localhost",
    "proxy_port" => 8888,
    "stream_context" => stream_context_create([
        'ssl' => [
            'cafile' => '/usr/local/etc/charles-ssl-proxying-certificate.crt'
        ]
    ];
];

$client = new \SoapClient($wsdl, $options);

Now everything works and I can see the actual data that's being sent to the SSL SOAP service and I solved my problem!