Modules
A module is the unit Quiote uses to group related actions, views, and templates. Every action in your application lives in exactly one module, and the module name is half of what the framework needs to resolve a request into code — the route says which module, which action, and the module’s directory layout says where the classes are.
Modules are pure convention: a directory with an expected shape and a namespace that matches. There is no module registry to edit and no build step to run — drop the files in the right place with the right names and the request resolves.
How a module fits in a request
Section titled “How a module fits in a request”A module is not a pipeline stage of its own — it is where the code lives that the pipeline runs. The connection is made during dispatch:
RoutingMiddlewareresolves the request to a_moduleand_action(see Routing).- When
DispatchMiddlewareruns the action,ActionExecutorcallsController::createActionInstance($module, $action), which builds the class name deterministically ({namespace_prefix}\Modules\{Module}\Actions\{Action}Action) and lets the autoloader find it. ViewNameResolvermaps the same module + action + returned view name to the view class and template file.
RoutingMiddlewareresolves_module/_action→DispatchMiddlewareasks theControllerto instantiate the action class in that module → the action runs →ViewNameResolverlocates the view and template in the same module → the response is rendered.
Because the mapping is deterministic, there is nothing to register — matching names and directories are all that wire a module in. The rest of this page is those naming rules and the optional per-module config.
Layout
Section titled “Layout”A module is a directory under your application’s module directory (core.module_dir, Modules/ by default). Its name is the module name, PascalCase:
Modules/ Blog/ Actions/ PostAction.php Views/ PostSuccessView.php Templates/ PostSuccess.php Models/ (optional) — DTOs, see Services & models Validate/ (optional) — validators.xml per action Config/ (optional) — module.xml, config_handlers.xmlOnly Actions/, Views/, and Templates/ are needed for a working module. The rest are opt-in and covered below.
The classes in a module share a namespace built from your app’s namespace prefix:
<?phpnamespace App\Modules\Blog\Actions; // App = core.namespace_prefix
use Quiote\Action\Action;use Quiote\Request\WebRequest;
class PostAction extends Action{ public function executeRead(WebRequest $rd) { $this->setAttribute('post', /* ... */); return 'Success'; }}How a module is discovered
Section titled “How a module is discovered”There is no scan and no manifest. When a route resolves to a module and action, the Controller builds the class name deterministically and lets the autoloader find it. For an action in module Blog named Post:
{core.namespace_prefix}\Modules\Blog\Actions\PostActionThe same pattern produces the view and model class names:
| Kind | Class | File |
|---|---|---|
| Action | App\Modules\Blog\Actions\PostAction | Modules/Blog/Actions/PostAction.php |
| View | App\Modules\Blog\Views\PostSuccessView | Modules/Blog/Views/PostSuccessView.php |
| Model | App\Modules\Blog\Models\PostModel | Modules/Blog/Models/PostModel.php |
core.namespace_prefix is set in your settings config (App by default). As long as your namespaces and directories follow the pattern, the class resolves — the autoloader does the finding, so no registration is involved.
Naming and resolution
Section titled “Naming and resolution”The convention that makes modules work is the alignment of three names — action, view, template:
- An action in module
BlognamedPostreturning'Success'… - …resolves to the view class
PostSuccessView(action name + view name +View)… - …and the template
PostSuccess.php(action name + view name).
ViewNameResolver performs this mapping. Keep the names aligned and the wiring stays implicit; deviate and you wire it explicitly. This is the same contract described in Actions and views and Templates and rendering — modules are just where those files live.
Per-module path directives
Section titled “Per-module path directives”The filesystem layout above is not hard-coded — it is a set of per-module directives the framework sets up with conventional defaults on first use of a module, then reads whenever it needs to locate a file. Each key is modules.<lowercase-module>.quiote.*:
| Directive | Default |
|---|---|
quiote.action.path | %core.module_dir%/${moduleName}/Actions/${actionName}Action.php |
quiote.view.path | %core.module_dir%/${moduleName}/Views/${viewName}View.php |
quiote.template.directory | %core.module_dir%/${module}/Templates |
quiote.validate.path | %core.module_dir%/${moduleName}/Validate/${actionName}.xml |
quiote.cache.path | %core.module_dir%/${moduleName}/cache/${actionName}.xml |
quiote.view.name | ${actionName}${viewName} |
A directive supplied by the module’s own config is never overwritten by the default, so a module can lay itself out differently — point quiote.action.path at a src/ subdirectory, say — without affecting any other module. Most applications never touch these.
Per-module configuration
Section titled “Per-module configuration”A module can carry its own config in a Config/ directory. Two files are recognised on the module’s first initialisation:
Config/module.xml— the module’s own settings. If present, it is loaded (and compiled/cached) once; if absent, the module is simply enabled with defaults.Config/config_handlers.xml— registers additional config handlers scoped to this module.
A Config.php file directly under the module directory, if present, is required on initialisation — a place for imperative module setup that doesn’t fit declarative config.
Enabling and disabling a module
Section titled “Enabling and disabling a module”Every module has an enabled flag under modules.<lowercase-module>.enabled. It defaults to true; a disabled module throws DisabledModuleException when an action in it is requested (its views stay usable, so an error view from a disabled module still renders). Set it in the module’s module.xml, or centrally in your app settings:
return [ 'modules.blog.enabled' => false, // turn the Blog module off];modules.blog.enabled: false<!-- Config/settings.xml — inside <settings> --><setting name="modules.blog.enabled">false</setting>Module names are matched case-insensitively for these keys — the directory Blog is configured under modules.blog.*.
A complete module
Section titled “A complete module”The scaffolded app’s Default module is the smallest real example. Its Contact action, view, and template:
<?phpnamespace App\Modules\Default\Actions;
use Quiote\Action\Action;use Quiote\Request\WebRequest;use Quiote\Routing\Attribute\Route;
#[Route('/contact', name: 'contact', methods: ['GET'])]class ContactAction extends Action{ public function executeRead(WebRequest $rd) { return 'Success'; }}<?phpnamespace App\Modules\Default\Views;
use Quiote\Request\WebRequest;use Quiote\View\View;
class ContactSuccessView extends View{ public function executeHtml(WebRequest $rd) { $this->loadLayout(); $this->setAttribute('title', 'Contact'); }}<h1><?php echo htmlspecialchars($template['title'], ENT_QUOTES, 'UTF-8'); ?></h1>Three files, three aligned names, one route — and GET /contact renders. Adding a feature to an application is usually adding a module (or an action to an existing one) in exactly this shape. See Your first application for the walkthrough that generates it.