Skip to content
V
For developers
Architecture, conventions, the core and security
Getting started / Project structure

Project structure#

The root#

Folder Contents
app/ all the application's PHP code
config/ the configuration (NEON)
www/ the public root, themes, assets, uploaded files
docs/ documentation — 🔴 served statically, outside the application
manual/ the sources of this manual (MkDocs)
tests/ the tests
skills/ the binding procedures
improvements/ working notes and plans
_legacy/ the previous generation of the system
temp/, log/, var/ runtime data

docs/ is a public path outside the application's authorisation

Whatever you generate there is downloadable by anyone — and the application never even learns about the request, so there is nowhere to log it. Outputs containing data belong in var/. In detail: CORS, rate limiting and audit.

app/Core/ — the shared foundation#

Folder Contents
Base/ the layers' base classes, split by section plus Shared/
Bridge/ ApiBridge — the bridge between Front/Admin and Api
Traits/ shared implementations, split into Shared / Api / Admin / Front / Cron
Utils/Hydrators/ both hydrators
Utils/Filters/ the composable filters
Security/ JWT, RBAC, CORS, rate limiting
Forms/ the forms core; Form/Parameters/ the dynamic parameter system
Latte/ template extensions and macros
Routers/ routing, route factories and translators
Cache/ the query cache and the route cache
Attributes/ attributes for entities and actions
Integrations/ Elasticsearch and others
Theme/ selecting the active theme

app/UI/ — section × module#

The most important division in the whole project. The section determines the entry point, the module determines the domain.

app/UI/
  Front/     ← the public site
  Api/       ← the only section with real database access
  Admin/     ← the administration
  Cron/      ← scheduled tasks
  Scripts/   ← one-off and maintenance scripts
  Script/    ← an older namespace for the same (System only)
  Error/     ← error pages (Error4xx, Error5xx)
    Base/  Bazaar/  Blog/  Comcat/  Discussion/  Eshop/  Invoicer/  Store/  System/

app/UI/Admin/Eshop/ is the e-shop's administration, app/UI/Api/Eshop/ its API. The same tables, a different entry point, different rules.

Script/ and Scripts/ are two different sections

The namespace determines the translation domain, so a presenter in App\UI\Scripts\* looks its texts up in the scripts domain. Creating a script in the other one means the texts are not found — and that surfaces as empty labels, not as an error.

A module's structure#

app/UI/<Section>/<Module>/
  Presenters/
    XxxPresenter.php
    Traits/
      Inits/       initialisation, injection, the topic's signals
      Lists/       listings
      Details/     details
      Forms/       forms
      Pickers/     Picker configurations (the administration)
  Components/
    XxxComponent/
      Xxx.php  XxxFactory.php  Traits/  Templates/Default/
  Templates/
  Models/
    Entities/  Managers/  Services/  Mappers/  Repositories/
    Generators/  Helpers/  Search/     (as needed)

A presenter should be thin, the logic belongs in traits

A trait for the listing, a trait for the form, a trait for the detail. The presenter file is then just a list of use statements, properties and a constructor — see Code conventions.

Where an entity exists three times over#

The same entity exists in several shapes at once, and that is deliberate, not duplication:

Where What it is
app/Core/Base/Shared/<Module>/Models/Entities/ the abstract ancestor with the columns and accessors
app/UI/Api/<Module>/Models/Entities/ the Doctrine entity over the table
app/UI/Admin/<Module>/Models/Entities/ what the administration needs
app/UI/Front/<Module>/Models/Entities/ what the public site needs

In detail: Hydrators and the Five-layer model.

Legacy#

_legacy/ in the root is the previous generation of the system. Inside app/ there are three further Legacy/ subfolders — leftovers not yet converted.

Do not touch legacy code and do not copy patterns from it

It is marked obsolete and will be removed. When adding a feature, write it the new way — the relevant skill in skills/ describes how. What makes it dangerous is that legacy code looks like a valid model, because it sits in the same project.

Where a new class belongs#

I am writing It belongs in
domain logic app/UI/Api/<Module>/Models/Managers/
an SQL query app/UI/Api/<Module>/Models/Repositories/
process orchestration (checkout, a state transition) a manager-tier class next to the entity manager, not in Services/
an artefact generator (PDF, export) app/UI/Api/<Module>/Models/Generators/
a pure computation with no I/O app/UI/Api/<Module>/Models/Helpers/
an administration screen app/UI/Admin/<Module>/Presenters/
a scheduled task app/UI/Cron/<Module>/Presenters/
something two sections need app/Core/
translation keys app/Locale/<module>/<locale>/<domain>.<LOCALE>.neon

Follow-up chapters: Five-layer model · Code conventions · Modules overview · Translations