Skip to main content

Anatomy of a typical page (v2)

Before diving into an explanation of Tuskfish subsystems it may be useful to step through the execution of a sample page. This will show the sequence of events and help place the subsystems in context. Tuskfish uses a front controller and MVVM(C) architecture.

So, let's take a request to your home page as our example and step through it, as it uses all Tuskfish subsystems. The default Tuskfish homepage presents a blog-style feed of the latest content, ie. a list of thumbnails and teasers. It has pagination controls that can step back through the entire content of the site, and it can be filtered by tag.

Before we start: Strict types

Tuskfish uses strict type checking of parameters. The first line of every PHP file must be:

declare(strict_types=1);

In HTML templates, the declaration is made as a PHP snippet in the first line of every file:

<?php declare(strict_types=1); ?>

Routing

When a (non-file) request comes in, Apache will refer it to index.php. This behaviour is governed by the .htaccess file in the site route, which contains the following directive (if you have access to the virtual host file, you can move the directive there instead):

FallbackResource /index.php

index.php begins bootstrapping Tuskfish by including / instantiating essential resources:

  • mainfile.php:  Defines constants for the site root, trust_path (protected files outside of web root), site URL and includes config.php, which in turns defines most other file path constants including a reference to the SQLite database.
  • header.php: Initialises the DICE dependency injection container, which automates instantiation of Tuskfish components, includes the core language files and initialises logging. It also makes the xss() anti cross-site scripting function available system-wide.
  • routingTable.php: A lookup table (array) listing the MVVM components required to generate each route as classnames, with URL path as key.
  • Instantiates a Route object, which instantiates and holds references to the required MVVM components, based on the routing table.
  • Instantiates the front controller, passing in the Route object (which holds the class names of the required MVVM components).
...
// Access trust path, DB credentials, configuration and preferences.
require_once 'mainfile.php';
require_once TFISH_PATH . 'header.php';

// Routing table for front end controller is declared here for convenient editing.
$routingTable = require_once TFISH_PATH . 'routingTable.php';
...
// Route and process request.
$router = new Router($routingTable); 
$route = $router->route($relativePath); // Holds class names for the MVVM components sourced from the routing table.
$dice->create('\\Tfish\\FrontController', [$dice, $route, $relativePath]); // MVVM class names are passed in via the $route.

This is an example of the routing table entry for the home page (route '/'), the entries are namespaced class names:

// From trust_path/libraries/tuskfish/routingTable.php
return [
    '/' => new Route(
        '\\Tfish\\Content\\Model\\Listing',
        '\\Tfish\\Content\\ViewModel\\Listing',
        '\\Tfish\\View\\Listing',
        '\\Tfish\\Content\\Controller\\Listing',
        false), // Flag indicating if this route is restricted to admins (true) or public (false).
    ...
];

Front controller

The front controller \Tfish\FrontController orchestrates the page load cycle. Its responsibilities include:

  • Initialising the session.
  • Checking if the site is closed.
  • Checking if the route is restricted (admin-only access).
...
$this->session = $session;
$session->start();
$this->checkSiteClosed($preference, $path);
$this->checkAdminOnly($route);
...
  • Instantiating the MVVM(C) component classes needed to generate this route, note the use of the DICE dependency injection container to automate the process.
...
// Create MVVM components with dice (as they have variable dependencies).
$pagination = $dice->create('\\Tfish\\Pagination', [$path]);
$model = $dice->create($route->model());
$viewModel = $dice->create($route->viewModel(), [$model]);
$this->view = $dice->create($route->view(), [$viewModel]);
$this->controller = $dice->create($route->controller(),[$model, $viewModel]);
...
  • Extracting an action parameter (if present) from the request, and running it against the MVVM controller method of the same name, thereby starting execution of the MVVM triad, which is where most of the processing occurs (described in a separate section below). If no action is specified 'display' will be used by default. Actions may only consist of alphabetical characters. If the controller doesn't have a method name matching an action an error will be thrown (ie. the controller's methods whitelist actions).
  • Commence output buffering.
  • Checking the cache: The controller's "action" method will pass back a list of parameters used to identify the cached version of this page (if any). The front controller will ask the cache to check if a cached copy of this page exists. If so (and if caching is enabled in site preferences), the cache will return the prebuilt copy of the page, the buffer will flush output to the browser and execution will stop.
$action = $this->trimString(($_REQUEST['action'] ?? 'display'));

if (!$this->isAlpha($action) || !method_exists($this->controller, $action)) {
        \trigger_error(TFISH_ERROR_BAD_ACTION, E_USER_NOTICE);
        \header('Location: ' . TFISH_URL . 'error/');
        exit;
    }

    \ob_start();

    $cacheParams = $this->controller->{$action}();
    $cache->check($path, $cacheParams);
    ....
  • If no cached page is available (or if cache is disabled in site preferences, execution will proceed to generate a copy of the page through renderLayout(). If caching is enabled, a copy of the page will be saved to cache via save().
  • Cleaning up: The front controller will close the database connection and flush the output buffer to the browser.
    ...
    $this->renderLayout($metadata, $viewModel);
    $cache->save($cacheParams, \ob_get_contents());
    $database->close();

    return \ob_end_flush();
}

That's a high level overview of a page load. But as mentioned, the bulk of processing actually occurs in the MVVM triad components, which begin running when the action method is called on the MVVMC controller.

Execution of the MVVM(C) triad

For a walk through of the MVVM(C) components please refer to Overview of the architecture.

Further reading

For a more thorough grounding, I suggest starting with index.php and working your way through the code execution for the default route '/'. It is useful to see a complete example of parameter validation, preparation of filter criteria, model structure and use of the viewmodel to access data from the model within templates.

Copyright, all rights reserved.