Search code examples
phpmodel-view-controllerresponse

PHP MVC pass data in response object to body(view)?


I am using php + blade and wrote simple mvc and now I am wondering how to pass data to view. My response object contains header, status code and body(view). Is it ok to pass data to response body section next to view?
->setBody(['view'=>'result','somedata'=>$somedata]);

or response object should have statuscode, header, body(view), and data separately from body(view)?

now in controller i have:

return Response::create()
    ->setStatusCode(200)
    ->setHeader(['Content-Type' => 'text/html'])
    ->setBody(['view'=>'result']);

my response class:

class response implements ResponseInterface
{
    protected $statusCode;
    protected $headers;
    protected $body=[];

    public function setStatusCode(string $code) :object
    {
        $this->statusCode = $code;
        return $this;
    }

    public function setHeader(array $header):object
    {
        $this->headers=$header;
        return $this;
    }

    public function setBody(array $body): object
    {
        $this->body = $body;
        return $this;
    }

    public function getStatusCode():string
    {
        return $this->statusCode;
    }
    public function getHeaders():array
    {
        return $this->headers;
    }
    public function getBody():array
    {
        return $this->body;
    }
    public static function create(string $statusCode = null, array $headers = null, string $body = null): response
    {
        return new Response($statusCode, $headers, $body);
    }
    public function send(): void
    {
        foreach ($this->headers as $header => $value) {
            header(strtoupper($header).': '.$value);
        }

        if ($this->headers['Content-Type']==="text/html" && $this->statusCode === "200") {
            View::renderTemplate($this->body['view']);
        }


    }
}

i am able to pass data like this, by adding fx datatable variable in controllers and it works:

 return Response::create()
        ->setStatusCode(200)
        ->setHeader(['Content-Type' => 'text/html'])
        ->setBody(['view'=>'result','datatable'=>$datatable]);

and then need to adjust response send action to:

public function send(): void
{
    foreach ($this->headers as $header => $value) {
        header(strtoupper($header).': '.$value);
    }

    if ($this->headers['Content-Type']==="text/html" && $this->statusCode === "200") {
        View::renderTemplate($this->body['view'],'datatable'=>$this->body['datatable']);
    }

}


Solution

  • Response factory

    A Response instance shouldn't be responsible for creating a Response object. This task should be performed by a Response factory. An example:

    class ResponseFactory extends MessageFactory implements ResponseFactoryInterface {
    
        //...
    
        public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface {
            return new Response($this->body, $code, $reasonPhrase, $this->headers, $this->protocolVersion);
        }
    
        //...
    
    }
    

    Response emitter

    A Response instance shouldn't be responsible for sending the response headers of a HTTP response. This task, along with printing the content of the Response body to screen, should be performed by a Response emitter. An example:

    class ResponseEmitter implements ResponseEmitterInterface {
    
        //...
    
        public function emit(ResponseInterface $response): void {
            $this
                ->checkHeadersSent()
                ->sendStatusLine($response)
                ->sendResponseHeaders($response)
                ->outputBody($response)
            ;
    
            exit();
        }
    
        //...
    
    }
    

    Template renderer/engine

    A Response instance shouldn't be responsible for rendering template files (also known as views). So, it shouldn't be coupled to any template renderer or View instance in order to render template files. The task of rendering template files should be performed by a template renderer/engine. An example:

    class TemplateRenderer extends BaseTemplateRenderer implements TemplateRendererInterface {
    
        //...
    
        public function render(string $templateName, array $context = []): string {
            $templateFile = $this->templatesPath . $this->buildTemplateName($templateName);
    
            $this
                ->validateTemplateFile($templateFile)
                ->saveContextValuesToContextCollection($context)
            ;
    
            return $this->renderTemplateFile($templateFile);
        }
    
        private function renderTemplateFile(string $filename): string {
            ob_start();
    
            extract($this->contextCollection->all());
    
            require $filename;
    
            $content = ob_get_contents();
    
            ob_end_clean();
    
            return $content;
        }
    
        //...
    
    }
    

    The rendered content (a string!) is then passed to the body of a Response instance.

    Example

    Here is the example of a class, whose method addUser is responsible for rendering the given template file, creating a Response instance, writing the rendered content to the Response body and returning the Response.

    /**
     * A view to handle the users.
     */
    class Users extends Primary {
    
        //...
    
        /**
         * Add a user.
         * 
         * @return ResponseInterface The response to the current request.
         */
        public function addUser(): ResponseInterface {
            $bodyContent = $this->templateRenderer->render('@Templates/Users/Users.html.twig', [
                'activeNavItem' => 'Users',
                'message' => 'User successfully added',
                'users' => $this->getAllUsers(),
            ]);
    
            $response = $this->responseFactory->createResponse();
            $response->getBody()->write($bodyContent);
    
            return $response;
        }
    
        //...
    
    }
    

    The Response returned by Users:addUser is further processed by a Response emitter. Its body content is finally printed on screen.

    // Create a request.
    $request = $container->get('request');
    
    // Create a middleware queue.
    $middlewareQueue = $container->get('middleware-queue');
    
    // Handle the request and produce a response.
    $response = $middlewareQueue->handle($request);
    
    // Emit the response.
    $responseEmitter = $container->get('response-emitter');
    $responseEmitter->emit($response);
    

    Back to... your simplified Response

    So, in your case, Response could be simplified to the following. It is up to you to develop the rest of the components - the ones that I mentioned above.

    class Response implements ResponseInterface {
    
        public function getStatusCode(): int {
            //...
        }
    
        public function setStatusCode(int $code): static {
            //...
        }
    
        public function getHeaders(): array {
            //...
        }
    
        public function setHeader(array $header): static {
            //...
        }
    
        public function getBody(): string {
            //...
        }
    
        public function setBody(string $content): static {
            //...
        }
    
    }
    

    Note: If you are using return $this; in a method, then use static instead of object as the returned data type.

    Some suggestions

    If I may, I would suggest you to develop an MVC-based application which uses external libraries for its components. For example, instead of developing your own implementation of HTTP message (Request, Response, URI, uploaded files, etc) - which would take you a very long time!, I'd recommend to use Laminas Diactoros package. Or to use Twig as the template engine and renderer for your application. And so on.

    Note: By the way, the render method of the Twig template engine will give you the answer to your question (Is it ok to pass data to response body section next to view?).

    Also, for your PHP applications, if you didn't do it already, I'd suggest to follow the PHP Standards Recommendations.

    Resources