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="http://nonsecure.example.com/Configuration.svc"/>
    </wsdl:port>
    <wsdl:port name="BasicHttpsBinding_IConfiguration" binding="tns:BasicHttpsBinding_IConfiguration">
        <soap:address location="https://secure.example.com/Configuration.svc"/>
    </wsdl:port>
</wsdl:service>

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():

$client->__setLocation('https://secure.example.com/Configuration.svc');

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) {
    $client->__setLocation($location);
}
// 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();

$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.

Middleware

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:

$app->add('App\Middleware\AppMiddleware:run');

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 :

VAGRANTFILE_API_VERSION = '2'

# Inline provisioning shell script
@script = <<SCRIPT

# Set up variables
DB_NAME=my_db_name
MYSQL_DUMP_FILE=/vagrant/docs/my_db_dump_file.sql

# 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 ]
then
  curl -Ss https://getcomposer.org/installer | php
  php composer.phar install --no-progress
fi

# Do we need to create a MySQL database?
if [ -e $MYSQL_DUMP_FILE ]
then
    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
fi

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


# Vagrant configuration
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = 'rasmus/php7dev'
  config.vm.network :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"]
  end

end

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!

My Xdebug configuration

With the release of Xdebug 2.3, I have updated my xdebug php.ini settings, so it seems sensible to write them down where I won't lose them!

The new-for-2.3 features which prompted this are xdebug.overload_var_dump, which Derick has written about and xdebug.halt_level which I have previously written about. I find both of these very useful.

This is my current php.ini configuration:

; Xdebug settings

; var_dump() displays everything, including filename and line number
xdebug.overload_var_dump        = 2
xdebug.var_display_max_children = -1
xdebug.var_display_max_data     = -1
xdebug.var_display_max_depth    = -1

; don't supress errors
xdebug.scream = 1

; stop if there's a warning/notice
xdebug.halt_level = E_WARNING|E_NOTICE|E_USER_WARNING|E_USER_NOTICE

; remote debugging
xdebug.remote_enable       = 1
xdebug.remote_connect_back = 1
xdebug.remote_port         = 9000

; profiling is triggered via browser extension
xdebug.profiler_enable         = 0
xdebug.profiler_enable_trigger = 1

For every other setting, I've found that the default is fine.

I control html_errors within my app so that it's set to 1 when the app is rendering HTML, and 0 otherwise, such as with CLI applications.

Turn warnings into exceptions

As I keep looking this up in other projects that I've written, I'm putting it here so I can find it more easily.

There are a number of built-in PHP functions that generate a notice or warning that you can't turn off when something goes wrong, such as parse_ini_file and file_get_contents.

One common solution to this is to suppress using the @ operator:

$result = @file_get_contents($url);
if (false === $result) {
    // inspect error_get_last() to find out what went wrong
}

This doesn't work when using Xdebug with the xdebug.scream set to 1 and its also inelegant and inefficient..

A better way is to use set_error_handler:

set_error_handler(function ($severity, $message, $file, $line) {
    throw new \ErrorException($message, $severity, $severity, $file, $line);
});

$result = file_get_contents($url);

restore_error_handler();

In this case, we register our own error handler that converts every notice, warning and error into an ErrorException that can then be caught elsewhere. We then call the PHP function of interest and immediately call restore_error_handler to put back the one we had earlier.

Interestingly, in PHP7, we can expect to see exceptions in the engine itself which should allow us to solve this problem like this:

try {
    $result = file_get_contents($url);
} catch (EngineException $e) {
    // do something with $e
}

A Slim3 Skeleton

Warning Slim 3 is currently under active development. Expect BC breaks!

I'm creating a number of projects in Slim 3 (which is currently under development) at the moment in order test things out and found that I was setting them up in essentially the same way. To save me having to do this each time, I've now created a skeleton project that is set up with the directory structure that I like and also includes Twig and Monolog.

To create a new Slim 3 project, simply do this:

$ composer create-project -n -s dev akrabat/slim3-skeleton my-app

This will create a new folder called my-app with the project files in it and then install the composer dependencies. You now have a working skeleton application, so test it using the built-in PHP web server:

$ cd my-app
$ php -S 0.0.0.0:8888 -t public public/index.php

Browse to http://localhost:8888 and you should see this:

Slim3 skeleton screenshot

What's included?

When exploring the application, these are the key files to look at.

  • index.php instantiates the Slim application object, sets the application up using files in app/ and runs it.
  • app/dependencies.php contains all the dependencies that are registered with Pimple.
  • app/middleware.php is where you should register any application middleware that you want to use. It's empty in the skeleton.
  • app/routes.php contains all the routes that the application responds to. I like having them all in one place and to keep the file sensible, I use the DIC to grab the action for dispatch.
  • app/src/Action/HomeAction.php is the class that is loaded by the DIC and then the dispatch method is executed. The nice thing about having a class like this is that I can load the dependencies via the constructor. Look in dependencies.php for the factory for this class.
  • app/templates/home.twig is the Twig view script that is rendered.

That's it

From this point on, you should delete what you don't want and add what you do in order to write your application!

Role models

Wikipedia defines a role model like this:

A role model is a person whose behavior, example, or success is or can be emulated by others

All my life, I have identified people that I perceive to be a little better than I am in some facet of life that I want to get better at and I emulate them. I've been doing this all my life. They have been my role models.

My first recollection of this was when I was around 9 or 10 – my last two years of primary school – when I discovered that by emulating and learning how my friend Claire approached maths problems, I got better at it. Similarly at secondary school, I found the academically best people I could to inspire me.

At University there were three people that had characteristics that I wanted in myself. This was when I discovered that a role model could inspire me to change how I view and live my life as well as improve my abilities. My role models helped me get a first class degree, but one of them unknowingly helped me become a more empathetic person.

This trend that has continued all the way through my life. I've found people who are better than me and have emulated them. I pay attention to how they behave & what they do and try to change appropriately. My role models have (and continue to) inspired me to be better.

I cannot thank them enough.

I hadn't thought about the gender mix of my inspirations before reading Zaron Burnett's Can A Man Have Female Role Models? I found this article eye-opening and I highly recommend that you take the time to read. He points out that the way our society portrays and treats women has done us all (yet another) disservice by hindering our ability to select the best role-models we can:

This hidden hierarchy of gender in language and culture runs rampant as blackberry thicket and is just as thorny and difficult to remove. It’s that bias that makes men reluctant to be like women, or identify with women, or imagine life from a woman’s eyes, or conceive of what a day in her experience might be like that. That’s a problem.

A smart man selects from both men and women for his role models. He learns from both and draws lessons from the whole body of humanity’s experience, rather than only taking counsel from the half he might meet in a public restroom.

I will look up to whomever I choose in order to better myself. I suggest you do too.

Styling rst2pdf tables

I currently use rst2pdf to create presentations slide decks from reStructured Text files. I like rST a lot as it's more expressive than Markdown and allows for extension.

Tables in rST are marked up like this:

+-----------+-----------+-----------+
| Heading 1 | Heading 2 | Heading 3 |
+===========+===========+===========+
| a         | b         | c         |
|           |           |           |
| aa        |           |           |
+-----------+-----------+-----------+
| d         | e         | f         |
+-----------+-----------+-----------+
| g         | h         | i         |
+-----------+-----------+-----------+
| j         | k         | l         |
+-----------+-----------+-----------+

We create a PDF file with the command rst2pdf test.rst which produces a table that looks like this:

Rst table standard

To style, this we create styles within a style file and then compile using rst2pdf test.rst -s my.style.

Let's start with the table element:

styles:
    table:
      commands: []
         [VALIGN, [ 0, 0 ], [ -1, -1 ], TOP ]
         [INNERGRID, [ 0, 0 ], [ -1, -1 ], 0.25, black ]
         [ROWBACKGROUNDS, [0, 0], [-1, -1], [white,#E0E0E0]]
         [BOX, [ 0, 0 ], [ -1, -1 ], 0.25, black ]

Behind the scenes, rst2pdf uses ReportLab to create the PDF. The commands style maps directly to ReportLab's TableStyle commands (section 7.4 of the current documentation)

Each command contains an identifier, the start and stop cell definition to which it applies and then the style to apply. The cell definition is defined as [X,Y] where [0,0] is the top left cell, [2,3] would be the cell with e in it in the definition above. Negative numbers count from the bottom right, so [-1,-1] is the bottom-right corner.

Key options:

VALIGN Vertical text alignment. Options: TOP, BOTTOM, MIDDLE.
INNERGRID, BOX, LINEBELOW, LINEABOVE, LINEBEFORE, LINEAFTER Style of the borders. First parameter is line thickness and second is colour.
BACKGROUND, ROWBACKGROUNDS, COLBACKGROUNDS Background colour. Parameter is an array of colours, used cyclically.
TOPPADDING, BOTTOMPADDING, LEFTPADDING, RIGHTPADDING Padding with cells. Parameter is a number.

The style for the table heading is called table-heading:

    table-heading:
        parent : heading
        backColor : beige
        alignment : TA_CENTER
        valign : BOTTOM
        borderPadding : 0

These settings should be obvious. I always override backColor!

The style for the table elements is table-body:

table-body:
      parent : normal

By default, it isn't styled, but if you want to change the textColor, this is where to do it.

Overriding on a per table basis

To override for a specific table, then set a class before the table:

.. class:: mytable

+-----------+-----------+-----------+
| Heading 1 | Heading 2 | Heading 3 |
+===========+===========+===========+
| a         | b         | c         |
|           |           |           |
| aa        |           |           |
+-----------+-----------+-----------+
| d         | e         | f         |
+-----------+-----------+-----------+

The most common reason to do this is to set up specific column widths:

    mytable:
        parent: table
        colWidths: [3cm, 6cm, 3cm]

This is mostly useful when the auto-sizing routine causes odd line breaks in heading text.

If you are targeting rst2pdf, then you can also set widths using the .. widths:: directive like this:

.. widths:: 20 20 60

+-----------+-----------+-----------+
| Heading 1 | Heading 2 | Heading 3 |
+===========+===========+===========+
| a         | b         | c         |
+-----------+-----------+-----------+
| d         | e         | f         |
+-----------+-----------+-----------+

The width numbers are percentages and it only works if you pass the command line option -e preprocess when compiling.

My defaults

My defaults currently are:

styles:
    table:
        commands: []
            [VALIGN, [ 0, 0 ], [ -1, -1 ], MIDDLE ]
            [ROWBACKGROUNDS, [0, 0], [-1, -1], [white]]
            [LINEBELOW, [0, 0], [-1, 0], 0.5, black]
            [BOTTOMPADDING, [0, 1], [-1, -1], 5]
            [TOPPADDING, [0, 1], [-1, -1], 5]
            [ALIGN, [ 0, 0 ], [ -1, -1 ], LEFT ]


    table-heading:
        parent : heading
        fontName: stdBold
        backColor : white
        alignment : TA_LEFT

This results in the very simple table style of:

Rst default

This suits me as a starting point.