Skip to main content

Overview of the architecture (v2)

Tuskfish v2 has a MVVM(C) architecture, with a front end controller to handle routing. It is very similar to MVC, except the 'view' is split into a 'view' and 'view model' to improve separation of concerns. I recommend a read of Tom Butler's Programming Blog for an explanation of the difference between MVC and MVVM (Tuskfish v2 is designed based on the concepts in this article). Each page (route) requires instantition of the front end controller, plus a specific triad of MVVM(C) components required to build that page. 

The MVVM architecture forces strict partitioning of code, improving speed and efficiency by minimising the amount of code that is read. Another advantage is that you can add additional routes to your site without touching any of the existing components, minimising the risk of breakage, and you know before you set out exactly what components you need to add.

The main components and their functions are as follows:

Front end controller

The front controller reads the route (URL path) and parameters from the request, and initialises a triad of M-V-VM components and a controller. The specific components required to generate this route are read from a static routing table (trust_path/libraries/tuskfish/routingTable.php). For example, the routing table entry for or yoursite.com/search is:

return [
    ...
    '/search/' => new Route(
     '\\Tfish\\Content\\Model\\Search',
     '\\Tfish\\Content\\ViewModel\\Search',
     '\\Tfish\\View\\Listing',
     '\\Tfish\\Content\\Controller\\Search',
     false), // Specify if this route is restricted to admins (true) or not (false)
    ...
];

If an 'action' parameter has been received, eg. /?action=edit, the front controller will attempt to call a corresponding method on the MVVM(C) controller, which will begin execution of the route's MVVM triad.

MVVM(C) triad

Controller

The controller contains a series of 'action' methods, which correspond to things a user may request this route to do. It is responsible for:

  • Setting request parameters on the viewmodel, thereby also acting as a whitelist.
  • Calling the relevant 'action' method on the viewmodel, depending on context (search() in the example below). If no action parameter has been specified then by convention the default 'display' method will be run.
// From \Tfish\Content\Controller\Search
public function search(): array
{
        $start = (int) ($_GET['start'] ?? 0);
        $this->viewModel->setStart($start);

        $action = $this->trimString($_REQUEST['action'] ?? '');
        $this->viewModel->setAction($action);

        $searchType = $this->trimString($_REQUEST['searchType'] ?? '');
        $this->viewModel->setSearchType($searchType);

        // Search terms passed in from a pagination control link have been i) encoded and ii) escaped.
        // Search terms entered directly into the search form can be used directly.
        $cleanTerms = '';

        if (isset($_GET['searchTerms'])) {
            $terms = $this->trimString($_REQUEST['searchTerms']);
            $terms = \rawurldecode($terms);
            $cleanTerms = \htmlspecialchars_decode($terms, ENT_QUOTES|ENT_HTML5);
        } else {
            $cleanTerms = $this->trimString($_POST['searchTerms'] ?? '');
        }

        $this->viewModel->setSearchTerms($cleanTerms);
        $this->viewModel->setAction('search');
        $this->viewModel->search();

        return [ ];
    }
}

The MVVMC controller holds references to the model and viewmodel components as properties.

Viewmodel

The viewmodel is responsible for:

  • Requesting data from the model, which may be stored within its properties or directly returned via calls on its methods.
  • Making data available within the template; the viewmodel is passed into the template, so its methods and properties are accessible.
  • Specifying which template will be used for display.

Having the viewmodel available in the templates provides enormous flexibility, since it provides a bridge to retrieve data from the model / database.

public function search()
{
    // The viewmodel makes a call to the model to retrieve data.
    $searchResults = $this->model->search([
        'searchTerms' => $this->searchTerms,
        'escapedSearchTerms' => $this->escapedSearchTerms,
        'searchType' => $this->searchType,
        'start' => $this->start,
        'limit' => $this->limit(),
        'onlineStatus' => $this->onlineStatus
    ]);

    $this->contentCount = (int) \array_shift($searchResults);
    $this->searchResults =  $searchResults;
}

The viewmodel holds references to the model and site preferences.

Model

The model contains the business logic for the route. It is responsible for:

  • Validating and range checking parameters within the context of the business logic.
  • Reading from and writing to the database.
  • Processing data for use by the viewmodel.
public function search(array $params): array
{
    $cleanParams = $this->validateParams($params);

    // searchContent() is an internal method that queries the database
    // according to the parameters (filter criteria).
    return $this->searchContent($cleanParams);
}

The model holds references to the database, site preferences and session as properties.

View

The view's main responsibility is to render the HTML template, passing in the viewmodel and pagination control via render(), where they can be accessed directly as PHP objects in the HTML templates.

public function render(): string
{
    $this->template = $this->viewModel->template();
    $this->template->assign('viewModel', $this->viewModel);
    $this->template->assign('pagination', $this->pagination());

    return $this->template->render();
}

Copyright, all rights reserved.