• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / maintenancemode / 25548350420

08 May 2026 09:28AM UTC coverage: 77.17% (+1.7%) from 75.492%
25548350420

push

github

daycry
docs: full docs/ tree and modernised README

README rewritten as a tight landing page (~115 lines, was 511):
- Three badge sections (Package / Quality / Community) matching the
  daycry/* convention, including per-workflow status badges for PHPUnit,
  PHPStan, Psalm, Rector, Code Style, CodeQL plus Coveralls.
- Highlights, install, quick start, plus a "Quality bar" table linking to
  each workflow file. Everything else is one click away in docs/.

New docs/ tree (16 files):
- README.md (index), installation, configuration, commands, bypass,
  filters-and-events, storage-drivers, architecture, security,
  troubleshooting, faq, upgrade, roadmap.
- examples/: basic-maintenance, scheduled-window, api-json-response,
  webhook-notifications (all functional today), multi-tenant and
  cdn-cloudflare (planned, with workarounds).

The previous README's misleading content has been removed:
- "v2.0.0 Latest" claim and the v1/v2 changelog block (superseded by
  CHANGELOG.md).
- setcookie('maintenance_bypass', 'your-secret-key', ...) snippet (the
  legacy cookie-bypass logic was effectively broken; v3 uses a 32-byte
  random cookie_value compared with hash_equals).
- mm:migrate --to=cache flag that never existed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

649 of 841 relevant lines covered (77.17%)

31.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.62
/src/Controllers/Maintenance.php
1
<?php
2

3
namespace Daycry\Maintenance\Controllers;
4

5
use CodeIgniter\Controller;
6
use CodeIgniter\HTTP\IncomingRequest;
7
use CodeIgniter\HTTP\ResponseInterface;
8
use Config\Services;
9
use Daycry\Maintenance\DTO\MaintenanceData;
10
use Daycry\Maintenance\Exceptions\ServiceUnavailableException;
11
use Daycry\Maintenance\Services\CheckResult;
12
use Daycry\Maintenance\Services\MaintenanceService;
13

14
class Maintenance extends Controller
15
{
16
    /**
17
     * Static entry point used by the legacy filter (and by tests).
18
     *
19
     * Returns true when the request should be allowed through, or a
20
     * {@see ResponseInterface} when the package wants to short-circuit with a
21
     * redirect / JSON 503 response. Throws a
22
     * {@see ServiceUnavailableException} when maintenance is active and no
23
     * bypass matched and HTML rendering is requested.
24
     *
25
     * @return bool|ResponseInterface
26
     */
27
    public static function check()
28
    {
29
        // CLI is always allowed except in the testing environment, where we
30
        // simulate a real HTTP request to exercise the bypass logic.
31
        if (is_cli() && ENVIRONMENT !== 'testing') {
56✔
32
            return true;
×
33
        }
34

35
        $service = MaintenanceService::fromCurrentConfig();
56✔
36

37
        if (! $service->isActive()) {
56✔
38
            return true;
9✔
39
        }
40

41
        $request = Services::request();
48✔
42

43
        // The CLI short-circuit above guarantees we have an HTTP request here.
44
        if (! $request instanceof IncomingRequest) {
48✔
45
            return true;
×
46
        }
47

48
        $result = $service->check($request);
48✔
49

50
        if ($result->allowed) {
48✔
51
            self::applyAutoCookie($result);
21✔
52

53
            return true;
21✔
54
        }
55

56
        $data = $service->getData();
28✔
57

58
        // Per-window redirect short-circuits everything else.
59
        if ($data !== null && $data->redirect_url !== '') {
28✔
60
            return Services::response()
×
61
                ->redirect($data->redirect_url, 'auto', 302);
×
62
        }
63

64
        // JSON content negotiation.
65
        if ($service->shouldRespondJson($request)) {
28✔
66
            return self::buildJsonResponse($service, $data);
×
67
        }
68

69
        // HTML 503: set the Retry-After header and let the framework render the
70
        // exception template. resolveTemplate() decides which one.
71
        Services::response()->setHeader('Retry-After', (string) $service->getRetryAfterSeconds());
28✔
72

73
        $message = $data === null ? '' : $data->message;
28✔
74

75
        throw ServiceUnavailableException::forServerDown(
28✔
76
            $message !== '' ? $message : $service->getDefaultMessage(),
28✔
77
        );
28✔
78
    }
79

80
    /**
81
     * Persist the bypass cookie on the current response when the service asked
82
     * for it (Sprint 3 auto-cookie). The framework will flush it with the
83
     * outbound response.
84
     */
85
    private static function applyAutoCookie(CheckResult $result): void
86
    {
87
        if ($result->setCookie === null) {
21✔
88
            return;
18✔
89
        }
90

91
        Services::response()->setCookie([
3✔
92
            'name'     => $result->setCookie['name'],
3✔
93
            'value'    => $result->setCookie['value'],
3✔
94
            'expire'   => $result->setCookie['lifetime'],
3✔
95
            'httponly' => true,
3✔
96
            'samesite' => 'Lax',
3✔
97
            'secure'   => self::isHttps(),
3✔
98
        ]);
3✔
99
    }
100

101
    private static function isHttps(): bool
102
    {
103
        $request = Services::request();
3✔
104

105
        if ($request instanceof IncomingRequest) {
3✔
106
            return $request->isSecure();
3✔
107
        }
108

109
        return false;
×
110
    }
111

112
    private static function buildJsonResponse(
113
        MaintenanceService $service,
114
        ?MaintenanceData $data,
115
    ): ResponseInterface {
116
        $message = $data === null ? '' : $data->message;
×
117
        if ($message === '') {
×
118
            $message = $service->getDefaultMessage();
×
119
        }
120

121
        $body = [
×
122
            'status'      => 503,
×
123
            'error'       => 'Service Unavailable',
×
124
            'message'     => $message,
×
125
            'retry_after' => $service->getRetryAfterSeconds(),
×
126
        ];
×
127

128
        if ($data !== null && $data->estimated_end !== null) {
×
129
            $body['estimated_end'] = gmdate('c', $data->estimated_end);
×
130
        }
131

132
        return Services::response()
×
133
            ->setStatusCode(503)
×
134
            ->setHeader('Retry-After', (string) $service->getRetryAfterSeconds())
×
135
            ->setJSON($body);
×
136
    }
137
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc