====== Limits and Non-behaviour ====== The generated reference says what each class does. This page records the things it deliberately does **not** do, and the bounds that are fixed in code rather than configuration. ===== 1. Configuration is a hard precondition, not a fallback ===== `lib/main.php` checks for `config.php` before the framework is allowed to exist. If it is missing and `install/` is present, the request is handed to the installer; if the installer is gone too, the request ends there. The practical consequence: **the application cannot boot in a degraded or read-only mode for inspection.** There is no "load what you can" path. A deployment either has a complete `config.php` or does not run. ===== 2. The supported-language set is seven codes, hard-coded in five places ===== The whitelist is literally `en, ar, zh, fr, ru, es, id`, and it appears five times in the tree: | Location | Form | |---|---| | `lib/main.php:129` | `$validLocales = ['en', 'ar', ...]` | | `lib/controller/TranslationController.php:40` | `in_array($_GET['lang'], ['en', 'ar', ...])` | | `lib/controller/LocaleController.php:28` | fallback when `available_locales()` is absent | | `lib/dao/LanguageDao.php:13` | documented in the class docblock | | `install/include/setup.php:811` | `$langOrder` for the installer's seed data | Adding a language therefore means five coordinated edits, and the installer's seed order can drift from the runtime whitelist. **Any locale with a region subtag is rejected.** The sanitiser in `lib/main.php` is `preg_replace('/[^a-z]{2}/', '', strtolower($code))`, which only removes *runs of two or more* non-`a-z` characters. A single separator is left alone, so the value reaches the whitelist unchanged and fails: | Input | After sanitiser | Result | |---|---|---| | `en` | `en` | accepted | | `EN` | `en` | accepted | | `pt-br` | `pt-br` | rejected — `-` is a run of one | | `pt_BR` | `pt_br` | rejected | | `zh-Hans` | `zh-hans` | rejected | | `en-us` | `en-us` | rejected | So `pt-BR` cannot be expressed, and adding it would need a real normalisation step rather than a longer whitelist. ===== 3. One URL style at a time, decided by a single setting ===== `Dispatcher::dispatch()` branches on one configuration value, `rewrite_status`: * `'yes'` → `handleSeoFriendlyUrl()`, matching the 13 patterns in the route table after stripping an optional locale prefix. * anything else → `handleQueryStringUrl()`, serving `?pg=`, `?p=`, `?a=`, `?cat=`, `?tag=`, `?search=`, `?q=`. Both styles are always present in the code; only one is reachable per configuration. A new page type must be added to **both** the route table in `Bootstrap::defineRoutingRules()` and the query-parameter list in `lib/main.php`, or it will work in one URL style and 404 in the other. `?download=` is the documented exception: it is handled before the SEO branch specifically so downloads survive either permalink setting. ===== 4. `config.php` is required, and `config.sample.php` is the only template ===== There is no environment-variable fallback for the database credentials. `.env` is loaded through Dotenv when present, but the application's own settings live in `config.php`, which is derived from `config.sample.php`. See [[scriptlog:nonfunctional:requirements|Requirements]]. ===== 5. FrontHelper fails quietly ===== This is the most consequential non-behaviour in the codebase, and it is the facade's own documented decision. `Scriptlog\Core\FrontHelper` is a deprecated facade over `Scriptlog\Service\FrontService`. Every data-access method delegates to the `FrontService` instance registered via `FrontHelper::setFrontService()`. **When no service is registered, the methods return `null` or an empty array.** The inline SQL that used to back those methods was removed because it drifted from `FrontService`, used string interpolation, and one path was mysqli-only and broke on PDO. The fix was correct, but the chosen failure mode is silence. A theme calling `FrontHelper` before `Bootstrap` has run gets an empty result and renders a blank region with no error anywhere. ===== 6. `tests` are extensive but not name-mapped to units ===== 83 types have a test whose name suggests it covers them. 113 do not: | Layer | Types without a matching test name | |---|---| | core | 75 | | controller | 17 | | dao | 9 | | model | 9 | | service | 4 | This is a name-based heuristic, not proof of a coverage gap: a test may cover a class under a different name, or through an integration test. But the shape is informative — **`service` is the best-covered business layer by name (only 4 unmatched) while `core` is by far the largest gap.** With 2,649 methods in the suite, the project clearly tests heavily; what it does not do is maintain a one-test-per-class correspondence. ===== 7. `@todo` inventory is empty ===== There are **zero** `@todo` annotations in first-party code. The one hit in an earlier pass was inside a vendored HTMLPurifier shim. Either the backlog is tracked outside the source, or it is tracked in issues; it is not in the code. ===== 8. Known inconsistencies, recorded rather than hidden ===== * `Scriptlog\Service\FrontService::getGalleries(int $start, int $limit)` interpolates its arguments into SQL as `LIMIT " . $start . ", " . $limit`. Both parameters are `int`-typed, so PHP coerces them and this is not currently injectable — but it is precisely the string-interpolation pattern the `FrontHelper` docblock cites as a reason for removing the old inline queries. It is the last place in the tree that still does it that way. * `Scriptlog\Controller\DownloadController` declares `private $view;` with a bare `/** @deprecated */` and never reads it (`$this->view` appears zero times in the file). ===== Related ===== * [[scriptlog:nonfunctional:boundaries|Layer Boundaries and Dependencies]] * [[scriptlog:nonfunctional:negative-space|Negative Space and Dead Code]] * [[scriptlog:nonfunctional:requirements|Requirements]]