Search code examples
phprestapislimmiddleware

Pass array from controller to middleware Slim 3 PHP


I'm trying to pass an array with data to middleware and format it based on Accept HTTP header.
The controller gets the data from db and should pass it to the response object. Response object write() method accepts only strings:

public function getData(Request $request, Response $response): Response {
    return $response->write($this->getUsers());
    # This line of code should be fixed
}

The middleware should get the response and format it correctly:

public function __invoke(Request $request, Response $response, callable $next) {
    $response = $next($request, $response);
    $body = $response->getBody();

    switch ($request->getHeader('Accept')) {
        case 'application/json':
            return $response->withJson($body);
            break;
        case 'application/xml':
            # Building an XML with the data
            $newResponse = new \Slim\Http\Response(); 
            return $newResponse->write($xml)->withHeader('Content-type', 'application/xml');
            break;
        case 'text/html':
            # Building a HTML list with the data
            $newResponse = new \Slim\Http\Response(); 
            return $newResponse->write($list)->withHeader('Content-type', 'text/html;charset=utf-8');
            break;
    }
}

I have a few routes behaves similarly:

$app->get('/api/users', 'UsersController:getUsers')->add($formatDataMiddleware);
$app->get('/api/products', 'UsersController:getProducts')->add($formatDataMiddleware);

By using middlewares I can add such functionality in a declarative way, keeping my controller thin.

How can I pass the original data array to response and implementing this pattern?


Solution

  • The Response-Object doesn't provide this functionallity nor has some extensions to do that. So you need to adjust the Response-Class

    class MyResponse extends \Slim\Http\Response {
        private $data;
        public function getData() {
            return $this->data;
        }
        public function withData($data) {
            $clone = clone $this;
            $clone->data = $data;
            return $clone;
        }
    }
    

    Then you need to add the new Response to the Container

    $container = $app->getContainer();
    $container['response'] = function($container) { // this stuff is the default from slim
        $headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']);
        $response = new MyResponse(200, $headers); // <-- adjust that to the new class
    
        return $response->withProtocolVersion($container->get('settings')['httpVersion']);
    }
    

    Now change the Response type to MyResponse and use the withData method

    public function getData(Request $request, \MyResponse $response): Response {
        return $response->withData($this->getUsers());
    }
    

    At the end you can use the getData method and use its value and process it inside the middleware.

    public function __invoke(Request $request, \MyResponse $response, callable $next) {
        $response = $next($request, $response);
        $data = $response->getData();
        // [..]
    }
    

    That would be the answer to your question. A better solution in my opinion, would be a helper class which does what your middleware does, then you could something like this:

    public function getData(Request $request, Response $response): Response {
        $data = $this->getUsers();        
        return $this->helper->formatOutput($request, $response, $data);
    }
    

    For that there is already a lib for: rka-content-type-renderer