General architecture

XML layout, blocks and templates

The general structure of the Loki Checkout is created via the XML layout. This makes it easy for newcomers to understand the architecture and it makes changes also very simple. Through the XML layout, blocks are created - the overall checkout, steps, forms, fields, sidebar, etcetera.

Most of the templates are kept small and simple: Logic is moved to PHP classes instead. The only exception here is that most blocks are created with the XML layout, but some child templates are rendered via PHTML (using generic methods like $block->getChildHtml() or the Loki Checkout specific $blockRenderer).

Block classes

Most blocks are created without any custom Block classes - in other words, in the XML layout, they lack the class attribute. Therefor the default Template class is used to allow rendering the HTML via a PHTML template.

For example:

<block name="loki.checkout.generic.title" template="Yireo_LokiCheckout::generic/title.phtml"/>

ViewModel classes

ViewModels are used to insert data into templates. Often, a single template is making use of multiple ViewModels, each ViewModel having a specific purpose.

A special group of ViewModels is formed by Loki Components. With Loki Components, a block is transformed into a component by adding various parts automatically to the block, including a Viewmodel. Because these ViewModels inherit from an interface ComponentViewModelInterface and often extend from ComponentViewModel, they are commonly referred to as Loki Component ViewModel or ComponentViewModel.

Loki Components

The goal of Loki Components is to streamline the updating of blocks via user input, including client-side validation and server-side validation, including filtering and including refreshing various parts on the page.

In an overview, the following parts are involved with a Loki Component:

  • A Block defined through the XML layout, with an unique name
  • A component definition in a file etc/loki_components.xml where the component name matches the block name
  • An AlpineJS component that triggers AJAX calls
  • Optionally a ViewModel class for displaying data
  • Optionally a Repository class for modifying data
  • Optionally a Context class for inserting dependencies into the ViewModel and/or Repository
  • Targets identifying HTML parts in the page that should be updated

When a component includes a ViewModel, this ViewModel is automatically inserted into the PHTML templatea as $viewModel.

There is much more to say about Loki Components. This will be explained through the documentation of this site.

ViewModelFactory

To allow ViewModels to be inserted into PHTML templates easily, Loki makes use of a custom ViewModelFactory class that is automatically inserted into every PHTML templates (via the observable event view_block_abstract_to_html_before). Thanks to this factory, ViewModels are easily instantiated in PHTML templates, without requiring XML layout block arguments:

$example = $viewModelFactory->create(ExampleViewModel::class);

This is very similar to the ViewModelRegistry $viewModels solution of Hyvä. However, with Loki, this factory is also available under other frontends.

$css, $debug and other variables

In a similar way that the $viewModelRegistry variable is added to PHTML templates, there are also other variables added to the PHTML templates to lighten the load:

  • The $css executable creates an unique HTML class based on the block name, plus it allows overriding the default CSS classes via the XML layout;
  • The $debug boolean is set when debugging is enabled;
  • The $blockRenderer variable allows for rendering child blocks or any block (see below);

A small example with the $css() variable:

<div class="<?= $css('container') ?>"></div>

^^See Customizing CSS classes

Variables are inserted via the observer \Yireo\LokiCheckout\Observer\AssignAdditionalBlockVariables.

Block rendering

Yet another variable that is inserted into any PHTML template is the $blockRenderer variable - an instance of the class \Yireo\LokiCheckout\Util\Block\BlockRenderer. This utility allows you to render a block identified either by its template or its layout name.

For instance, the loader icon can be easily inserted like this:

<?= $blockRenderer->html('loki.checkout.utils.loader-small') ?>

Similarly, a block can be rendered by passing its template:

<?= $blockRenderer->html('form/field/text/script.phtml', $block) ?>

The utitity has two main rendering methods:

  • html(string $blockIdentifier, ?AbstractBlock $parentBlock = null, array $data = []): AbstractBlock
  • get(string $blockIdentifier, ?AbstractBlock $parentBlock = null, array $data = []): AbstractBlock

Both methods boil down to the same thing - instantiating a block. The html() method actually calls upon the get() method to retrieve a block object and then calls upon the toHtml() method of that object (so $block->toHtml()).

Note that the $blockRenderer utility does not take the layout hierarchy into account. It assumes that a block is defined somewhere with a specific block name (in the XML layout: <block name="foobar" />) or that there is a PHTML template somewhere available. This means that you might still want to call upon child blocks regularly as well:

<?= $block->getChildHtml($childAlias) ?>

Block attributes

Every single PHTML template that starts with an HTML element (<div>, <aside>, <span>) with a few exceptions (<script>) is able to receive additional HTML attributes via the XML layout. This includes an unique ID which is based on the block name in the XML layout.

When a block belongs to a Loki Component - in other words, when the block name in the XML layout is also used to declare a component in the file etc/loki_components.xml - the HTML is further extended with data for AlpineJS.

For instance, take the following PHTML template for the block loki-example.component:

<div class="<?= $css('relative') ?>"></div>

This will be transformed into the following HTML output:

<div id="loki-example-component"
    x-data="LokiComponent"
    x-title="LokiExampleComponent"
    x-init-data='{"foo":"bar"}' 
    class="loki-example-component relative"></div>

^^See also "A simple component"

Last modified: January 22, 2025