29 January 2018

Fun with Slim

I'm working on a smallish project to build service endpoints using Slim. (We've used other frameworks in the past. I recently converted my model code to a PSR-4 autoloader. The team strongly suggested Slim for this project.)

The Slim documentation makes it abundantly clear that Response objects are immutable: each method that accepts a Response as an argument returns a copy of that object. You can also puzzle out that methods like withHeader() and withStatus() can be chained together. And the docs explain how to use withJson() if you want your endpoint's payload to be JSON and you're content with the default MIME type.

What is not so well explained: that getBody()->write() does not return a Response, so it can't be chained. Nor is it clear how to use these methods when you want to set your own MIME type ("application/hal+json", in my case) or status code. Hence, this example, somewhat simplified.

The GET /short endpoint returns a brief HAL representation of the resource identified by :id.



namespace Demo;

class Controller {
    /**
     * GET /short/:id
     * 
     * @param \Slim\Http\Request $request
     * @param \Slim\Http\Response $response
     * @param string[] $args
     * @return \Slim\Http\Response
     */
    public function short($request, $response, $args) {
        $id = $args['id'];
        try {
            $guid = ...;  //look up info about $id
            if (empty($guid)) {
                Logger::warning("No info for $id");
                return $response->withStatus(404);
            }
            $doc = ...; // construct a model, using $guid
            $rsp = $response->withAddedHeader('Content-type', 'application/hal+json');
            $rsp->getBody()->write(json_encode($doc->getDocArray()));
            return $rsp;
        }
        catch (Exception $e) {
            Logger::warning($e->getMessage()." Failed");
            return $response->withStatus(500);
        }
    }
}

And how do you write a unit test for this endpoint? Likewise, the docs give some strong hints, but leave you to connect the dots. The trick is to use Environment::mock() to get an object that can be passed to a factory method on Request. A simplified version of my happy-path unit test:


class ControllerShortTests extends \PHPUnit_Framework_TestCase
{
    /** @var \Slim\Http\Request */
    private $request;
    /** @var \Slim\Http\Response */
    private $response;
    /** @var string[] */
    private $args;
    /** @var \Slim\Container */
    private $container;
    /** @var \Demo\Controller */
    private $controller;

    public function setUp()
    {
        $this->request = \Slim\Http\Request::createFromEnvironment(\Slim\Environment::mock());
        $this->response = new \Slim\Http\Response();
        $this->args = [];
        $this->container = new \Slim\Container();
        ... // set up a mock datastore provider
        $this->controller = new \Demo\Controller($this->container);
    }

    public function testExpectSuccess()
    {
        ... // configure the provider
        $this->args['id'] = 987654321;
        $response = $this->controller->short($this->request, $this->response, $this->args);
        $this->assertEquals(200, $response->getStatusCode(), 'Wrong status');
        ... // and more assertions
    }
}

14 January 2018

Gaming the benchmarks

What's holding us back from exascale computing? According to Katherine Bourzac's report, the simple answer is power.