contenir / contenir-maintenance
Framework-agnostic maintenance-mode toggle for Contenir CMS — admin writes, Site reads.
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
- contenir/contenir-config: ^2.1
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: v2.1.0
This package is auto-updated.
Last update: 2026-10-07 03:13:08 UTC
README
Formerly contenir/maintenance; the old package is abandoned in favour of this one.
Framework-agnostic maintenance-mode toggle for Contenir CMS.
The CMS writes a maintenance flag (and optional message); the consuming Site (Mezzio, Laminas MVC, anything else) reads it on every request and returns 503 with the message until the flag is cleared.
This package provides the domain: a small immutable state value plus a
repository interface, with file-based and in-memory implementations.
Framework-specific middleware and listeners come from sibling packages such
as contenir/contenir-maintenance-laminas-mvc.
Requirements
- PHP 8.3, 8.4 or 8.5
contenir/contenir-config^2.1, installed automatically;Repository\FileRepositoryreads and writes through it
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
Usage
MaintenanceState
An immutable value with three public readonly properties:
| Property | Type | Meaning |
|---|---|---|
active |
bool |
Whether the Site should serve 503 |
message |
string |
Operator message shown on the 503 page; '' when inactive |
since |
?DateTimeImmutable |
When maintenance was switched on; always null when inactive |
use Contenir\Maintenance\MaintenanceState; $down = MaintenanceState::active('Back online by 5pm AEST.'); // since = now $down = MaintenanceState::active('Back online by 5pm AEST.', $startedAt); // explicit since $up = MaintenanceState::inactive(); $custom = new MaintenanceState(active: true, message: 'Down', since: null);
MaintenanceRepositoryInterface
interface MaintenanceRepositoryInterface { public function get(): MaintenanceState; /** @throws RuntimeException If the state cannot be persisted. */ public function save(MaintenanceState $state): void; }
Implementations treat a missing or unreadable backing store as inactive rather than throwing; write failures must throw so the admin UI can show them.
Repository\FileRepository (admin side)
use Contenir\Maintenance\MaintenanceState; use Contenir\Maintenance\Repository\FileRepository; $repo = new FileRepository('/var/www/shared/config/autoload/maintenance.local.php'); $repo->save(MaintenanceState::active('Back online by 5pm AEST.')); // ... later ... $repo->save(MaintenanceState::inactive()); $state = $repo->get(); if ($state->active) { // Render 503 with $state->message }
Writes are atomic and invalidate the file's opcache entry (via
contenir/contenir-config). A failed write throws
Contenir\Config\Exception\WriteException, a RuntimeException, with a
message such as Cannot write maintenance state to "...".
File format
FileRepository reads and writes a Laminas/Mezzio-style config file, so
the Site can drop it into config/autoload/ and have it merged into the
application config:
<?php return [ 'maintenance' => [ 'state' => [ 'active' => true, 'message' => 'Back online by 5pm AEST.', 'since' => '2026-05-05T03:14:15+00:00', ], ], ];
The repository owns only maintenance.state. Other top-level keys, and
sibling keys under maintenance, are preserved on save.
On read:
- a missing, unreadable or unparseable file, or one without
maintenance.state, is inactive; - a falsy
activeis inactive, and any lingering message is dropped; - a non-scalar
messagereads as''; - a missing or unparseable
sincereads asnull.
Repository\InMemoryRepository
Holds state in memory. It is shipped in src/ so Sites can build state from
their merged config without touching the filesystem, and so consumers can use
it in their own tests:
use Contenir\Maintenance\MaintenanceState; use Contenir\Maintenance\Repository\InMemoryRepository; $repo = new InMemoryRepository(MaintenanceState::active('Down')); // defaults to inactive
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: state value and in-memory repository, no I/O composer test-integration # integration suite: FileRepository against a temp directory composer test-coverage # both suites, clover.xml for Codecov composer mutation-test # Infection mutation testing over both suites
License
MIT. See LICENSE.