Vignesh
Vignesh

Reputation: 1063

having trouble with slimframework's Immutable responses

I am trying to setup a project for an API using slim framework version 3, I don't know who made the PSR-7 and marked the response object as immutable, I don't see any use in that (IMHO. please explain me if I am wrong). Things were much easier when it was slim 2. Now I came back to slim after a long time.

I have a route which is a post method, I am getting data and saving it to the database and I am trying to send 201 as the response code. all the examples and the documentation is showing you how can you change the response code within the index.php file itself, But I am trying to change it from a response builder which I have tried to use the factory pattern to provide different responses. The problem is the response code always stays 200 no matter what function I call from the response builder class. I tried many forums and different ways of slim but still couldn't able to pull this up. I almost decided to give up on a PSR 7 router implementation and going to implement my own routing solution. But I remember not to reinvent the wheel again so I came here as a final try. Below is the code.

the route definition

$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res) {
    $data = $req->getParsedBody();
    $model = new \Apex\Models\User(ApexDB::getInstance());
    $jsonBuilder = ApexResponse::getBuilder('JSON', $res);
    $control = new \Apex\Controllers\User($model, $jsonBuilder);
    $control->create($data);

});

the controller method (abstract I am just setting it up)

public function create($data) {
        if($this->model->save($data)) {
            $this->response->build($data,201);
        } else {
            $this->response->build('error',400);
        }
    }

the JSON builder

class JSONBuilder implements Response
{
    public $response;

    public function __construct($response)
    {
        $this->response = $response;
    }

    public function build($data, $status)
    {
        $response = $this->response->withJSON($data,$status);
        return $response;
    }
}

can anyone point me in the right direction?

Upvotes: 0

Views: 205

Answers (1)

Rob Allen
Rob Allen

Reputation: 12778

The PSR-7 decision to use immutable objects for Request and Response is documented in the Why value objects? section of the Meta document.

With Slim 3, you must always return a Response instance from the controller method.

$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res): ResponseInterface {
    $data = $req->getParsedBody();
    $model = new \Apex\Models\User(ApexDB::getInstance());
    $jsonBuilder = ApexResponse::getBuilder('JSON', $res);
    $control = new \Apex\Controllers\User($model, $jsonBuilder);

    return $control->create($data);
});

and then your create method also needs to return the $response:

public function create($data) {
    if ($this->model->save($data)) {
        $this->response->build($data, 201);
    } else {
        $this->response->build('error', 400);
    }

    return $this->response;
}

It should then work.

However, you can use the controller method directly from the route declaration and avoid the need for a the closure:

$app->post('/users', `Apex\Controllers\User::create`);

The controller's create method would then look like this:

namespace Apex\Controllers;

class User
{
    public function create($request, $response)
    {
        $data = $request->getParsedBody();

        $model = new \Apex\Models\User(ApexDB::getInstance());
        $jsonBuilder = ApexResponse::getBuilder('JSON', $response);

        if ($model->save($data)) {
            $response = $jsonBuilder->build($data, 201);
        } else {
            $response = $jsonBuilder->build('error', 400);
        }

        return $response;
    }
}

Finally, consider rka-content-type-renderer instead of JsonBuilder, though maybe it does more than you've shown.


Update:

Ideally you'd use constructor injection to inject the User model into the controller. To do this:

  1. Update your controller:

     namespace Apex\Controllers;
    
     use Apex\Models\User as UserModel;
    
     class User
     {
         protected $userModel;
    
         public function __construct(UserModel $userModel)
         {
             $this->userModel = $userModel;
         }
    
         public function create($request, $response)
         {
             $data = $request->getParsedBody();
    
             $jsonBuilder = ApexResponse::getBuilder('JSON', $response);
    
             if ($this->userModel->save($data)) {
                 $response = $jsonBuilder->build($data, 201);
             } else {
                 $response = $jsonBuilder->build('error', 400);
             }
    
             return $response;
         }
     }
    
  2. Write a factory for the Pimple dependency injection container:

     $container = $app->getContainer();
     $container['Apex\Controllers\User'] = function ($c) {
         $userModel = new \Apex\Models\User(ApexDB::getInstance());
         return new \ApexController\User($userModel);
     };
    

Upvotes: 2

Related Questions