Joe's
Digital Garden

View

Views in web applications tend to either be expressed in the HTML or JSON types. Less commonly these days, variations of XML. Conversion of the response into JSON, and in particular the JSON-API specification is readily handled by existing libraries. However, my experience with HTML views remains mixed.

In general, I see the direction of PHP development heading towards expressing the domain in the form of a REST API that returns responses in the JSON-API format. This back-end feeds into a front-end expressed in HTML and Javascript which manages the complex interactions of UX state. This makes the "view" layer of the application extremely simple -- the view is just the JSON serialization of the entities returned by the [[Application Service]] layer.

Simpler HTML based monoliths still exist though. In these cases, the view is expressed in two parts: a View object (sometimes called a View model) and a view template.

The View object is created by the Controller and then fed the various Entities returned from the [[Application Service]] layer. This ensures that all properties required by the View are either set or set to some known default. It also allows us to perform any last minute formatting of the data such as [[datetime handling]], normalizing number or currency representations (trailing zeros, comma vs periods, currency signs), etc. We should avoid any kind of business logic in the View model. Simple formatting is one thing, but no where should we declare new symbols nor modify existing symbols except changing their expression -- e.g. converting $20.00 to $20,00 does not change the value, but the expression 20.00 + 20.00 does.

The below is a very simple expression of a View object and template. As is, there exists many good libraries that will net you this and more fine-tuned functionality for hitting all the obscure points for generating valid HTML mark up. However, as I've seen it, most boil down to these two components.

The View object:

interface IView
{
    /**
     * Render the view
     */
    public function render(): string

    /**
     * Set the page title
     * @return $this
     */
    public function setTitle(): IView
}

class CustomerView implements IView
{
    private $Customer;
    private $template = './templates/customer.html.php';
    private $title = '';

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

    public function setTitle(string $title): CustomerView
    {
        $this->title = $title;
        return $this;
    }

    public function render(): string
    {
        ob_start();
        include $this->template;
        return ob_get_clean();
    }
}

$View new CustomerView((new CustomerRepository($pdo))->customer(10));
$View->setTitle("Customer Page");
echo $View->render();

As you can see, the interface is simple. The Controller creates a new CustomerView, passing the required entity (a Customer) into the constructor. The builder pattern can be deployed to chain together adding all optional data points. If the builder methods are not called, a sensible default is provided.

The render method is then used to include the template file in a buffer which has access to the $this object for retrieving any of the private properties off the view class. The buffer is returned as a string, and echoed out by the Controller.

The Template

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" type="text/css" href="/css/style.css" media="all" />
    <link href="/media/favicon.ico" rel="icon" type="image/x-icon">

    <title><?= $this->title ?></title>
</head>

<body>
<h1>Hello, <?= $this->Customer->fullName() ?></h1>

Your addresses are:

<?php foreach($this->Customer->addresses() as $key => $Address): ?>
    <h3>Address #<?= $key + 1 ?></h3>
    Street: <?= $Address->street() ?>
    City: <?= $Address->city() ?>
    State: <?= $Address->state() ?>
    Zip: <?= $Address->zip() ?>
<?php endforeach; ?>

</body>

</html>

We may build out templates to handle more complex use cases, such as providing a common header, injection of external stylesheet or javascript assets, or an outer wrapper to provide a consistent layout to the site. In it's simpliest form the above can represent an entire HTML page.

Linked References

  • layered-web-application-architecture
    • The Controller uses Repositories to Persist any Models returned and passes their values to a [[View]] (can be of HTML or JSON media types), renders the View and returns it to the Client.