contenir / contenir-maintenance-laminas-mvc
Laminas MVC adapter for contenir/contenir-maintenance — short-circuits dispatch with 503 when maintenance mode is active.
Package info
github.com/contenir/contenir-maintenance-laminas-mvc
pkg:composer/contenir/contenir-maintenance-laminas-mvc
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
- contenir/contenir-maintenance: ^2.1
- laminas/laminas-eventmanager: ^3.13
- laminas/laminas-http: ^2.19
- laminas/laminas-mvc: ^3.7
- laminas/laminas-servicemanager: ^3.22 || ^4.0
- psr/container: ^1.1 || ^2.0
Requires (Dev)
- infection/infection: ^0.34.1
- php-db/phpdb-qa-tools: 0.1.x-dev
- phpunit/phpunit: ^11.5.42
Suggests
None
Provides
None
Conflicts
None
Replaces
- contenir/maintenance-laminas-mvc: v2.2.0
This package is auto-updated.
Last update: 2026-10-07 03:13:05 UTC
README
Formerly contenir/maintenance-laminas-mvc; the old package is abandoned in favour of this one.
Laminas MVC adapter for contenir/contenir-maintenance.
When the admin (Contenir CMS) toggles maintenance mode, this module
short-circuits dispatch in the consuming Site with a 503 Service Unavailable
response until the flag is cleared.
Requirements
- PHP 8.3, 8.4 or 8.5
laminas/laminas-mvc^3.7contenir/contenir-maintenance^2.1
The 0.x releases, which support PHP 8.1, remain available from the 0.x
branch and v0.* tags; see UPGRADE-2.0.md.
Install
composer require contenir/contenir-maintenance-laminas-mvc
With laminas/laminas-component-installer, the module is registered for
you. Otherwise add it to config/modules.config.php:
return [ // ... 'Contenir\Maintenance\Laminas\Mvc', ];
How it works
Module::getConfig()returnsConfigProvider::__invoke(), which registersMaintenanceListenerFactoryforListener\MaintenanceListener.Module::onBootstrap()fetches the listener and attaches it toMvcEvent::EVENT_DISPATCHat priorityModule::DISPATCH_PRIORITY(10000), ahead of route-to-controller dispatch.- When the state is active and no bypass applies, the listener triggers
pagecache.disableon the application's event manager (socontenir/contenir-cache-laminas-mvcwill not store the page), builds a 503 withRetry-AfterandContent-Type: text/html; charset=utf-8headers, sets it on the event and stops propagation.
Configuration
All keys live under maintenance and are optional.
| Key | Default | Meaning |
|---|---|---|
state |
inactive | active, message, since: the state written by the admin |
retry_after |
600 |
Seconds sent in the Retry-After header |
bypass |
null |
callable(MvcEvent): bool; only a true return lets the request through |
body_template |
not set | Inline sprintf template; wins over body_template_path |
body_template_path |
bundled view/contenir/maintenance/index.phtml |
Template file; null or '' uses a minimal inline page |
State
The admin writes the state with Contenir\Maintenance\Repository\FileRepository
into a file the Site loads as config, for example
config/autoload/maintenance.local.php:
return [ 'maintenance' => [ 'state' => [ 'active' => true, 'message' => 'Back online by 5pm AEST.', 'since' => '2026-05-05T03:14:15+00:00', ], ], ];
The factory turns maintenance.state into an InMemoryRepository, so no
file is read per request. If the Site caches its merged config, the cache
must be cleared when the state changes. To read the state from another
source instead, register a service for
Contenir\Maintenance\MaintenanceRepositoryInterface; it always wins:
'service_manager' => [ 'factories' => [ MaintenanceRepositoryInterface::class => static fn(): MaintenanceRepositoryInterface => new FileRepository('/var/www/shared/maintenance.local.php'), ], ],
Bypass for operators
'maintenance' => [ 'bypass' => static function (\Laminas\Mvc\MvcEvent $event): bool { $auth = $event->getApplication()->getServiceManager()->get('auth'); return $auth->hasIdentity() && $auth->getIdentity()->isSuperAdmin(); }, ],
A bypass that is neither callable nor null makes the factory throw a
RuntimeException.
Response body
The body is a sprintf format. It receives two arguments, and any other %
must be written as %%:
| Placeholder | Value |
|---|---|
%s or %1$s |
The HTML-escaped message |
%2$s |
since, when maintenance started, as ISO 8601 (2026-05-05T03:14:15+00:00); an empty string when the state has no since or it could not be parsed |
A template that only uses %s is unaffected by since. To show it:
'body_template' => '<h1>Down for maintenance</h1><p>%1$s</p>' . '<p>Down since <time datetime="%2$s">%2$s</time></p>',
'maintenance' => [ // A file: .phtml and .php are included once when the listener is built, // so they may contain PHP; any other extension is read verbatim. 'body_template_path' => __DIR__ . '/../../view/maintenance.phtml', // Or an inline string, which wins over body_template_path: 'body_template' => '<h1>Down for maintenance</h1><p>%s</p>', 'retry_after' => 1800, ],
A body_template_path that is not a readable file makes the factory throw a
RuntimeException. The bundled template is self-contained (inline CSS, no
layout) so it renders even when the Site's assets are unavailable.
For anything more elaborate (layouts, view helpers, translation), replace the
MaintenanceListener service with your own factory. The listener's
constructor is
new MaintenanceListener(MaintenanceRepositoryInterface $repository, int $retryAfter = 600, string $bodyTemplate = MaintenanceListener::DEFAULT_BODY_TEMPLATE, ?callable $bypass = null).
Public API
| Symbol | Purpose |
|---|---|
Module |
getConfig(), onBootstrap(MvcEvent), attachListener(EventManagerInterface, MaintenanceListener), DISPATCH_PRIORITY |
ConfigProvider |
__invoke(), getDependencies(), getMaintenanceDefaults(), defaultBodyTemplatePath(), DEFAULT_BODY_TEMPLATE |
Factory\MaintenanceListenerFactory |
__invoke(ContainerInterface): MaintenanceListener |
Listener\MaintenanceListener |
__invoke(MvcEvent): ?Response, DEFAULT_BODY_TEMPLATE |
Development
The QA toolchain is contenir/contenir-qa-tools.
Mago is a standalone binary, installed
separately (brew install mago).
composer check # everything below composer cs-check # mago format --check && mago lint composer static-analysis # mago analyze composer test # unit suite: listener, module and factory with test doubles, no I/O composer test-integration # integration suite: template files, bundled view, real service and event managers composer test-coverage # both suites, clover.xml for Codecov composer mutation-test # Infection mutation testing over both suites
License
MIT. See LICENSE.