PHP MVC - One view class for all controllers vs a view class for each controller

#1

Hi,

I am creating an MVC inspired PHP framework, mainly for learning purposes.
I basically have the framework created and am building an app on it and improving the framework as i go along.

I am still confused/not sure about certain aspects of this sort of architecture, and at the moment i am questioning my implementation of the View part.

How i have set up my framework:
Its a very simple set up, for example: you go to the url /post/post_id, this will load index.php which will instantiate the router. The router will then check the url and instantiate the correct controller and method based on the url. In this case it would be PostController, and the method would be a default method that would use the post_id to get the posts data from the relevant model. Next the controller would set up a “data” variable that will hold the data to pass on to the View, and this is where i am confused - should it send to its own View object (a view class file dedicated to the PostController), or to a generally used View class that is used by all controllers to load an html file?

At the moment my controller is sending data to the View class, this data includes what template file should be included/shown, and the actual data for the page (what we got from the Model through the controller).

My question is this:
Should this type of system have one View object that renders all of the views (html files) based on what data is given to the “render” method, or, should each controller that eventually sends data to the View have its own View object/class?

Meaning, should PostController send a request to the general view class, the same one that is used by all controllers to renders pages, or should the PostController send to a dedicated View Class (call it PostView if it makes it clearer), and this class will then render the specific html file?

Basically if it should be one View class for all controllers that will render what ever html file the controller tells it to, or if there should be many View classes, one for each page load.

#2

You are able to use single class instance only if this class stateless. Stateless view - is not really comfortable. That’s why you should have one view pro one HTML-template.

Another question: could it be instances of same class or specific classes that depend of its templates. I would like second variant.

#3

Thanks for the reply, i’m not sure if i understand what you mean though.

This is the process my controllers have for rendering a view:

  1. viewPost method in PostController gets the data for a specific post from the model, saves it to $viewData, then it calls the main (parent) controller method: renderView($viewData) - this method adds data to the viewData, data needed by all pages being rendered
  2. renderView($data) instantiates a new view class, e.g. new View($viewData)
  3. The view class calls its own renderView() method, this method actualls renders the page by including it (either a full page, including the header, footer, main content, etc, or just a single html file)

$viewData is an array that holds data used by the view (for example, post data)
Each controller that eventually requests to render something does this same thing, just with different data sent to the view.

#4

I think you are conflating two concepts here. Generally people use a Renderer class to render templates for them, and that Renderer receives view data. That view data can either be a plain old array, or an instance of some sort of View class. If you go with the View class, I would make it a DTO with a marker interface and create a separate one for each type of data being carried, such as a PostView, CommentView, etc.

The difference between plain old arrays and a View object is that latter can be used for some very basic display logic. For example

class Person implements View
{
    public $firstName;
    public $lastName;
    public $title;

    public function getFullName(): string
    {
        $name = $firstName.' '.$lastName;
        if (null !== $this->title) {
            $name .= ', '.$this->title;
        }
        return $name;
    }
}

Nothing too complex, just simple stuff.

So then you’d basically get something along these lines:

class PostController
{
    private $renderer;
    public function __construct(Renderer $renderer)
    {
        $this->renderer = $renderer;
    }

    public function showPost(): string
    {
        $post = obtain_post();
        $view = new PostView($post->title, $post->contents);

        return $this->renderer->render('some_template.html.tpl', $view);
    }
}

interface View
{
}

class PostView implement View
{
    public $title;
    public $contents;
}

class Renderer
{
    public function render(string $template, View $view): string
    {
        ob_start();
        // add error handling here, catch any errors and render error template instead
        include $template;
        $view = ob_get_clean();
      
        return $view;
    }
}
#5

Where are you returning this to in the end?
I see that its going back to this return $this->renderer->render('some_template.html.tpl', $view);, but where does that return to?

Are you outputting that in the index file or where ever you call the router from?

#6

Eventually outputting that in the index.php yes. There will surely be some abstractions between index.php and the controller, so it has to travel through those, but eventually it will end up being printed.

This is similar to Symfony’s Request/Response cycle. See https://symfony.com/doc/current/introduction/http_fundamentals.html

#7

So in index i would for example do something like this?

$app = new Router();
echo $app;

What would be the difference between outputting it in the index file, compared to outputting it directly from the view class?

I have a main view class that has a render function, and this function gets all of the data it needs for the view from the controller, and then it includes the view html file.

something like:

public function render($viewFile, $viewData){
    include($viewFile);
}

And the view file has access to the $viewData.
(That’s not exactly how i have it set up, its just an example of what i mean).

Is that wrong or considered bad practice?

#8

Outputing is kind of dependency. And this dependency absolutelly not required for view object. View object should return some string. That’s all.

E.g. if you would to form your e-mail body with some view. If your view makes directly output, you should in any thouse case use ob_ functions. That is not really clean code.

#9

I don`t really understand that, why should the view object return the view back to the index.php file instead of just outputting it itself?
I mainly don’t understand what the difference would be.

#10

…Request -> Middleware -> Response. Single point for request and also single point for response. Then you can completelly control it. Now you would to use many output points.

…Response could be not only single view. It could be composition of many views, it could be also some headers there, it could be another content type.

#11

The problem with directly outputting the view is that functions that do that aren’t referentially transparent. That means that the function produces a side effect. Due to that, you can’t do exception handling well, because you’ve already output something, and you can’t take that back.

Most frameworks work with a big try/catch that finally produces a string (the output), which can then be safely echoed because echo won’t throw exceptions.

Also, as @igor_g says, when you return the response instead of printing it directly, you get a chance to use the middleware pattern. See PSR-7 / PSR-15.

#13

ok, i understand a little bit better now, thanks!

So would this be correct then:

  1. A user loads the page and gets sent to index.php
  2. index.php calls the Router() class
  3. The Router() class instantiates the correct controller and method
  4. The controller sets up all of the required data (from model too if needed), and sends a request to the View class
  5. The view class returns a string of everything that needs to be outputted to the controller
  6. The controller sends that back to the router
  7. The router sends that back to the index.php file
  8. The index.php file echos out that string

Is that somewhat correct?

#14

Not completelly. As I said, response could have another content type than text/html. That’s why your live cycle actually correct, but not universal.

#15

But would this be correct if at the moment i only return text/html, and it also allows me to implement other responses in he future if needed?

Or is this just not efficient and correct?

#16

Sorry, but not. You return some string. There is no ability to extend it to universal response.

You need some specific response object, that includes and able to output at first headers and at second body of any content type. And your view is just some kind of response’s Body. One from the lot.

#17

ok now you lost me :sweat_smile:

so, i need the option to first output headers, and then the actual view i want?

If so, at least now i actually have the view in a string, so now i just need to implement a way to set different headers?

#18

Yes. Content type, site status, specific download headers and so on… And different bodies as xml, json, bytes stream, image… Without it all you could have just very limited application.

#19

ok, where would those normally be set? would they be set in the controller?

#20

Genearally, yes. But better to pack all required functionality to resonse package.

#21

which means that there should be a separate entity that checks which headers should be set for the current page, and to include it in the response that is returned to the index.php file?