crustum / structarmed-cakephp
StructArmed preset for CakePHP 5 conventions (Controller/Component, Table/Entity/Behavior, View/Cell/Helper, Command)
Requires
- php: ^8.2
- boundwize/structarmed: ~0.15
Requires (Dev)
- cakephp/cakephp-codesniffer: ^5.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.5 || ^12.5
- rector/rector: ^2.5
This package is auto-updated.
Last update: 2026-08-13 03:35:59 UTC
README
StructArmed preset for CakePHP 5 application conventions.
Reuses the ideas of StructArmed's built-in MVC preset (layer isolation, thin
entry points, naming, safety rules) but maps them to CakePHP's real layout.
Layers
| Layer | CakePHP namespace | Naming |
|---|---|---|
Controller |
<App>\Controller\FooController |
suffix Controller |
Component |
<App>\Controller\Component\FooComponent |
suffix Component |
Table |
<App>\Model\Table\FooTable |
suffix Table |
Entity |
<App>\Model\Entity\Foo |
— |
Behavior |
<App>\Model\Behavior\FooBehavior |
suffix Behavior |
View |
<App>\View\AppView |
— |
Cell |
<App>\View\Cell\FooCell |
suffix Cell |
Helper |
<App>\View\Helper\FooHelper |
suffix Helper |
Command |
<App>\Command\FooCommand |
suffix Command |
Mailer |
<App>\Mailer\FooMailer |
suffix Mailer |
Middleware |
<App>\Middleware\FooMiddleware |
suffix Middleware |
Form |
<App>\Form\FooForm |
suffix Form |
Type |
<App>\Database\Type\FooType |
suffix Type |
Route |
<App>\Routing\Route\FooRoute |
suffix Route |
Widget |
<App>\View\Widget\FooWidget |
suffix Widget |
CacheEngine |
<App>\Cache\Engine\FooEngine |
suffix Engine |
LogEngine |
<App>\Log\Engine\FooLog |
suffix Log |
MailerTransport |
<App>\Mailer\Transport\FooTransport |
suffix Transport |
Event |
<App>\Event\FooListener |
— |
Policy |
<App>\Policy\FooPolicy |
suffix Policy |
Authenticator |
<App>\Authenticator\FooAuthenticator |
suffix Authenticator |
Identifier |
<App>\Identifier\FooIdentifier |
suffix Identifier |
PasswordHasher |
<App>\PasswordHasher\FooPasswordHasher |
suffix PasswordHasher |
UrlChecker |
<App>\UrlChecker\FooUrlChecker |
suffix UrlChecker |
<App> is the root namespace of your application (or plugin), passed to the
constructor (default App).
Templates are not classes — the preset calls skipPathsForRuleset(['templates/'])
so templates/* is still scanned for PSR checks but excluded from layer rules.
Nested layers are never forced into their parent's suffix: a class that resolves
to Component is exempt from the Controller suffix rule even when a broader
host-defined App\Controller\* pattern also matches it (same for Behavior/
Entity vs Table).
Plugins
Plugins are separate namespaces (Billing\, Inventory\, …). Pass them to the preset
and the same canonical layers, naming, quality and safety rules apply inside the
plugin code:
return Architecture::define() ->withPreset(new CakeAppPreset( pluginNamespaces: ['Billing', 'Inventory'], // pluginAppAccess: ['Billing' => ['App\Util']], // grant app kernel access ));
Each plugin gets an owner layer (PluginBilling ↔ /^Billing\\.*$/) registered before
the canonical layers, so per-plugin rules can be keyed on it in ruleset(). By
default a plugin may not import the app namespace at all; grant specific app
namespaces per plugin via pluginAppAccess.
Install
composer require --dev crustum/structarmed-cakephp
Usage
// structarmed.php use Boundwize\StructArmed\Architecture; use Crustum\StructArmed\Cake\CakeAppPreset; return Architecture::define() ->layer('Source', []) // PSR-4 scan paths (needed for the PHPUnit extension) ->withPreset(new CakeAppPreset()) ->ruleset([ // your own domain layers, if any // ... ]);
Tune it like the built-in presets:
new CakeAppPreset( namespace: 'App', // root namespace (no trailing backslash) controllerMaxComplexity: 3, // default: 5 controllerMaxMethodLength: 15, // default: 20 controllerMaxDependencies: 4, // default: 5 viewMaxComplexity: 2, // default: 3 — View classes should just load helpers )
Skip a rule or a path:
use Crustum\StructArmed\Cake\CakeAppPreset; return Architecture::define() ->withPreset(new CakeAppPreset()) ->skip(CakeAppPreset::CONTROLLER_MAX_DEPENDENCIES); // whole rule // or: ->skip([CakeAppPreset::ENTITY_MUST... => ['src/Legacy/']]);
Rule keys
Rule keys live on the Crustum\StructArmed\Cake\CakeRule enum and are prefixed
with cakephp.. Pass CakeRule::X->value to skip() / replaceRule():
use Crustum\StructArmed\Cake\CakeRule; return Architecture::define() ->withPreset(new CakeAppPreset()) ->skip(CakeRule::CONTROLLER_MAX_DEPENDENCIES->value);
- Layer isolation:
cakephp.layer.<from>_not_depend_<to>(TABLE_NOT_DEPEND_CONTROLLER,VIEW_NOT_DEPEND_TABLE,MAILER_NOT_DEPEND_CONTROLLER,TYPE_NOT_DEPEND_TABLE, …) - Naming:
cakephp.<layer>.name_must_end_with_<suffix> - Quality:
<layer>.max_complexity/max_method_length/max_dependencies,<layer>.no_superglobals,<layer>.must_have_return_types - Safety:
cakephp.safety.<layer>_no_<fn|construct>(dd, dump, var_dump, print_r, var_export, die, exit) — derived per layer, not enumerated on the enum
What is intentionally not enforced
MustBeFinalRule— CakePHP controllers/tables are meant to be extended, not final.MustBeInterfaceRule— no repository interfaces in Cake; Table is the interface.- Doctrine/PDO bans — Cake uses its own ORM;
DateTimeis fine in Model layer.
Requirements
- PHP 8.2+
License
Licensed under the MIT License. Redistributions of the source code included in this repository must retain the copyright notice found in each file.