Showing posts with label GuzzleHttp. Show all posts
Showing posts with label GuzzleHttp. Show all posts

Thursday, 17 November 2016

PHP: decorating async GuzzleHttp calls

G'day:
A while back I looked at the decorator pattern in the context of some stuff we were doing:

That's all surprisingly straight forward and handy stuff.

We're revisiting some of this for a new implementation of our HTTP adapter library, which had everything baked into it before, and the decorating has had me scratching my head a bit as we're implementing an adapter for GuzzleHttp, and it does its requests asynchronously, and we want to leverage that.

Just to recap, previously we'd have this sort of thing:

namespace me\adamcameron\testApp;

use GuzzleHttp\Client;

class GuzzleAdapter {

    private $client;
    private $endPoint;

    public function __construct($endPoint){
        $this->endPoint = $endPoint;
        $this->client = new Client();
    }

    public function get($id){
        $response = $this->client->requestAsync(
            "get",
            $this->endPoint . $id
        );

        return $response;
    }

}

And say a logging decorator for that:

namespace me\adamcameron\testApp;

class LoggedGuzzleAdapter {

    private $adapter;
    private $logger;

    public function __construct($adapter, $logger) {
        $this->adapter = $adapter;
        $this->logger = $logger;
    }

    public function get($id){
        $this->logger->logMessage(sprintf("Requesting for %s", $id));

        $response = $this->adapter->get($id);

        $body = $response->getBody();
        $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body));
        
        return $response;
    }

}


And we'd init our adapter thus:

$endPoint  = "http://cf2016.local:8516/cfml/misc/guzzleTestEndpoints/getById.cfm?id=";

$guzzleAdapter = new GuzzleAdapter($endPoint);
$logger = new LoggingService();
$adapter = new LoggedGuzzleAdapter($guzzleAdapter, $logger);


So the underlying GuzzleAdapter handles the Guzzle stuff, the LoggedGuzzleAdapter handles just the logging stuff, but defers to its GuzzleAdapter to do its part of the job, and keeps all the moving parts and bits of functionality sensibly separated.  And it's pretty simple. And as detailed in those earlier articles, we can keep layering decorators around an adapter to add caching or what-have-you in a similar way. Easy. Nice.

However this only works cos the call to Guzzle actually returns the result on the spot. And this is cos we were using it synchronously: we make a call to it, it blocks until it gets the answer back from the target and gives us the answer.

Now that we're using async calls, Guzzle doesn't give us the answer, it just gives us a Promise which will eventually resolve to be the answer. This is in theory good cos it means the calling code can make a bunch of HTTP calls, and not wait around for each of them to resolve in series: Guzzle will actually make them all in parallel. I have a look at this in article "PHP: async requests using Guzzle and request pools".

If we go back to our decorator we can see the problem:

public function get($id){
    $this->logger->logMessage(sprintf("Requesting for %s", $id));

    $response$promisedResponse = $this->adapter->get($id);

    $body = $response$promisedResponse->getBody();
    $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body));
    
    return $response$promisedResponse;
}

At the point at which our decorator needs the body... we don't have it yet. All we have is a promise that at some point we'll have a body (or we'll have response object via the resolved promise, anyhow; and the response object will have the body).

To get the body we first need to wait for the promise to resolve... which is a blocking operation and kinda defeats the purpose of using the async approach in the first place. IE: we could do this sort of thing:

public function get($id){
    $this->logger->logMessage(sprintf("Requesting for %s", $id));

    $promisedResponse = $this->adapter->get($id);
    $response = $promisedResponse->wait();
    
    $body = $response->getBody();
    $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body));
    
    return $response;
}

But doing the wait immediately defeats the purpose of making the call async in the first place. We want the wait call to... err... wait... until the calling code says "right I need to actually use that data now".

Our initial attempt to sort this out was to analyse the issue as being one of "well we don't have the data we need until we call wait, so then we need to do the logging then... which means we need to intercept the wait call... which means we need to return our own decorated version of the response from the call to the adapter with its own wait and have a LoggedResponse, and return that from the code above..." But... and I hope my colleague doesn't mind me saying... when I saw this code in code review I kinda went "there must be a better, more semantic way of doing this". I won't repeat the code as I cannot use our actual work code in my blog, and I am not in the office right now and can't remember the detail of the implementation anyhow. But it's not ideal, so I don't want to share it anyhow.

I've been off sick for the last coupla days, which gives me a lot of time to deliberate and mess around with stuff and google a lot more than I allow myself time for when I'm in the office. With a wee bit of "standing back and having another think about it", the solution became clear. We don't need to explicitly override the wait method before we call it... the promise builds that capability in! At the time we're making the call, we get to tell the Promise what happens when it gets resolved. So I've knocked together this proof of concept:

public function get($id){
    $this->logger->logMessage(sprintf("(%s) Requesting for %s", $this->thisFile, $id));

    $response = $this->adapter->get($id);

    $response->then(function($response) use ($id){
        $body = $response->getBody();
        $this->logger->logMessage(sprintf("(%s) Response for %s: %s", $this->thisFile, $id, $body));
        $body->rewind();
    });

    return $response;
}

This was a bit of a head-slap moment - in that it took me a while to work out what to do - the whole thing about Promises is that one's able to chain functionality onto their lifecycle. if we wanna run some code once the Promise resolves, we just slack a then handler onto it.

This is a good solution IMO as it keeps all the logging decoration in one place, doesn't require any blocking of the async part of the operation, and is still very clear what's going on, even if the inner workings of Promises are a bit opaque (which TBH, they still are for me. Every time I work with 'em I have to re-read the docs. Which in Guzzle's case are pretty bloody awful, if I'm to be unkindly frank).

I chucked all this into a test rig to demonstrate to myself it was sound.

First up I contrived an endpoint in CFML for the PHP code to hit. I wanted to do this outside of PHP so it didn't in any way interfere with the PHP code running. It's simple:

<cfscript>
cfcontent(type="application/json");

writeLog(file="testApp", text="[ID: #URL.id#] request received");

sleep(5000);

writeOutput(serializeJson({"id"=URL.id, "retrieved"=now().dateTimeFormat("HH:nn:ss.lll")}));
writeLog(file="testApp", text="[ID: #URL.id#] response returned");
</cfscript>


This just takes an ID parameter and waits 5sec then returns it in a JSON packet. I wait 5sec to make the async-ness of the PHP calls easier to see. I'm also logging some stuff here to compare when the requests got to CF compared to when they were sent by PHP.

On the PHP end of things, I've got this:

use \me\adamcameron\testApp\GuzzleAdapter;
use \me\adamcameron\testApp\LoggedGuzzleAdapter;
use \me\adamcameron\testApp\LoggingService;

require_once __DIR__ . "/../vendor/autoload.php";

$endPoint = "http://cf2016.local:8516/cfml/misc/guzzleTestEndpoints/getById.cfm?id=";

$guzzleAdapter = new GuzzleAdapter($endPoint);
$logger = new LoggingService();
$adapter = new LoggedGuzzleAdapter($guzzleAdapter, $logger);

$ids = ["001", "002", "003", "004"];

$thisFile = basename(__FILE__);

$logger->logMessage(sprintf("(%s) Making requests...", $thisFile));

$responses = [];
foreach ($ids as $id){
    $logger->logMessage(sprintf("(%s) Requesting for %s", $thisFile, $id));
    $responses[] = $adapter->get($id);
}
$logger->logMessage(sprintf("(%s) Requests made", $thisFile));

$logger->logMessage(sprintf("(%s) Getting bodies from requests...", $thisFile));
foreach ($responses as $response){
    $logger->logMessage(sprintf("(%s) before calling wait()", $thisFile));
    $body = $response->wait()->getBody()->getContents();
    $logger->logMessage(sprintf("(%s) Response Body: %s", $thisFile, $body));
}
$logger->logMessage("Done");

This does the following:
  • creates the GuzzleAdapter;
  • uses a LoggedGuzzleAdapter to decorate it with some logging (the LoggingService just wraps some Monolog stuff);
  • loops over four IDs;
  • and requests their bumpf;
  • we then loop over the responses we've accumulated;
  • wait for them to resolve (calling wait blocks until they are);
  • and outputs the result;
  • all the way along, writing to the log at key points.

The actual LoggedGuzzleAdapter I'm using here is:

class LoggedGuzzleAdapter {

    private $adapter;
    private $logger;
    private $thisFile;

    public function __construct($adapter, $logger) {
        $this->adapter = $adapter;
        $this->logger = $logger;
        $this->thisFile = basename(__FILE__);
    }

    public function get($id){
        $this->logger->logMessage(sprintf("(%s) Requesting for %s", $this->thisFile, $id));

        $response = $this->adapter->get($id);

        $response->then(function($response) use ($id){
            $body = $response->getBody();
            $this->logger->logMessage(sprintf("(%s) Response for %s: %s", $this->thisFile, $id, $body));
            $body->rewind();
        });

        return $response;
    }

}


And the underlying GuzzleAdapter:

use GuzzleHttp\Client;

class GuzzleAdapter {

    private $client;
    private $endPoint;

    public function __construct($endPoint){
        $this->endPoint = $endPoint;
        $this->client = new Client();
    }

    public function get($id){
        $response = $this->client->requestAsync(
            "get",
            $this->endPoint . $id,
            ["http_errors" => true]
        );

        return $response;
    }

    public function getHandlerStack()
    {
        return $this->client->getConfig('handler');
    }

}


The things to note here are:

  • I'm logging the file name the log entry was made from. This is just to make it clearer in the logs which code is doing what for a given entry.
  • Just note the complete separation of concerns between the two files. One does logging. One does... "Guzzling". And they each focus on the job at hand, and that's it.

Here's the result. This is what PHP logged. I've trimmed-out some repetition & clutter, but have not changed any context or ordering of what got logged:

[14:15:53](makeRequests.php) Making requests...
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 001
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 002
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 003
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 004
[14:15:53](makeRequests.php) Requests made
[14:15:53](makeRequests.php) Getting bodies from requests...
[14:15:53](makeRequests.php) before calling wait()
[14:15:58](LoggedGuzzleAdapter.php) Response for 003{"retrieved":"14:15:58.450","id":"003"}
[14:15:58](LoggedGuzzleAdapter.php) Response for 002{"retrieved":"14:15:58.450","id":"002"}
[14:15:58](LoggedGuzzleAdapter.php) Response for 004{"retrieved":"14:15:58.450","id":"004"}
[14:15:58](LoggedGuzzleAdapter.php) Response for 001{"retrieved":"14:15:58.450","id":"001"}
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"001"}
[14:15:58](makeRequests.php) before calling wait()
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"002"}
[14:15:58](makeRequests.php) before calling wait()
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"003"}
[14:15:58](makeRequests.php) before calling wait()
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"004"}

Things to note:

  • first and foremost: the PHP code is indeed making the HTTP requests asynchronously. Remember the CFML code pauses for 5sec for each response, which means if we do four requests that's a total of 20sec paused time. But the whole process for all four requests takes only 5sec (CF is intrinsically multi-threaded, so from its side of things, it'll process all four requests simultaneously).
  • PHP makes all four requests within the same first second of execution.
  • After 5sec, the responses start to come in.
  • The first wait call bears the brunt of this 5sec wait. Bear in mind this is a blocking operation so PHP is now waiting for the 5sec-long response from CF to come back. But after that, all the responses start to come in before their equivalent wait calls, so those promises are resolved before the code even calls wait for them.
  • most importantly: the correct logging is taking place at the correct time. So the code actually works as intended.
One interesting thing here. it doesn't seem like the act of making the request actually triggers the request. It's the act of waiting that causes the requests to be sent (albeit it: all of them).

I stuck a ten second wait in my PHP code, between making the requests and asking about their responses:

$logger->logMessage(sprintf("(%s) Requests made", $thisFile));

sleep(10);

$logger->logMessage(sprintf("(%s) Getting bodies from requests...", $thisFile));

And here's the log for that run:

[14:47:58] (makeRequests.php) Making requests...
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 001
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 002
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 003
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 004
[14:47:58] (makeRequests.php) Requests made
[14:48:08] (makeRequests.php) Getting bodies from requests...
[14:48:08] (makeRequests.php) before calling wait()
[14:48:13] (LoggedGuzzleAdapter.php) Response for 004: {"retrieved":"14:48:13.704","id":"004"}
[14:48:13] (LoggedGuzzleAdapter.php) Response for 001: {"retrieved":"14:48:13.719","id":"001"}
[14:48:13] (LoggedGuzzleAdapter.php) Response for 003: {"retrieved":"14:48:13.719","id":"003"}
[14:48:13] (LoggedGuzzleAdapter.php) Response for 002: {"retrieved":"14:48:13.704","id":"002"}
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.719","id":"001"}
[14:48:13] (makeRequests.php) before calling wait()
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.704","id":"002"}
[14:48:13] (makeRequests.php) before calling wait()
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.719","id":"003"}
[14:48:13] (makeRequests.php) before calling wait()
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.704","id":"004"}
[14:48:13] Done

Notes:

  • the requests are all made @ 58.
  • you can see the 10sec sleep here. If the requests had actually been made, then they'd've all come back in that 10sec window.
  • but we can tell from the values coming back from ColdFusion that the requests weren't actually even made until 08 (as the 5sec delay is indicated in 13 in the returned timestamp
  • so it's the first call to wait that actually triggers the requests to be sent.

I'm not too sure I like that. Guzzle should crack on with it, as soon as it's asked, surely? Not wait around for me to ask "are we there yet?" Still... I don't think this will adversely impact what we're doing very often. In reality we're making a single call and then waiting for it immediately, almost all the time (this is to the extent I have actually questioned why we are doing this work, and why we're arsing about with Guzzle when we could just be using curl. But... oh well).

So that's the end of my most recent adventure with Guzzle and Promises and async calls and the like. I'm pleased with the resulting code. Not so pleased with how Guzzle works.

I've another two articles that cover the next challenge I faced with this stuff still to come. I've got the code for the second one written... but the topic for the third one only came to mind whilst writing this. So perhaps tomorrow I'll document my adventures of howTF to coerce Guzzle into raising the exceptions I choose for it to raise when requests come back with 400-500 responses, not its generic ones; and after that, how to implement a caching decorator for all this stuff. I've been off work sick for yesterday and today, but will be back in the office tomorrow. So I suspect the second article won't be out until Saturday now. I've had enough of being awake today, and am heading back to bed now. it is 3pm, after all ;-)

Righto.

--
Adam

Wednesday, 14 January 2015

PHP: async requests using GuzzleHttp and request pools

G'day:
This'll round out my investigations into GuzzleHttp for the time being. Yesterday I looked at "PHP: trying to get async HTTP requests working via GuzzleHttp", and got that working with only a coupla hitches (all from me not RTFMing). That approached things by making individual async calls. What I'm doing today is to create an array of requests, and use GuzzleHttp's request pools to make them all simultaneously. It's taken about four hours to get the code together in a way I think makes sense. I had a coupla delays due to not paying attention to my code, then being bemused as to why it didn't work. But on the whole it was pretty smooth and predictable. And it just works.

Tuesday, 13 January 2015

PHP: trying to get async HTTP requests working via GuzzleHttp

G'day:
Last week I started looking at a few frameworks on PHP: "PHP: messing around with Silex, Pimple & GuzzleHttp". In that article I set up a basic Silex-driven site, using Pimple for managing dependency injection, and GuzzleHttp for handling HTTP requests to a stub REST API I had knocked together in CFML.

That was - for all intents and purposes - using synchronous calls in GuzzleHttp. It was making them asynchronously, but then immediately blocking until they completed. One of my requirements is to use asynchronous calls, so the HTTP requests don't block my app. Well: as little as possible, anyhow. If I have three five-second HTTP requests to make, I'd rather fire and forget them for as long as possible until I need the data, at which point I'll wait for them to complete. TBH, for my actual purposes REST is the wrong answer, especially if we need to actually worry about its latency, but we seem to be stuck with that. We'd be in the position to install the app on our PHP servers themselves and integrate with them more tightly (it's our own app we're communicating with via REST), but we're not able to do this for reasons I cannot fathom. C'est la vie.

Anyway, I've build on my app before - well the general structure of it - expanding it out to be a "blog"...or at least a remote REST API which serves up an article, some reference links and some comments for a given article number.

Monday, 5 January 2015

PHP: messing around with Silex, Pimple & GuzzleHttp

G'day:
As you know, I've recently shifted most of my attention in my day job from CFML to PHP. This has had a coupla false starts: firstly I was needed back on the CFML team for a while to oversee some maintenance work, and then I was off work with eye trouble for a week or so. And then bloody Xmas & New Year came around, interfering with everything as well. All in all: I am well behind in my PHP learning / baptism-by-fire. I've good a month to catch up before I'm off work again for a month whilst I follow New Zealand's trials and tribulations in the Cricket World Cup. Conveniently being held in New Zealand (oh, and Aussie too, I s'pose) this year. This is only "convenient" as I was scheduled to be back in NZ around that time anyhow, checking in on the family and drinking beer with mates.

[disclosure... I am victim of a flight delay so have been killing time coding and drinking Guinness. I am halfway through my fourth pint, so my verbal diarrhoea will be worse than usual, as that previous paragraph evidenced. On review of the rest during proofreading, it doesn't get any better].

Anyway, I sat down and looked at our new app's PHP codebase the other day and went "Blimey! This is complicated!". Now I'm a newbie with PHP so I don't expect to follow the minutiae of each line of code, but I figured I should understand the general flow of what's going on. But no. For what should be a reasonably simple website (a front for online accommodation booking), we seemed to have an awful lot of Routings and Factories and Providers and Builders and Controllers and Repositories and combinations of the above (eg: ProviderFactoryBuilders... or it might be RepositoryBuilderFactories or something). It looked to me like a Java application in its structure, not one written in a dynamic web language. Still: I could be missing something, so decided to look into the app's architecture.

Basically we're using a framework called Silex. This is based on Symfony, but is very cut down. I guess if it was the CFML world, Silex might be taking FW/1's approach, and Symfony might be more like how ColdBox views the world. My position on frameworks is that FW/1 is about right; and ColdBox is too all-inclusive. But mileage varies, and let's not get into that. I'd heard of Symfony - but never used it - but had not heard of Silex. However reading the docs it sounds quite good (in that the docs are fairly short, I understand them).

I've been using ColdSpring for years, and like the idea of DI. Coming from CFML, it makes slightly less sense to me in the context of PHP as everything is request-centric: there's no sense of application (and hardly any sense of session in what I'd consider to be a professional implementation). So unlike how ColdSpring will cache bean configs for the life of the application, one has to tread more cautiously with PHP because the whole lot starts from scratch every request. That said, it's still desirable to define the wiring of one's classes & dependencies once, and then when one comes to need a new service (or ProviderFactoryRepositoryBuilder ;-), it's just a matter of saying "one of those please", rather than having to hand code initialising the thing with all its dependencies (and their dependencies, etc). Fortunately Silex ships with a DI framework - Pimple - baked in. Nice. But I need to know how to use it. I also needed to know whether we really need a lot of our factory classes, as they didn't really seem to be doing much... and I knew that if I was using CFML and ColdSpring I'd not need them at all. I was hoping this'd be the same for our new app.

Another new concept to me - I've been stuck using Fusebox (the XML version, not the CFC version) for years - is having the framework handle the request routing too. We've been using .htaccess for this previously. TBH: I actually think this is the correct approach, too: routing is the job of the HTTP tier, not the application tier. However frameworks seem to want to do this stuff themselves these days. They seem to have migrated from "MVC" to be more like "RMVC" (or, more sensibly: "RCMV"), with the R standing for routing. Our routing code seemed to be very very repetitive (copy and paste boilerplate ceremony with a few differences per route), and also seemed to be a mishmash of config and code, which struck me as being less than ideal. As well as just making for a lot of code that we oughtn't need. I was buggered if I knew how to fix it, but I figured I'd try to understand how we got to where we did, and whether there might be a better approach.

Finally, this new app hits a REST API to get its data. I'm hesitant about using REST for internal server-to-server comms... it's just slow and not the right tool for the job if one can just run the code to do the work, instead of making an HTTP request... to run the code to do the job. But we have no choice here, so REST is what we use. I know the theory of REST adequately (and a lot better having read Adam Tuttle's book: "Book review: REST Web APIs: The Book (win a copy here)"), and can wire together REST stuff in CFML, but had no idea how it works in PHP. It's not just a matter of calling <cfhttp>, which is about where my experience of consuming REST web services extends to. On our project we use something called GuzzleHttp. Which also sounds pretty bloody cool, actually.

Oh... that wasn't the last bit. We're also using Twig as our templating engine, and whilst I had not specific qualms about our usage of it, I simply didn't know how it worked, so wanted to have a brief look at that too. Well: one qualm. I dunno why mark-up-aware languages like PHP and CFML actually need a templating engine... given that's what the languages themselves are for. PHP's less good at it than CFML is (no custom tags, for example), but IMO all the templating engines I've seen make sense in the context of the languages they do the templating for, but most of the considerations justifying a specific additional tier for views just doesn't apply with either PHP or CFML. But "it's the way it's done", so be it.

I'm in Ireland this weekend, and I had Saturday and Sunday afternoons free to either stare at walls (of my B&B or Shannon Airport, respectively), or write some code. So my weekend mission was to install Silex, Pimple, GuzzleHttp, Twig, etc, and write a proof of concept site doing some routing, some DI, and some REST requests and displaying them with some Twiggage. I had a false start yesterday as the wireless at the pub (which is where I do my work on Saturdays) was off when I arrived, so I had to do something which didn't require any RTFM. So I continued to work on "Learn CFML in 24 hours". I knocked out another coupla thousand words, which was good. It was only today @ Shannon I've been able to work on the PHP stuff. "fortunately" my flight has been delayed so I have had a good few hours sitting here with a Guinness and messing around with this PHP stuff.

Pleasingly... it's only taken me a coupla hours to knock out the basic skeleton of what I wanted to test, I'm moderately happy with it, and it covers all the bases. And I think I know how everything works now, too. Hopefully. Here it is.

Installation

This is just a pleasure. It's all done by Composer, and it all just works. This probably wouldn't be mention-worthy for most people, but coming from the CFML world (where CommandBox is only just now beginning to offer any sort of package management), it's a great new experience. I've already got Composer installed, so all I need for my app is the composer.json file:

{
    "require": {
        "silex/silex": "~1.1",
        "symfony/config": "~2.6",
        "symfony/yaml": "~2.6",
         "guzzlehttp/guzzle": "~5.0",
        "twig/twig": ">=1.8,<2.0-dev"
    },
    "autoload": {
        "psr-4": {
            "dac\\silexdemo\\": "src/",
            "app\\": "app/"
        }
    }
}

I then do composer update from the command line, and everything installs. Oh the PSR-4 stuff is just my namespacing for my own code, which Composer looks after the autoloading of these days too. Nice.

My plan is to create a site which has a single route: /user/n/ where n is an ID, and the site will dash off and call the REST service, get the requested user, and then - via Twig - output the details (the details being : ID, firstName, lastName). That's it. I wanted to use the minimum of code (whilst still architecting things adequately), and especially a minimum of procedural code. I wanna be using OO, not just PHP script files.

File organisation

My app is organised as follows:

phpsilex.local/ - app root
 composer.json
 composer.lock
 vendor/ - Silex, Twig, Pimple, Symfony, GuzzleHttp stuff, as installed by Composer
 app/ - app bootstrap stuff
  app.php
  Dependencies.php
  Routes.php
 config/ - non PHP files
  routes.yml
 src/ - the code for the app itself
  beans/
   User.php
  controllers/
   User.php
  services/
   User.php
  views/
   user.html.twig
 public/ - site root (only this stuff is web browseable)
  index.php

Website code

As one has come to expect these days, the publicly accessible code is minimal:

<?php
// index.php
require __DIR__ . '/../app/app.php';

It just bootstraps the app.

Application config and bootstrap

<?php
// app.php

use Silex\Application;

require_once __DIR__.'/../vendor/autoload.php';


$app = new Application();
$app["debug"] = true;

$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app->register(new Silex\Provider\TwigServiceProvider(), [
    "twig.path" => __DIR__.'/../src/views'
]);

app\Dependencies::configure($app);
app\Routes::configure($app);

$app->run();

app.php does the boilerplate stuff of declaring the app to be a Silex one, and configuring it and running it. My two intrusions here are how I am doing the DI, and how I'm doing the routing.

The general Silex guidance seems to err towards procedural code, which sux a bit. I want to keep things as encapsulated as possible, and also making them as testable as possible. From start to finish. This means classes and methods. So I've put my dependency injection config into a class, as I have with the routing.

Dependencies (and their injection)


<?php
// Dependencies.php

namespace app;

use \dac\silexdemo\controllers;
use \dac\silexdemo\services;
use \dac\silexdemo\beans;
use GuzzleHttp\Client;

class Dependencies {

    static function configure($app){

        $app["controllers.hello"] = $app->share(function() {
            return new controllers\Hello();
        });        
        $app["controllers.user"] = $app->share(function($app) {
            return new controllers\User($app["twig"], $app["services.user"]);
        });        


        $app["services.user"] = $app->share(function($app) {
            return new services\User($app["factories.user"], $app["services.guzzle.client"]);
        });        


        $app["factories.user"] = $app->protect(function($id, $firstName, $lastName) {
            return new beans\User($id, $firstName, $lastName);
        });


        $app["services.guzzle.client"] = function() {
            return new Client();
        };
    }

}

Look how easy that lot is. Pimple is baked into Silex, so all I need to do is to stick a bunch of function expressions into the $app itself. Then when I come to use them, I just reference them in exactly the same way. This is still using configuration over convention (so like how ColdSpring would do it, more than how DI/1 or even more so WireBox would want to do it), and this suits me as well. But the configuration is clean because it uses actual code.This comment flies in the face of other comments I've made regarding separating config from code, but I think it's the appropriate handling here as the config actually is code: it's defining how code should be run.

Note that because everything here is a function expression, nothing actually runs to start with. I was concerned that if one had a huge dependency-injected application that there's be a lot of upfront overhead whilst the dependencies are executed. This would be disastrous with PHP needing to do this every request, but the DI config is only ever actually executed when it's needed. Nice.

There are three main conceits I'm leveraging here.

Firstly, the default syntax is to create a new object each time a service reference is used, eg:

$app["services.guzzle.client"] = function() {
    return new Client();
};

Each time I reference $app["services.guzzle.client"] in my code, I'll get a new Client object. This is fine in this situation, but for a lot of my services a singleton will be fine, so I use this syntax instead:

$app["controllers.user"] = $app->share(function($app) {
    return new controllers\User($app["twig"], $app["services.user"]);
});

The share() call there returns the same object every time. We have a lot of boilerplate code in our app currently which ensures this, and as far as I can tell it's simply not necessary.

We also have a lot of hand-written factory classes for handling creation of transient beans. This is baked into Pimple too:

$app["factories.user"] = $app->protect(function($id, $firstName, $lastName) {
    return new beans\User($id, $firstName, $lastName);
});

That basically defines a singleton factory for creating transient User beans. I didn't need any specific factory code at all: all I needed to do is to wrap my function expression with the protect() call. Nice one.

There are no-doubt situation wherein we might actually need a bespoke factory method or two, but I can't recall seeing any in our existing code. And certainly not for my purposes. Still: the way to handle this is to only write the code that's necessary. We don't need a factory for every transient if Pimple can handle it for us with configuration. We should only write the code that we need. Code that fulfills only a ceremonial or "just in case" role is bad code.

Aside: I can't help but think if we'd been using TDD whilst writing this lot, we'd have a lot less code to contend with, because TDD encourages the writing of only necessary code. Unfortunately a lot of this code was written before our current team existed, so we had no say in that particular decision.

That's my dependencies wired.

Routing


<?php
// Routes.php

namespace app;

use Silex\Application;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Routing\RouteCollection;

class Routes {

    static function configure($app){
        $app["routes"] = $app->extend("routes", function (RouteCollection $routes, Application $app) {
            $loader     = new YamlFileLoader(new FileLocator(__DIR__ . "/../config"));
            $collection = $loader->load("routes.yml");

            $routes->addCollection($collection);
            return $routes;
        });
    }

}

This code I copied from some blog or other (I'll try to dig it up, but don't have it in front of me just now... found it: "Scaling Silex applications (part II). Using RouteCollection"). The documented approach to routing in Silex is to write each of them with PHP code, within the app.php file. That's bloody awful. Not only do I want them out of the app.php file, I also want them out of PHP code completely. Route definitions are not code. Plain and simple. So this code above lets me define my routes using YAML (which I am unimpressed with as a format, but hey) instead:

# routes.yml
_hello:
    path:        /hello/{name}
    defaults:    { _controller: "controllers.hello:doGet"}

_user:
    path:        /user/{id}
    defaults:    { _controller: "controllers.user:getUser"}
    requirements:
        id:  \d+

I'm not doing much with the routing here, but you get the general idea. One thing I am doing here is that my controllers are DI-ed services (Silex's guidance is to do those inline too!), which the Symfony RouteCollection approach supports just fine. Note that controllers.user is a reference to the controller's definition in Dependencies.php, and getUser is the relevant method in same. I'll get to that.

Application code

Indeed I'll get to that now. Here's the controller;

<?php
// User.php

namespace dac\silexdemo\controllers;

class User {

    protected $twig;
    protected $userService;

    function __construct($twig, $userService){
        $this->twig = $twig;
        $this->userService = $userService;
    }

    function getUser($id){
        $user = $this->userService->getUser($id);

        return $this->twig->render('user.html.twig', array(
            'user' => $user,
        ));
    }

}

This shows the correlation between the DI config and the actual classes. From Dependencies.php:

$app["controllers.user"] = $app->share(function($app) {
    return new controllers\User($app["twig"], $app["services.user"]);
});

The controller needs to know about the User service so it can go grab some data, and it needs to know about Twig, which is configured for all the views. This is from app.php:

$app->register(new Silex\Provider\TwigServiceProvider(), [
    "twig.path" => __DIR__.'/../src/views'
]);

(I say "all": there's only the one view in this app).

getUser() calls the model (services.user) and renders the view (user.html.twig), passing the User fetched from the model to it.

Here's the model:

<?php
// User.php

namespace dac\silexdemo\services;

class User {

    protected $userFactory;
    protected $guzzleClient;

    function __construct($userFactory, $guzzleClient){
        $this->userFactory = $userFactory;
        $this->guzzleClient = $guzzleClient;
    }

    function getUser($id){
        $startTime = self::getElapsed("Start");

        $response = $this->guzzleClient->get('http://cf11.local:8511/rest/api/person/' . $id,["future"=>true]);
        self::getElapsed("After async guzzle call", $startTime);

        $response->wait();
        self::getElapsed("After wait() call", $startTime);

        $userAsArray = $response->json();

        $userFactory = $this->userFactory;
        $user = $userFactory($userAsArray["ID"], $userAsArray["FIRSTNAME"], $userAsArray["LASTNAME"]);

        return $user;
    }

    private static function getElapsed($message, $start=-1){
        if ($start == -1){
            $start = time();
        }
        error_log(sprintf("%s: %d", $message, time() - $start));
        return $start;
    }

}

There's a lot more code there than is necessary, as I am also recording some metrics when I run that code (getElapsed() and the calls to same).

I hit my REST web service using an async call to Guzzle, as this is also a requirement which we seem to have hand-coded instead of using what's provided for us already. I just wanted to make sure it all works. Here the Guzzle call returns a FutureResponse object rather than a result, per se. This lets the rest of the code continue whilst Guzzle makes the (non-performant) REST call. I'm being slightly contrived here, but it's not until the wait() call is made that the calling code will actually block until Guzzle is done. This would not be how I'd do this usually, but it demonstrates the concept. I've tweaked my web service to take five seconds to run, and if we look in the logs, we can see that the blocking only occurs when wait() is called:

Start: 0
After async guzzle call: 0
After wait() call: 5

There's a bunch of other stuff one can do with the future / promise pattern here, but that's a discussion for another day. This just proves Guzzle does a good job of making async HTTP calls, without any coding on our part.

The web service returns JSON, which Guzzle can expect, and I populate a new bean with this. This is where we use that config-free bean factory. Remember this from Dependencies.php?

$app["factories.user"] = $app->protect(function($id, $firstName, $lastName) {
    return new beans\User($id, $firstName, $lastName);
});

Getting and populating a new User bean is as simple as:

$userFactory = $this->userFactory;
$user = $userFactory($userAsArray["ID"], $userAsArray["FIRSTNAME"], $userAsArray["LASTNAME"]);

Calling $userFactory() is creating a new User object. Oh... the upper case key names are ColdFusion's fault, btw. It does that (uppercases stuff). Stoopid.

Here's the User bean:

<?php
// User.php

namespace dac\silexdemo\beans;

class User {

    protected $id;
    protected $firstName;
    protected $lastName;

    function __construct($id, $firstName, $lastName){
        $this->id = $id;
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    function getId(){
        return $this->id;
    }

    function getFirstName(){
        return $this->firstName;
    }

    function getLastName(){
        return $this->lastName;
    }

}

Note there's nothing in there to help the factory process: it's just a standard class.

That gets passed to the Twig file, which is then rendered:

{# user.html.twig #}

ID: {{ user.id }}<br>
First Name: {{ user.firstName }}<br>
Last Name: {{ user.lastName }}<br>

Note how I don't need to call the getter methods explicitly: Twig works that out for me. Because I'm accessing user.id, it knows to get that with getId(). Cool!

And all this renders the - very spectacular - result of:

ID: 1
First Name: Zachary
Last Name: Cameron Lynch


Job done.

That's about it, really. I've put Silex through its motions, testing its routing and its integration with Pimple. I've had a look at Guzzle, and rendered stuff with Twig.

From start to finish - including research and doc-reading time - this took about eight hours I guess. 4h the other day to do the installs and get the routing sorted out; another 4h today for the DI, controllers, model (including Guzzle) and view stuff. And maybe 2h to write up and proofread. Pretty quick. This is not a brag (at all), it demonstrates that everything is documented well, googling for stuff is easy with PHP because the community is so big, and that the apps actually do what they set out to do, and just get on with it, rather than throwing unnecessary shite at me (like some of my experiences with popular CFML solutions to similar situations).

And my flight's finally in the air, so I'm gonna shut this thing down and do something more entertaining. Sleep, probably.

Righto.

--
Adam