V for view

The view is the layer that takes care of the… view. In this layer, you find all the templates that render the HTML that the user gets. Although the separation between views and the rest of the application is easy to see, that does not make views an easy part. In fact, you will have to learn a new technology in order to write views properly. Let's get into the details.

Introduction to Twig

In our first attempt at writing views, we mixed up PHP and HTML code. We already know that the logic should not be mixed in the same place as HTML, but that is not the end of the story. When rendering HTML, we need some logic there too. For example, if we want to print a list of books, we need to repeat a certain block of HTML for each book. And since a priori we do not know the number of books to print, the best option would be a foreach loop.

One option that a lot of people take is minimizing the amount of logic that you can include in a view. You could set some rules, such as we should only include conditionals and loops, which is a reasonable amount of logic needed to render basic views. The problem is that there is not a way of enforcing this kind of rule, and other developers can easily start adding heavy logic in there. While some people are OK with that, assuming that no one will do it, others prefer to implement more restrictive systems. That was the beginning of template engines.

You could think of a template engine as another language that you need to learn. Why would you do that? Because this new "language" is more limited than PHP. These languages usually allow you to perform conditionals and simple loops, and that is it. The developer is not able to add PHP to that file, since the template engine will not treat it as PHP code. Instead, it will just print the code to the output—the response' body—as if it was plain text. Also, as it is specially oriented to write templates, the syntax is usually easier to read when mixed with HTML. Almost everything is an advantage.

The inconvenience of using a template engine is that it takes some time to translate the new language to PHP, and then to HTML. This can be quite time consuming, so it is very important that you choose a good template engine. Most of them also allow you to cache templates, improving the performance. Our choice is a quite light and widely used one: Twig. As we've already added the dependency in our Composer file, we can use it straight away.

Setting up Twig is quite easy. On the PHP side, you just need to specify the location of the templates. A common convention is to use the views directory for that. Create the directory, and add the following two lines into your index.php:

$loader = new Twig_Loader_Filesystem(__DIR__ . '/views');
$twig = new Twig_Environment($loader);

The book view

In these sections, as we work with templates, it would be nice to see the result of your work. We have not yet implemented any controllers, so we will force our index.php to render a specific template, regardless of the request. We can start rendering the view of a single book. For that, let's add the following code at the end of your index.php, after creating your twig object:

$bookModel = new BookModel(Db::getInstance());
$book = $bookModel->get(1);

$params = ['book' => $book];
echo $twig->loadTemplate('book.twig')->render($params);

In the preceding code, we request the book with ID 1 to the BookModel, get the book object, and create an array where the book key has the value of the book object. After that, we tell Twig to load the template book.twig and to render it by sending the array. This takes the template and injects the $book object, so that you are able to use it inside the template.

Let's now create our first template. Write the following code into view/book.twig. By convention, all Twig templates should have the .twig extension:

<h2>{{ book.title }}</h2>
<h3>{{ book.author }}</h3>

<hr>

<p>
    <strong>ISBN</strong> {{ book.isbn }}
</p>
<p>
    <strong>Stock</strong> {{ book.stock }}
</p>
<p>
    <strong>Price</strong> {{ book.price|number_format(2) }} €
</p>

<hr>

<h3>Actions</h3>

<form method="post" action="/book/{{ book.id }}/borrow">
    <input type="submit" value="Borrow">
</form>

<form method="post" action="/book/{{ book.id }}/buy">
    <input type="submit" value="Buy">
</form>

Since this is your first Twig template, let's go step by step. You can see that most of the content is HTML: some headers, a couple of paragraphs, and two forms with two buttons. You can recognize the Twig part, since it is enclosed by {{ }}. In Twig, everything that is between those curly brackets will be printed out. The first one that we find contains book.title. Do you remember that we injected the book object when rendering the template? We can access it here, just not with the usual PHP syntax. To access an object's property, use . instead of ->. So, this book.title will return the value of the title property of the book object, and the {{ }} will make Twig print it out. The same applies to the rest of the template.

There is one that does a bit more than just access an object's property. The book.price|number_format(2) gets the price of the book and sends it as an argument (using the pipe symbol) to the function number_format, which has already got 2 as another argument. This bit of code basically formats the price to two digital figures. In Twig, you also have some functions, but they are mostly reduced to formatting the output, which is an acceptable amount of logic.

Are you convinced now about how clean it is to use a template engine for your views? You can try it in your browser: accessing any path, your web server should execute the index.php file, forcing the template book.twig to be rendered.

Layouts and blocks

When you design your web application, usually you would want to share a common layout across most of your views. In our case, we want to always have a menu at the top of the view that allows us to go to the different sections of the website, or even to search books from wherever the user is. As with models, we want to avoid code duplication, since if we were to copy and paste the layout everywhere, updating it would be a nightmare. Instead, Twig comes with the ability to define layouts.

A layout in Twig is just another template file. Its content is just the common HTML code that we want to display across all views (in our case, the menu and search bar), and contains some tagged gaps (blocks in Twig's world), where you will be able to inject the specific HTML of each view. You can define one of those blocks with the tag {% block %}. Let's see what our views/layout.twig file would look like:

<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <div style="border: solid 1px">
        <a href="/books">Books</a>
        <a href="/sales">My Sales</a>
        <a href="/my-books">My Books</a>
        <hr>
        <form action="/books/search" method="get">
            <label>Title</label>
            <input type="text" name="title">
            <label>Author</label>
            <input type="text" name="author">
            <input type="submit" value="Search">
        </form>
    </div>
    {% block content %}{% endblock %}
</body>
</html>

As you can see in the preceding code, blocks have a name so that templates using the layout can refer to them. In our layout, we defined two blocks: one for the title of the view and the other for the content itself. When a template uses the layout, we just need to write the HTML code for each of the blocks defined in the layout, and Twig will do the rest. Also, to let Twig know that our template wants to use the layout, we use the tag {% extends %} with the layout filename. Let's update views/book.twig to use our new layout:

{% extends 'layout.twig' %}

{% block title %}
    {{ book.title }}
{% endblock %}

{% block content %}
<h2>{{ book.title }}</h2>
//...
</form>
{% endblock %}

At the top of the file, we add the layout that we need to use. Then, we open a block tag with the reference name, and we write inside it the HTML that we want to use. You can use anything valid inside a block, either Twig code or plain HTML. In our template, we used the title of the book as the title block, which refers to the title of the view, and we put all the previous HTML inside the content block. Note that everything in the file is inside a block now. Try it in your browser now to see the changes.

Paginated book list

Let's add another view, this time for a paginated list of books. In order to see the result of your work, update the content of index.php, replacing the code of the previous section with the following:

$bookModel = new BookModel(Db::getInstance());
$books = $bookModel->getAll(1, 3);

$params = ['books' => $books, 'currentPage' => 2];
echo $twig->loadTemplate('books.twig')->render($params);

In the preceding snippet, we force the application to render the books.twig template, sending an array of books from page number 1, and showing 3 books per page. This array, though, might not always return 3 books, maybe because there are only 2 books in the database. We should then use a loop to iterate the list instead of assuming the size of the array. In Twig, you can emulate a foreach loop using {% for <element> in <array> %} in order to iterate an array. Let's use it for your views/books.twig:

{% extends 'layout.twig' %}

{% block title %}
    Books
{% endblock %}

{% block content %}
<table>
    <thead>
        <th>Title</th>
        <th>Author</th>
        <th></th>
    </thead>
{% for book in books %}
    <tr>
        <td>{{ book.title }}</td>
        <td>{{ book.author }}</td>
        <td><a href="/book/{{ book.id }}">View</a></td>
    </tr>
{% endfor %}
</table>
{% endblock %}

We can also use conditionals in a Twig template, which work the same as the conditionals in PHP. The syntax is {% if <boolean expression> %}. Let's use it to decide if we should show the previous and/or following links on our page. Add the following code at the end of the content block:

{% if currentPage != 1 %}
    <a href="/books/{{ currentPage - 1 }}">Previous</a>
{% endif %}
{% if not lastPage %}
    <a href="/books/{{ currentPage + 1 }}">Next</a>
{% endif %}

The last thing to note from this template is that we are not restricted to using only variables when printing out content with {{ }}. We can add any valid Twig expression that returns a value, as we did with {{ currentPage + 1 }}.

The sales view

We have already shown you everything that you will need for using templates, and now we just have to finish adding all of them. The next one in the list is the template that shows the list of sales for a given user. Update your index.php file with the following hack:

$saleModel = new SaleModel(Db::getInstance());
$sales = $saleModel->getByUser(1);

$params = ['sales' => $sales];
echo $twig->loadTemplate('sales.twig')->render($params);

The template for this view will be very similar to the one listing the books: a table populated with the content of an array. The following is the content of views/sales.twig:

{% extends 'layout.twig' %}

{% block title %}
    My sales
{% endblock %}

{% block content %}
<table>
    <thead>
        <th>Id</th>
        <th>Date</th>
    </thead>
{% for sale in sales %}
    <tr>
        <td>{{ sale.id}}</td>
        <td>{{ sale.date }}</td>
        <td><a href="/sales/{{ sale.id }}">View</a></td>
    </tr>
{% endfor %}
</table>
{% endblock %}

The other view related to sales is where we want to display all the content of a specific one. This sale, again, will be similar to the books list, as we will be listing the books related to that sale. The hack to force the rendering of this template is as follows:

$saleModel = new SaleModel(Db::getInstance());
$sale = $saleModel->get(1);

$params = ['sale' => $sale];
echo $twig->loadTemplate('sale.twig')->render($params);

And the Twig template should be placed in views/sale.twig:

{% extends 'layout.twig' %}

{% block title %}
    Sale {{ sale.id }}
{% endblock %}

{% block content %}
<table>
    <thead>
        <th>Title</th>
        <th>Author</th>
        <th>Amount</th>
        <th>Price</th>
        <th></th>
    </thead>
    {% for book in sale.books %}
        <tr>
            <td>{{ book.title }}</td>
            <td>{{ book.author }}</td>
            <td>{{ book.stock }}</td>
            <td>{{ (book.price * book.stock)|number_format(2) }} €</td>
            <td><a href="/book/{{ book.id }}">View</a></td>
        </tr>
    {% endfor %}
</table>
{% endblock %}

The error template

We should add a very simple template that will be shown to the user when there is an error in our application, rather than showing a PHP error message. This template will just expect the errorMessage variable, and it could look like the following. Save it as views/error.twig:

{% extends 'layout.twig' %}

{% block title %}
    Error
{% endblock %}

{% block content %}
    <h2>Error: {{ errorMessage }}</h2>
{% endblock %}

Note that even the error page extends from the layout, as we want the user to be able to do something else when this happens.

The login template

Our last template will be the one that allows the user to log in. This template is a bit different from the others, as it will be used in two different scenarios. In the first one, the user accesses the login view for the first time, so we need to show the form. In the second one, the user has already tried to log in, and there was an error when doing so, that is, the e-mail address was not found. In this case, we will add an extra variable to the template, errorMessage, and we will add a conditional to show its contents only when this variable is defined. You can use the operator is defined to check that. Add the following template as views/login.twig:

{% extends 'layout.twig' %}

{% block title %}
    Login
{% endblock %}

{% block content %}
    {% if errorMessage is defined %}
        <strong>{{ errorMessage }}</strong>
    {% endif %}
    <form action="/login" method="post">
        <label>Email</label>
        <input type="text" name="email">
        <input type="submit">
    </form>
{% endblock %}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.62.122