Skip to content
V
For developers
Architecture, conventions, the core and security
Getting started / The five-layer model

The five-layer model#

The foundation of the whole system. Without understanding it the rest of the documentation makes no sense — and above all the code becomes unnavigable, because the same thing has a different name and a different role in every layer.

The model#

Presenter          thin; it only calls a manager
Manager            the domain's public interface, lifecycle hooks
Service            a thin layer, holds the counts, delegates
Mapper             turns raw data into entities
Repository         the ONLY place with SQL
Layer May May not
Presenter call a manager, fill the template query the database, hold domain logic
Manager domain logic, hooks, orchestration; inject other managers write SQL
Service delegate, hold totalCount make domain decisions, inject other services
Mapper hydrate data into entities write SQL
Repository SQL and the QueryBuilder domain logic

A shortcut across a layer always comes back

A query from a presenter straight into a repository works — until somebody needs the same thing from another section. Then the logic is duplicated and one of the copies stops being fixed.

The manager is the ONLY layer allowed to inject other managers

Service, Mapper and Repository are closed: a service holds only its mapper, a mapper only its repository. An XxxRunner or XxxResolver in Models/Services/ that injects a manager is a violation — it belongs in the manager tier next to the entity manager.

The front end does not touch the database#

The most important consequence of the model. Neither the front end nor the administration has database access. They request data from the Api section through the bridge (app/Core/Bridge/ApiBridge.php).

Front Manager → Service → Mapper → Repository → ApiBridge
                          an internal call (no HTTP) or an HTTP request
                                            Api presenter

The same module has several sets of entities

App\UI\Front\Eshop\Models\Entities\Product and App\UI\Api\Eshop\Models\Entities\Product are two different classes. The Api entity is a Doctrine entity over the table, the Front entity is the result of hydrating a response. Mixing the two up is the most common beginner's mistake in this system — and it surfaces as "a TypeError somewhere entirely different".

What flows between sections is an ARRAY, not an object

An array goes across the bridge. The object on the other side only comes into being through hydration — which is why there are two hydrators, one for each side.

A front manager can only read#

The base front manager has read traits onlyGetTrait, GetByTrait, GetAllTrait, TotalCountTrait. Writing has to be asked for explicitly:

use App\Core\Traits\Shared\Models\Managers\DeleteTrait;
use App\Core\Traits\Shared\Models\Managers\SaveTrait;

class AdvertFollowerManager extends BaseManager
{
    use SaveTrait;
    use DeleteTrait;
}

25 out of 111 front managers do so. The Admin and Api base classes have writing built in.

It is a deliberate brake, not an oversight

A write from the public site is an exception that should be visible in the code — not something that "just works".

Base classes#

Every layer has an ancestor in app/Core/Base/, separately for each section:

app/Core/Base/
  Api/     Front/     Admin/     Cron/     Script/     Shared/
    Models/Managers/Manager.php
    Models/Services/…
    Models/Mappers/…
    Models/Repositories/…

The shared implementations live in app/Core/Traits/<Section>/Models/.

The mapper has two shapes#

Where Folder What it does
an Api module Mappers/Db/ maps over the database
a Front/Admin module Mappers/Api/ hydrates the API response

It is the same layer in two different roles — which is why it has the same name.

Procedure: adding reads for a new entity#

  1. The Api side: entity → repository (SQL) → mapper → service → manager → get*Data() in the presenter with a permission guard.
  2. A permission key in the database, shaped :Api:<Module>:<Entity>:<action>.
  3. The Front/Admin side: a variant of the entity with hydration attributes → a repository with an apiEndpoint → mapper → service → manager.
  4. The presenter injects the manager only and calls findAll(…)->execute().
  5. Verify both paths — through a page and directly through the bridge.

In detail: Calling the API and Code conventions.

Why it is like this#

The administration, the front end and cron all take the same Api path. Domain logic in a manager therefore holds for every entry point at once — it does not have to be written three times and it cannot drift apart.

A side effect: the API is finished first

Because the front end uses the Api itself, the API is functional for third parties from the start rather than as an afterthought. See the API manual.

The price is one extra bottleneck

Every read goes through get*Data(), and therefore through the authorisation guard and through clearDoctrine(). The latter is the system's most serious landmine.

Follow-up chapters: Project structure · Calling the API · Hydrators · Identity map · Code conventions