====== Layer Boundaries and Dependencies ====== The intended architecture is described on [[scriptlog:architecture:overview|Architecture Overview]]. This page measures what the imports actually do, and lists every place the two disagree. ===== 1. The measured import graph ===== Every `use` statement that resolves to a first-party class, counted by the layer that wrote it and the layer that declares the target. 503 imports across 28 distinct layer pairs. | from \ to | admin | controller | core | dao | dto | handler | model | service | validator | |---|---|---|---|---|---|---|---|---|---| | admin | | | 4 | | | | | | | | controller | | | 122 | 11 | 8 | | | 31 | 3 | | core | | 5 | | 15 | | 46 | 5 | 7 | | | dao | | | 25 | | | | | | | | handler | | 1 | 48 | | | | | | | | model | | | 16 | | | | | | | | service | | | 47 | 38 | | | 2 | | 8 | | utility | | | 2 | | | | | | | | validator | | | | | 4 | | | | | Read it as a graph and five cycles are immediately visible: * **core ↔ controller** — `controller → core` 122, `core → controller` 5 * **core ↔ handler** — `handler → core` 48, `core → handler` 46 * **core ↔ dao** — `dao → core` 25, `core → dao` 15 * **core ↔ model** — `model → core` 16, `core → model` 5 * **core ↔ service** — `service → core` 47, `core → service` 7 ===== 2. Which cycles are real, and which are the composition root ===== A cycle is only a design problem if the dependency carries *behaviour*. Most of `core`'s upward reach is concentrated in the two files whose entire job is to know about every other layer. | Edge | Imports | Dominant file | Share | |---|---|---|---| | `core → handler` | 46 | `lib/core/Bootstrap.php` | 98% | | `core → dao` | 15 | `lib/core/Bootstrap.php` | 47% | | `core → service` | 7 | `lib/core/Bootstrap.php` | 43% | | `core → controller` | 5 | `lib/core/Dispatcher.php` | 60% | | `core → model` | 5 | `lib/core/AtomWriter.php` | 40% | | `admin → core` | 4 | `admin/authenticator.php` | 100% | **`core → handler` is not a layering defect.** 45 of its 46 imports sit in `Bootstrap.php`, and `Bootstrap` is the composition root: it exists to instantiate the handler registry. Reading a cycle through the composition root is a category error. Two upward dependencies are *not* explained that way, and are the real findings: * **`lib/core/AtomWriter.php → model`** (2 imports, `FrontContentModel` and `PostModel`). Atom generation is a `core` class reaching into the domain model. This is a genuine upward edge in a leaf-ish class. * **`lib/core/Authentication.php → dao`** (2 imports, `UserDao` and `UserTokenDao`). Authentication is a `core` class that queries the data layer directly rather than going through a service, so it carries SQL concerns that belong one layer up. ===== 3. The rules the code states, and whether it keeps them ===== The DAO layer's own description claims: *"No HTML, no session, no superglobals."* Measured against comment-stripped source: | Layer | `$_GET` | `$_POST` | `$_SERVER` | `$_SESSION` | `$_COOKIE` | `$_FILES` | `$GLOBALS` | Total | |---|---|---|---|---|---|---|---|---| | controller | 56 | 398 | 77 | 261 | 3 | 48 | 11 | **854** | | admin | 149 | 71 | 27 | 26 | 15 | 4 | 0 | **292** | | utility | 7 | 14 | 107 | 46 | 18 | 2 | 20 | **215** | | core | 16 | 0 | 94 | 32 | 24 | 0 | 12 | **178** | | install | 9 | 49 | 41 | 40 | 0 | 0 | 0 | **139** | | tests | 37 | 57 | 247 | 93 | 48 | 9 | 33 | 524 | | service | 0 | 48 | 4 | 7 | 10 | 0 | 0 | 69 | | public | 4 | 0 | 6 | 6 | 3 | 0 | 9 | 28 | | handler | 0 | 0 | 23 | 0 | 0 | 0 | 1 | 24 | | dto | 0 | 2 | 0 | 0 | 0 | 3 | 0 | 5 | | api | 0 | 0 | 4 | 0 | 0 | 0 | 1 | 5 | | libroot | 6 | 0 | 13 | 1 | 0 | 0 | 0 | 20 | | root | 4 | 0 | 2 | 0 | 0 | 0 | 0 | 6 | | **dao** | 0 | **2** | 0 | 0 | 0 | 0 | 0 | **2** | | **model** | 0 | 0 | 0 | 0 | 0 | 0 | 0 | **0** | | **validator** | 0 | 0 | 0 | 0 | 0 | 0 | 0 | **0** | * **The DAO rule is almost kept.** Two `$_POST` mentions across 19 DAO files is the whole violation, and neither is a query input. * **`model` and `validator` are completely clean** — zero superglobal mentions in either. * **The "only controllers read superglobals" claim is badly wrong.** The controller layer is the worst offender at 854, but `utility` (215), `core` (178) and `admin` (292) all reach into global state too, and `admin` is not a controller at all. * `service` is the surprise: it reads `$_POST` 48 times while claiming no superglobal access. Business logic that reads request data directly cannot be called from a non-HTTP context — a cron job, a CLI import or a test cannot supply `$_POST`. Counts are of comment-stripped source, but string literals are still counted, so treat them as *mentions* rather than proven reads. ===== 4. Other boundaries that are declared but not enforced ===== * **The deprecated facade is a soft boundary.** `Scriptlog\Core\FrontHelper` delegates to `FrontService` when `Bootstrap` has registered one, and returns `null` or an empty array when it has not. Nothing raises; a missing registration degrades into empty output rather than an error. * **The `handler` layer reaches back up once.** `lib/handler/DownloadHandler.php` imports `Scriptlog\Controller\DownloadController`, the only `handler → controller` edge in the tree. * **`service` depends on `utility`** (8 imports) for the export feature: `ExportService` imports `BlogspotExporter`, `GhostExporter` and `ScriptlogExporter`. A service layer importing flat procedural helpers is the boundary most likely to be crossed by a new contributor, because `lib/utility/` is unnamespaced and looks like a neutral dumping ground. * **`admin/` is procedural, not namespaced.** Its 103 files include `admin/ui/**` templates that declare no classes at all, so the admin UI has no layer boundary to lean on. ===== Related ===== * [[scriptlog:architecture:overview|Architecture Overview]] * [[scriptlog:nonfunctional:limits|Limits and Non-behaviour]] * [[scriptlog:nonfunctional:negative-space|Negative Space and Dead Code]]