Skip to content
V
For developers
Architecture, conventions, the core and security
Modules / Module overview

Modules#

The system's domain modules. Each has its part in every section — app/UI/{Front,Api,Admin,Cron}/<Module>/.

Module Domain Entities Chapter
System users, payments, settings, code lists, assets 90 —
Eshop goods, cart, orders, complaints, feeds 84 E-shop
Bazaar classifieds 34 Classifieds
Comcat company catalogue 33 Company catalogue
Blog articles, news, events, galleries, guestbook 29 Blog
Invoicer invoicing 28 Invoicing
Company the operator's own business — presentation, appointments, jobs 25 My company
Base shared — comments, ratings, polls, newsletter, ad slots, private messages 22 Private messages
Store warehouse 13 Warehouse
Discussion forum 13 Discussion

The counts are Api entities — ls app/UI/Api/<Module>/Models/Entities/*.php | wc -l. Exactly one module has no Front section: Invoicer, because documents are never shown to a visitor on their own, only as a file to download from an order.

Where to start#

I want Start here
to get my bearings in the modules for the first time Blog — the smallest of the "ordinary" ones and a reference model at the same time
to write a new search source ArticleDocumentSource.php from the blog
to understand dynamic fields Classifieds
to understand money and states E-shop → Warehouse → Invoicing, in that order

Shared patterns#

Most modules share the same patterns — recognise them in one and you recognise them everywhere:

Pattern Where it appears Where it is described
dynamic parameters instead of fixed columns Bazaar, Comcat, Eshop Classifieds
tree categories (nested set) Bazaar, Comcat, Discussion, Eshop, Blog, Company Classifieds
a denormalised counter plus a cron recount everywhere there is a tree Classifieds
tariffs and payment items Bazaar, Comcat, Discussion Discussion
time-limited highlighting (toped_until) Bazaar, Comcat, Discussion Classifieds
a state machine with action flags Eshop (orders, complaints) E-shop
a document as a snapshot Invoicer, Store Invoicing
an Elasticsearch source Blog, Bazaar, Comcat, Discussion, Eshop Elasticsearch

Comcat and Company are two different modules, not two halves of one

Comcat is a catalogue of other people's businesses (a copy of Bazaar), Company is the operator's own business. They share not a single table and have no common ancestor — only the name is similar. A fix in one has no effect in the other, so a report of "the company thing is broken" always has to be sorted out first.

Comcat was created as a copy of Bazaar

They are very similar. When solving something in one, check whether it is not in the other too — and whether the fix is not missing there. The note about its creation is in docs/comcat-copy-from-bazaar.md.

Similarity is not sameness, though

Comcat has no price, no sold and no followers, and its generated active column has one condition fewer. A copied SQL query or PHP getter can therefore fail — the list of differences is in Company catalogue.

Shared elements from Base#

Comments, ratings, polls, the newsletter and ad slots are one implementation for every module (app/UI/Api/Base/).

Fixing a shared component affects every module at once

A change in ratings shows up on products, adverts, companies and posts alike. Walk through all of them, not just the one you are fixing it for — and expect that three out of four will go untested unless you say so yourself.

Joining a module to system flows#

Some flows are owned by the System module, but other modules hold the data for them. A module joins them with a new class implementing an interface — DI finds it by type and the System module does not change.

Interface Flow Existing implementations
App\Core\Security\AccountAnonymizationStepInterface anonymizing a deleted account app/UI/Api/{Bazaar,Comcat,Base,Discussion}/Models/Managers/AccountAnonymizationManager.php
App\Core\Payments\Express\ExpressServiceProviderInterface a service paid by one express SMS app/UI/Api/{Bazaar,Comcat,Discussion,System}/Payments/*ExpressServiceProvider.php

An account anonymization step#

interface AccountAnonymizationStepInterface
{
    /** @return array<string, int> what changed, for the audit (e.g. bazaarAdvertsDeactivated => 2) */
    public function anonymizeUser(int $userId, string $originalEmail, string $deletedLabel): array;
}

AccountDeletionManager receives all implementations as an array from DI and calls them before the user row is overwritten, so $originalEmail is still the original one (a newsletter subscription is linked only by e-mail). $deletedLabel is the translated "Deleted user" for copies of the author name.

  1. The class belongs to the module's Api layer (manager tier) and works directly with managers and SQL — no bridge and no RBAC, the cron runs without an identity.
  2. The step must be idempotent. An exception stops the anonymization of that account only, is written to accountAudit and the next cron run tries again.
  3. Deactivate content, do not delete it. Adverts and profiles are deactivated (and reindexed), posts stay with the name "Deleted user"; invoices and payments do not change.
  4. Return the change counts — they end up in the audit log.

A module's express SMS service#

A module that sells a service paid by one SMS supplies a provider with getModuleCode(), resolve(), createPaymentItems() and describe(). The whole procedure, including the shared base for modules with quick prices, is in Express SMS payments.

Where to look#

I want Where
the shared entities app/UI/Api/Base/Models/Entities/
the modules' shared base entities app/Core/Base/Shared/<Module>/Models/Entities/
a module's cron tasks app/UI/Cron/<Module>/Presenters/
the search sources app/UI/Api/<Module>/Models/Search/

Follow-up chapters: Five-layer model · Project structure · Elasticsearch · Scheduled tasks

Cookie settings