Fun with Slim

David Gorsline
Technology at NPR
Published in
2 min readFeb 1, 2018

--

I’m working on a smallish project to build service endpoints in PHP using the Slim framework. (We’ve used other frameworks in the past but have recently begun to converge on Slim as our go-to because it is lightweight but supports the PSR-7 HTTP message interface standard, which is a requirement for interoperability with several other libraries we use. As part of this work, I also recently converted my model code to a PSR-4 autoloader.)

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 for this test
$this->args['id'] = 987654321;
$response = $this->controller->short(
$this->request, $this->response, $this->args);
$this->assertEquals(200, $response->getStatusCode(),
'Wrong status');
... // and more assertions about the result
}
}

Originally published, in somewhat different form, at iefbr14.blogspot.com on 29 January 2018.

--

--