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

AJenbo / agcms / 21419177689

28 Jan 2026 12:03AM UTC coverage: 52.306% (-1.4%) from 53.72%
21419177689

Pull #77

github

web-flow
Merge 25510f18e into 655757ab5
Pull Request #77: Bump phpunit/phpunit from 9.6.11 to 9.6.33 in /application

3039 of 5810 relevant lines covered (52.31%)

12.21 hits per line

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

81.58
/application/inc/Application.php
1
<?php
2

3
namespace App;
4

5
use App\Contracts\Middleware;
6
use App\Exceptions\Handler as ExceptionHandler;
7
use App\Http\Controllers\AbstractController;
8
use App\Http\Controllers\Base;
9
use App\Http\Request;
10
use App\Services\ConfigService;
11
use Symfony\Component\HttpFoundation\RedirectResponse;
12
use Symfony\Component\HttpFoundation\Response;
13
use Throwable;
14

15
class Application
16
{
17
    /** @var ?self */
18
    private static $instance;
19

20
    private string $basePath;
21

22
    /**
23
     * All of the global middleware for the application.
24
     *
25
     * @var array<class-string<Middleware>>
26
     */
27
    protected array $middleware = [];
28

29
    /** @var array<string, array<int, Route>> */
30
    private array $routes = [];
31

32
    /** @var array<class-string<object>, object> */
33
    private array $services = [];
34

35
    /**
36
     * Set up the enviroment.
37
     */
38
    public function __construct(string $basePath)
39
    {
40
        self::$instance = $this;
149✔
41
        $this->services[self::class] = $this;
149✔
42
        $this->basePath = $basePath;
149✔
43

44
        $this->initErrorLogging();
149✔
45
        $this->setLocale();
149✔
46
        $this->loadTranslations();
149✔
47
        $this->loadRoutes();
149✔
48
    }
49

50
    public function environment(string $environment): bool
51
    {
52
        return $environment === ConfigService::getString('enviroment', 'develop');
149✔
53
    }
54

55
    /**
56
     * Set error loggin.
57
     */
58
    private function initErrorLogging(): void
59
    {
60
        if ($this->environment('develop')) {
149✔
61
            ini_set('display_errors', '1');
×
62
            error_reporting(-1);
×
63
        }
64
    }
65

66
    /**
67
     * Set locale and endcodings.
68
     */
69
    private function setLocale(): void
70
    {
71
        date_default_timezone_set(ConfigService::getString('timezone', 'Europe/Copenhagen'));
149✔
72

73
        setlocale(LC_ALL, ConfigService::getString('locale', 'C'));
149✔
74
        setlocale(LC_NUMERIC, 'C');
149✔
75

76
        mb_language('uni');
149✔
77
        mb_detect_order('UTF-8, ISO-8859-1');
149✔
78
        mb_internal_encoding('UTF-8');
149✔
79
    }
80

81
    /**
82
     * Load translations.
83
     */
84
    private function loadTranslations(): void
85
    {
86
        bindtextdomain('app', $this->basePath . '/theme/locale');
149✔
87
        bind_textdomain_codeset('app', 'UTF-8');
149✔
88
        textdomain('app');
149✔
89
    }
90

91
    /**
92
     * Load application routes.
93
     */
94
    private function loadRoutes(): void
95
    {
96
        Routes::load($this);
149✔
97
    }
98

99
    /**
100
     * Get base path for the running application.
101
     */
102
    public function basePath(string $path = ''): string
103
    {
104
        return $this->basePath . $path;
66✔
105
    }
106

107
    /**
108
     * Get the most recent instance.
109
     */
110
    public static function getInstance(): self
111
    {
112
        if (self::$instance) {
149✔
113
            return self::$instance;
149✔
114
        }
115

116
        return new self(realpath(__DIR__ . '/../..') ?: '');
×
117
    }
118

119
    /**
120
     * Gets a service.
121
     *
122
     * @template T of object
123
     *
124
     * @param class-string<T> $id The service identifier
125
     *
126
     * @return T The associated service
127
     */
128
    public function get(string $id): object
129
    {
130
        if (!isset($this->services[$id])) {
149✔
131
            $this->services[$id] = new $id();
149✔
132
        }
133

134
        $service = $this->services[$id];
149✔
135
        assert($service instanceof $id);
136

137
        return $service;
149✔
138
    }
139

140
    /**
141
     * Add new middleware to the application.
142
     *
143
     * @param array<class-string<Middleware>>|class-string<Middleware> $middleware
144
     *
145
     * @return $this
146
     */
147
    public function middleware($middleware): self
148
    {
149
        $middleware = (array)$middleware;
149✔
150

151
        $this->middleware = array_unique(array_merge($this->middleware, $middleware));
149✔
152

153
        return $this;
149✔
154
    }
155

156
    /**
157
     * Add a route.
158
     *
159
     * @param class-string<AbstractController> $controller
160
     */
161
    public function addRoute(string $method, string $uri, string $controller, string $action): void
162
    {
163
        $this->routes[$method][] = new Route($uri, $controller, $action);
149✔
164
    }
165

166
    /**
167
     * Run the application.
168
     */
169
    public function run(): void
170
    {
171
        $request = Request::createFromGlobals();
×
172
        $response = $this->handle($request);
×
173
        $response->send();
×
174
    }
175

176
    /**
177
     * Handle a request.
178
     */
179
    public function handle(Request $request): Response
180
    {
181
        $this->services[Request::class] = $request;
149✔
182

183
        try {
184
            $response = $this->dispatch($request);
149✔
185
        } catch (Throwable $exception) {
21✔
186
            $response = $this->handleException($request, $exception);
21✔
187
        }
188
        $response->prepare($request);
149✔
189
        $response->isNotModified($request); // Set up 304 response if relevant
149✔
190

191
        return $response;
149✔
192
    }
193

194
    /**
195
     * Handle the given exception.
196
     */
197
    protected function handleException(Request $request, Throwable $exception): Response
198
    {
199
        $handler = $this->get(ExceptionHandler::class);
21✔
200

201
        $handler->report($exception);
21✔
202

203
        return $handler->render($request, $exception);
21✔
204
    }
205

206
    /**
207
     * Find a matching route for the current request.
208
     */
209
    private function dispatch(Request $request): Response
210
    {
211
        $method = $request->getMethod();
149✔
212
        $requestUrl = rawurldecode($request->getPathInfo());
149✔
213
        $processRequest = $this->matchRoute($method, $requestUrl);
149✔
214

215
        foreach ($this->middleware as $middleware) {
149✔
216
            $processRequest = $this->wrapMiddleware($middleware, $processRequest);
149✔
217
        }
218

219
        return $processRequest($request);
149✔
220
    }
221

222
    /**
223
     * Wrap closure in a middle ware call.
224
     *
225
     * @return callable(Request): Response
226
     */
227
    private function matchRoute(string $method, string $requestUrl): callable
228
    {
229
        if ('HEAD' === $method) {
149✔
230
            $method = 'GET';
1✔
231
        }
232

233
        foreach ($this->routes[$method] as $route) {
149✔
234
            if (preg_match('%^' . $route->getUri() . '$%u', $requestUrl, $matches)) {
149✔
235
                return function (Request $request) use ($route, $matches): Response {
148✔
236
                    $matches[0] = $request;
148✔
237

238
                    $class = $route->getController();
148✔
239
                    /** @var callable((Request|string)...): Response */
240
                    $callable = [new $class(), $route->getAction()];
148✔
241

242
                    return $callable(...$matches);
148✔
243
                };
148✔
244
            }
245

246
            if (preg_match('%^' . $route->getUri() . '$%u', $requestUrl . '/', $matches)) {
140✔
247
                return $this->redirectToFolderPath($requestUrl);
×
248
            }
249
        }
250

251
        return function (Request $request): RedirectResponse {
1✔
252
            return (new Base())->redirectToSearch($request);
×
253
        };
1✔
254
    }
255

256
    /**
257
     * Wrap closure in a middle ware call.
258
     *
259
     * @param class-string<Middleware>    $middlewareClass
260
     * @param callable(Request): Response $next
261
     *
262
     * @return callable(Request): Response
263
     */
264
    private function wrapMiddleware(string $middlewareClass, callable $next): callable
265
    {
266
        return function (Request $request) use ($middlewareClass, $next): Response {
149✔
267
            return (new $middlewareClass())->handle($request, $next);
149✔
268
        };
149✔
269
    }
270

271
    /**
272
     * Generate a redirect for the requested path with a / appended to the path.
273
     *
274
     * @return callable(Request): Response
275
     */
276
    private function redirectToFolderPath(string $requestUrl): callable
277
    {
278
        return function (Request $request) use ($requestUrl): RedirectResponse {
×
279
            $query = $request->getQueryString() ?: '';
×
280
            if ($query) {
×
281
                $query = '?' . $query;
×
282
            }
283

284
            return redirect($requestUrl . '/' . $query, Response::HTTP_PERMANENTLY_REDIRECT);
×
285
        };
×
286
    }
287
}
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