Do you need Zend Framework training or consultancy? Get in touch!

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.


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's web2 project:

$ phpcs --standard=PSR2 BaseApi.php 

 11 | ERROR | [x] Expected 1 space after closing parenthesis; found 9

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"
   <fileset dir="${basedir}/app">
     <include name="**/*.php"/>
   <fileset dir="${basedir}/tests">
     <include name="**/*.php"/>
   <formatter type="full" usefile="false"/>

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": [
                "excludes": [
                "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:

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',
        $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

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

        return $stmt->execute(compact('username', 'password', 'firstName',

    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:

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 or something:

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.

Replacing Pimple in a Slim 3 application

One feature of Slim 3 is that the DI container is loosely coupled to the core framework. This is done in two ways:

  1. The App composes the container instance rather than extending from it.
  2. Internally, App depends on the container implementing the container-interop interface.

You can see this decoupling in the way that you instantiate a Slim 3 application:

$settings = [];
$container = new Slim\Container($settings);
$app = new Slim\App($container);

Slim 3 ships with Pimple by default, but my preference is for Zend\ServiceManager, so I decided to integrate ServiceManager into Slim 3.

RKA\ZsmSlimContainer is the result of this work.


To use RKA\ZsmSlimContainer, simply add it via composer:

composer require akrabat/rka-slim-zfsm-container

and then update your index.php:

$settings = [];
$container = new RKA\ZsmSlimContainer\Container($settings);
$app = new Slim\App($container);

As you can see, due to the decoupling, the change required is very small and everything still works! The only thing that doesn't work is Pimple\ServiceProviderInterface's register() method as that is tightly coupled to the Pimple container itself.


To implement this, I extended Zend\ServiceManager and in the constructor registered Slim's default services. This is easy enough to do with ServiceManager's setFactory method. For example:

        function ($c) { return new CallableResolver($c); },

In this example, the callableResolver service needs to return a new instance every time it is retrieved from the container, so I pass false in as the third parameter to setFactory as it will default to shared otherwise.

This was the minimum effort required, but I wanted to be able to use code that had been written for Pimple (as far as possible), such as Twig-View. To do this, I implemented ArrayAccess as that's how Pimple works. Implementing offsetGet, offsetExists and offsetUnset was easy as I simply had to call ServiceManager's get, has and unregisterService methods respectively, but offsetSet required a bit more work.

When setting a service via array access, you can assign settings, closures or instances:

$container['foo'] = 'a setting';
$container['bar'] = function($c) { return new Bar($c->get('foo)); }
$container['baz'] = new Bar('another setting');

In ServiceManager, you would use different methods, such as setService or setFactory.

I needed to detect what was intended and call the correct internal method, so I came up with this:

    public function offsetSet($id, $value)
        if (is_object($value)) {
            if ($value instanceof \Closure) {
                return $this->setFactory($id, $value);
            return $this->setService($id, $value);

        if (is_string($value) && class_exists($value)) {
            return $this->setInvokableClass($id, $value);

        return $this->setService($id, $value);

I also support the invokable feature of ServiceManager, where if the value is a class name string, then ServiceManager can instantiate when required. This would be used like this:

$container['qux'] = \My\Fun\Thing::class;


All in all, I was pleased at how the decoupled nature of Slim 3 made it easy to replace Pimple with my preferred DI container. I particularly like how it is seamless enough that I can continue to use Twig-View.

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 '' : failed to load external entity "" 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.


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!

First beta of Slim Framework 3

Last night, I tagged beta 1 of Slim Framework 3! This is a significant upgrade to v2 with a number of changes that you can read on the Slim blog.

For me, the two key features that I'm most excited about are:

  • PSR-7 support, along with the standard middleware signature of:
        function($request, $response, $next) { return $response; }
  • Dependency injection container with container-interop compliance. We ship with Pimple by default, but I'm planning to use Zend\ServiceManager.

There's lots of other changes and we believe we have kept to the key tenants of Slim, keeping it focussed as a micro-framework suitable for building any application that you want to build. Slim is a particularly good choice our new Composer-based world where you can pick the best of breed components to build your application.


The easiest way to get going with a Slim 3 application is to use my skeleton application:

$ composer create-project -n -s dev akrabat/slim3-skeleton my-app
$ cd my-app
$ php -S -t public public/index.php

and browse to http://localhost:8888. I wrote up some more about my skeleton application previously.

Alternatively, the README file has information on starting from scratch.

This is a Beta!

Obviously, this is beta release – we are sure there are bugs and issues to be improved. We really want you to test it and kick the tyres. Please report any issues you find to us. We also appreciate pull requests!

You can also find helpful people on IRC in the #slimphp channel on Freenode.

Also not that while we hope that we won't need to change any function signatures, we aren't promising that we'll keep BC before 3.0 stable.


The new docs are very much a work in progress! They are incomplete and out of date in places. Again, please report any issues and pull requests are very welcome!

Next steps

We shall be concentrating on documentation and ensuring that Slim 3 is as stable as we can make it. This is an exciting time and I'm looking forward to when Slim 3.0 final is released.

Selecting the service port with PHP's SoapClient

I'm currently integrating with a SOAP service which has two different services defined.

The relevant part of the WSDL is:

<wsdl:service name="Config">
    <wsdl:port name="BasicHttpBinding_IConfiguration" binding="tns:BasicHttpBinding_IConfiguration">
        <soap:address location=""/>
    <wsdl:port name="BasicHttpsBinding_IConfiguration" binding="tns:BasicHttpsBinding_IConfiguration">
        <soap:address location=""/>

I discovered that PHP's SoapClient will select the first port it encounters and doesn't provide a way to select another one. This was a nuisance as I wanted to use the SSL one.

Through research, I discovered that I can use __setLocation():


However, I don't control that endpoint, so I would rather select based on the port's name.

As I couldn't find a way to get the data from SoapClient, I decided to parse the WSDL myself and pull the information out. I don't do a lot with XML namespaces, so had to look up how to handle them and then how to extract the right data using XPath.

As I had to look it up, I'm putting it here, so I can find it again more easily!

I converted my new found knowledge into a method to extract the location attribute from the <soap:address> element of the <wsdl:port> with the correct name element:

function getLocationForPort($wsdl, $portName)
    $file = file_get_contents($wsdl);

    $xml = new SimpleXmlElement($file);

    $query = "wsdl:service/wsdl:port[@name='$portName']/soap:address";
    $address = $xml->xpath($query);
    if (!empty($address)) {
        $location = (string)$address[0]['location'];
        return $location;

    return false;

Xpath is ideal for this job!

Usage is simply:

$client = new SoapClient($wsdl);
$sslLocation = getLocationForPort($wsdl, 'BasicHttpsBinding_IConfiguration');
if ($sslLocation) {
// work with $client as normal

Now, all my calls to the SOAP service are via SSL as they should be!

Accessing services in Slim 3

One of the changes between Slim Framework 2 and 3 is that the application singleton has gone.

In Slim 2, you could do this:

$app = \Slim\Slim::getInstance();
// do something with $app

In general, you didn't need access to $app itself, but rather you wanted access to something that the app knows about, such as a database adapter, or the router for access to the urlFor method to create a URL to a route.

With Slim 3, there is no getInstance() on App, so you need to inject the instances of whatever you need where ever you need them.

Setup the container

Let's start by setting up the container with a fictional mapper:

// create container and configure it
$settings = require 'settings.php';
$container = new \Slim\Container($settings);

$container['pdo'] = function ($container) {
    $cfg = $container->get('settings')['db'];
    return new \PDO($cfg['dsn'], $cfg['user'], $cfg['password']);

$container['books'] = function ($container) {
    return new BookMapper($container->get('pdo'));

// create app instance
$app = new \Slim\App($container);

This is standard Slim 3 initialisation code, to which we have added a DI entry called books which depends on a PDO instance, so we have also registered that to allow the container to create the BookMapper when we ask for it.

How would we get at books within our application?

Route callables

Let's start by looking at the two common ways to write a route callable.

A route callable closure:

If you use a closure, then Slim binds the application instance to $this.

$app->get('/', function($request, $response, $args) {
    $books = $this->getContainer()->get('books');
    // do something with $books and then return a response

There's also a shortcut you can use as App implements __get which proxies to the container object's get method:

$app->get('/', function($request, $response, $args) {
    $books = $this->books; // retrieve from the container
    // do something with $books and then return a response

A route callable class:

If you use a class method for a route callable like this:

$app->get('/', 'HomeAction:dispatch');

Slim will look for a DI key of HomeAction, use the DI container to instantiate the class and then dispatch by calling the dispatch() method.

Hence, you should use the DI container to inject what you need into the class constructor:

class HomeAction {
    public function __construct($books)
        $this->books = $books;

    public function dispatch($request, $response, $args)
        $books = $this->books;
        // do something with $books and then return a response

We now need to register the class with the container:

// in index.php:
$container['HomeAction'] = function ($container) {
    return new HomeAction($container->get('books'));

The registered route will now work and have access to the BookMapper.

All of Slim's services are in the container, so where I've used books in this example, you can access anything that Slim has registered, such as settings or router and in addition to what you have registered yourself.


The same thing pattern works with both app and route middleware.

Middleware closure

If you use a closure in middleware, then the container is bound to $this:

$app->add(function ($request, $response, $next) {
    $settings = $this->get('settings');
    // do something with $settings
    return $next($request, $response);

Middleware class

You can also use a class which works exactly as it does for a route:


Slim will look for a DI key of App\Middleware\AppMiddleware, use the DI container to instantiate it and then call the run() method.

As before, inject your dependencies via the class constructor:

namespace App\Middleware;

class AppMiddleware
    public function __construct($settings)
        $this->settings = $settings;

    public function run($request, $response, $next)
        $settings = $this->settings;
        // do something with $settings
        return $next($request, $response, $next);

and register with the container:

$container['App\Middleware\AppMiddleware'] = function ($container) {
    return new App\Middleware\AppMiddleware($container->get('settings'));

I've found this pattern to be nicely consistent and easy to remember.

To sum up

Slim 3 encourages you to think a little more carefully about which dependencies you need for any given middleware or action. Due to closure binding and the built-in DI container, it's easy to access the classes you need, when you need them.

Testing my ZF1 app on PHP7

Zend Framework 1 is still actively maintained and we fully intend to ensure that ZF1 works with no problems on PHP 7 when its released.

Now that PHP 7.0.0 Alpha 1 has been released, it's time to find out if your Zend Framework 1 app works with it. The easiest way to do this is to use a virtual machine. My preference is Vagrant with Rasmus' PHP7dev box.

A simple VM

I wanted to test a client's ZF1 application with PHP 7, so I created this drop-dead simple Vagrantfile to will boot up a virtual machine running PHP7:

# -*- mode: ruby -*-
# vi: set ft=ruby :


# Inline provisioning shell script
@script = <<SCRIPT

# Set up variables

# Switch to PHP7
newphp 7

# rebuild PHP7
makephp 7

# Configure nginx to point at our public/ directory and set APPLICATION_ENV to php7dev
echo '
server {
    listen       80;
    server_name  localhost;
    root         /vagrant/public;
    index        index.php index.html index.htm;
    access_log   /var/log/nginx/default-access.log  main;
    error_log    /var/log/nginx/default-error.log;

    location / {
        try_files $uri $uri/ @rewrite;
    location @rewrite {
        index index.php;
        rewrite ^(.*)$ /index.php;

    location ~ \.php {
        include                  fastcgi_params;
        fastcgi_keep_conn        on;
        fastcgi_index            index.php;
        fastcgi_split_path_info  ^(.+\.php)(/.+)$;
        fastcgi_param            PATH_INFO $fastcgi_path_info;
        fastcgi_param            SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param            APPLICATION_ENV php7dev;
        fastcgi_intercept_errors on;
        fastcgi_pass             unix:/var/run/php-fpm.sock;


' > /etc/nginx/conf.d/default.conf
service nginx restart

cd /vagrant

# Do we need to install and run composer?
if [ -e composer.json ]
  curl -Ss | php
  php composer.phar install --no-progress

# Do we need to create a MySQL database?
if [ -e $MYSQL_DUMP_FILE ]
    mysql -uvagrant -pvagrant -e "DROP DATABASE IF EXISTS $DB_NAME";
    mysql -uvagrant -pvagrant -e "CREATE DATABASE $DB_NAME";
    mysql -u vagrant -pvagrant $DB_NAME < $MYSQL_DUMP_FILE

echo "** Visit http://localhost:8888 in your browser for to view the application **"

# Vagrant configuration
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| = 'rasmus/php7dev' :forwarded_port, guest: 80, host: 8888
  config.vm.hostname = "zf1app.local"
  config.vm.provision 'shell', inline: @script

  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--memory", "1024"]


The nice thing about creating the provisioning script within the Vagrantfile itself is that we now have a one file solution, but it's probably not the best solution for more complex set ups!

It's slow to start because it re-compiles PHP 7 via the makephp 7 command. Note that the MySQL username and password is vagrant, so I set APPLICATION_ENV to php7dev, so that I can set the correct configuration in application.ini.

What I found

You must read the UPGRADING file as it tells you all the BC breaks. There's a lot of nice tidy-ups and consistency improvements, which fortunately, haven't affected us.

As we've been working on ensuring ZF1 works with PHP7, my client's website worked with just one issue:

"Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP;"

It turned out that we had an old version of the PHP Markdown library. So I upgraded it and this issue was sorted.

In summary

I've explored using Rasmus' box before for unit testing and playing with extensions, but it also turned out that it's the ideal starting point for running my web applications under PHP7 too! I was pleased to discover that so far, I've found nothing broken on my ZF1 applications.

Brent Simmons: How Not to Crash #9: Mindset

Brent Simmons has recently posted How Not to Crash #9: Mindset:

I used to think that means I should write code that’s about 80% as clever as I am. Save a little bit for debugging.

But over the years I’ve come to think that I should write code that’s about 10% as clever as I am. And I’ve come to believe that true cleverness is in making code so clear and obvious that it looks like nothing at all.

I'm on the same journey. I used to be proud that I could solve a problem in fewer lines of code. Now I'm proud of simple code. I'm still learning though and there are plenty of cases where my code doesn't live up to my ideals.

Because its too clever.

20 years of PHP

Today marks 20 years since PHP was released by Rasmus Lerdorf and Ben has been asking for how we started our PHP journey.

My first use of PHP was to write a website for an online computer gaming guild for EverQuest, back in 1999. A friend recommended it when I asked him how people programmed webpages in something other the C! That first website is still going and I'm not proud of the code. I'm very proud that it's still going strong and running on PHP 5.6 and has had some very minor updates for PHP version changes:

Oh yeah – I also fixed the SQL inject and XSS vulnerabilities!

That's quite a short list to make a PHP 3 application run on PHP 5.6! Of course, it's not object oriented, so I bypassed the upgrade pain there and it doesn't follow the latest best practices.

That PHP website also led to my first job in the web industry as I was headhunted from my job programming Windows applications. My first commercial website was an internal business application for sales tracking in an Internet hosting company. I've mostly staying in internal business applications and B2B apps ever since!

Of course, the way I write in PHP has changed considerably over the years. My first application was HTML pages with PHP where I needed it. I developed a library of procedural functions and moved most of my PHP code into .inc files. My first framework was Fusebox 4, which I first used in 2005. It was a procedural framework, but was a genuine Front Controller and encouraged separation of concerns. When I was ready to replace it with an OOP framework, Zend Framework had been announced and I jumped onto it…

PHP was built from day one for the web and, for me, it's still the best tool for the job!