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

phpolar / phpolar / 20388752655

20 Dec 2025 03:52AM UTC coverage: 90.909% (-9.1%) from 100.0%
20388752655

push

github

web-flow
feat: add exception handling middleware (#528)

1 of 22 new or added lines in 2 files covered. (4.55%)

210 of 231 relevant lines covered (90.91%)

5.43 hits per line

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

53.33
/src/App.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Phpolar\Phpolar;
6

7
use Closure;
8
use Phpolar\Phpolar\DependencyInjection\DiTokens;
9
use Phpolar\Phpolar\Http\EmptyResponse;
10
use Phpolar\Phpolar\Http\MiddlewareQueueRequestHandler;
11
use Phpolar\Phpolar\Http\RoutingMiddleware;
12
use Psr\Container\ContainerInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use Psr\Http\Server\MiddlewareInterface;
15
use Psr\Http\Server\RequestHandlerInterface;
16
use Throwable;
17

18
/**
19
 * Represents a web application that handles and responds to HTTP requests.
20
 */
21
final class App
22
{
23
    private MiddlewareQueueRequestHandler $mainHandler;
24
    private MiddlewareInterface $routingMiddleware;
25
    private \Laminas\HttpHandlerRunner\Emitter\EmitterInterface $emitter;
26
    private static ?App $instance = null;
27

28

29
    /**
30
     * Prevent creation of multiple instances.
31
     */
32
    private function __construct(
33
        private ContainerInterface $container,
34
    ) {
35
        /**
36
         * @var MiddlewareQueueRequestHandler $handler
37
         */
38
        $handler = $this->container->get(MiddlewareQueueRequestHandler::class);
6✔
39
        /**
40
         * @var MiddlewareInterface
41
         */
42
        $routingMiddleware = $this->container->get(RoutingMiddleware::class);
6✔
43
        $this->routingMiddleware = $routingMiddleware;
6✔
44
        /**
45
         * @var \Laminas\HttpHandlerRunner\Emitter\EmitterInterface $emitter
46
         */
47
        $emitter = $this->container->get(DiTokens::RESPONSE_EMITTER);
6✔
48
        $this->emitter = $emitter;
6✔
49
        $this->mainHandler = $handler;
6✔
50
    }
51

52
    /**
53
     * Creates a singleton web-based application.
54
     */
55
    public static function create(
56
        ContainerInterface $container,
57
    ): App {
58
        return self::$instance ??= new self($container);
6✔
59
    }
60

61
    /**
62
     * Handle and respond to requests from clients.
63
     */
64
    public function receive(ServerRequestInterface $request): void
65
    {
66
        $this->setupRouting();
5✔
67

68
        $response = $this->mainHandler->handle($request);
5✔
69
        $this->emitter->emit($response);
5✔
70
    }
71

72
    private function queueMiddleware(MiddlewareInterface $middleware): void
73
    {
74
        $this->mainHandler->queue($middleware);
5✔
75
    }
76

77
    /**
78
     * Queue the given PSR-15 middleware.
79
     *
80
     * @see https://www.php-fig.org/psr/psr-15/#22-psrhttpservermiddlewareinterface
81
     * for more information on PSR-15 middleware.
82
     */
83
    public function use(MiddlewareInterface $middleware): App
84
    {
85
        $this->queueMiddleware($middleware);
1✔
86
        return $this;
1✔
87
    }
88

89
    /**
90
     * Configures the application for checking route authorization.
91
     */
92
    public function useAuthorization(): App
93
    {
94
        /** @var \Phpolar\Phpolar\Http\RequestProcessingHandler */
95
        $authRoutingHandler = $this->container->get(DiTokens::AUTHENTICATED_ROUTING_HANDLER);
1✔
96
        $this->routingMiddleware = new RoutingMiddleware($authRoutingHandler);
1✔
97
        return $this;
1✔
98
    }
99

100
    /**
101
     * Configures a session.
102
     *
103
     * @param array<string,mixed> $options
104
     */
105
    public function useSession(
106
        array $options = [
107
            "cookie_httponly" => true,
108
            "cookie_samesite" => "Strict",
109
            "cookie_secure" => true,
110
            "cookie_path" => true,
111
            "use_strict_mode" => true,
112
        ]
113
    ): App {
114
        session_status() !== PHP_SESSION_ACTIVE && session_start($options); // @codeCoverageIgnore
115
        return $this;
1✔
116
    }
117

118
    /**
119
     * Configures the server for CSRF attack mitigation.
120
     *
121
     * The server will not process the request if the
122
     * CSRF check fails.  The current response
123
     * will be set up for CSRF detection.
124
     *
125
     * @param array<string,bool|int|float|string> $sessionOpts
126
     */
127
    public function useCsrfMiddleware(
128
        array $sessionOpts = [
129
            "cookie_httponly" => true,
130
            "cookie_samesite" => "Strict",
131
            "cookie_secure" => true,
132
            "cookie_path" => true,
133
            "use_strict_mode" => true,
134
        ]
135
    ): App {
136
        $this->useSession($sessionOpts);
1✔
137
        /**
138
         * @var MiddlewareInterface $csrfPreRouting
139
         */
140
        $csrfPreRouting = $this->container->get(DiTokens::CSRF_CHECK_MIDDLEWARE);
1✔
141
        /**
142
         * @var MiddlewareInterface $csrfPostRouting
143
         */
144
        $csrfPostRouting = $this->container->get(DiTokens::CSRF_RESPONSE_FILTER_MIDDLEWARE);
1✔
145
        $this->queueMiddleware($csrfPreRouting);
1✔
146
        $this->queueMiddleware($csrfPostRouting);
1✔
147
        return $this;
1✔
148
    }
149

150
    /**
151
     * Configures global exception/throwable handling.
152
     */
153
    public function useExceptionHandler(
154
        ExceptionHandlerInterface $exceptionHandler
155
    ): App {
NEW
156
        set_exception_handler(
×
NEW
157
            Closure::bind(
×
NEW
158
                function (Throwable $e) use ($exceptionHandler): void {
×
159
                    /**
160
                     * The provided exception handler may emit a response
161
                     * and end execution.
162
                     */
NEW
163
                    $response = $exceptionHandler->handle($e);
×
164

NEW
165
                    $serverErrorHandler = $this->container->get(DiTokens::SERVER_ERROR_HANDLER);
×
NEW
166
                    $request = $this->container->get(ServerRequestInterface::class);
×
167

NEW
168
                    if ($serverErrorHandler instanceof RequestHandlerInterface === false) {
×
NEW
169
                        return;
×
170
                    }
171

NEW
172
                    if ($request instanceof ServerRequestInterface === false) {
×
NEW
173
                        return;
×
174
                    }
175

NEW
176
                    $this->emitter->emit(
×
NEW
177
                        match (true) {
×
NEW
178
                            $response instanceof EmptyResponse => $serverErrorHandler->handle($request),
×
NEW
179
                            default => $response
×
NEW
180
                        }
×
NEW
181
                    );
×
NEW
182
                },
×
NEW
183
                $this
×
NEW
184
            ),
×
NEW
185
        );
×
NEW
186
        return $this;
×
187
    }
188

189
    /**
190
     * Configures the application with associated
191
     * routes and handlers.
192
     */
193
    private function setupRouting(): void
194
    {
195
        $this->queueMiddleware($this->routingMiddleware);
5✔
196
    }
197
}
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