Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
57.97% covered (warning)
57.97%
40 / 69
58.82% covered (warning)
58.82%
20 / 34
CRAP
0.00% covered (danger)
0.00%
0 / 1
App
57.97% covered (warning)
57.97%
40 / 69
58.82% covered (warning)
58.82%
20 / 34
158.79
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addErrorHandler
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 create
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 boot
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 run
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 config
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 core
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 bootstrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 plugin
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 section
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 collection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 icons
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 navigation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 meta
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 node
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 renderer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 routes
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addRoute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addGroup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 group
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 staticRoute
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 before
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 beforeHandlers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 after
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 afterHandlers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMiddleware
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 middleware
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 logger
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 container
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 factory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 router
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __call
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray;
6
7use BadMethodCallException;
8use Celema\Container\Container;
9use Celema\Container\Entry;
10use Celema\Core\App as CoreApp;
11use Celema\Core\Factory\Factory;
12use Celema\Core\Factory\Nyholm;
13use Celema\Core\Plugin as CorePlugin;
14use Celema\Router\AddsRoutes;
15use Celema\Router\After;
16use Celema\Router\Before;
17use Celema\Router\Group;
18use Celema\Router\Route;
19use Celema\Router\RouteAdder;
20use Celema\Router\Router;
21use Closure;
22use Cosray\Icons\Provider as IconProvider;
23use Cosray\Node\Types;
24use Cosray\Panel\Dashboard;
25use Cosray\View\Boiler\Error\Handler as ErrorHandler;
26use Psr\Http\Message\ResponseInterface as Response;
27use Psr\Http\Message\ServerRequestInterface as Request;
28use Psr\Http\Server\MiddlewareInterface as Middleware;
29use Psr\Log\LoggerInterface as Logger;
30
31/**
32 * Convenience facade for CMS applications.
33 */
34class App implements RouteAdder
35{
36    use AddsRoutes;
37
38    protected readonly CoreApp $core;
39    protected readonly Bootstrap $bootstrap;
40    protected bool $booted = false;
41
42    public function __construct(
43        public readonly Config $config,
44        Factory $factory,
45        Router $router,
46        Container $container,
47    ) {
48        $this->core = new CoreApp($factory, $router, $container);
49        $this->bootstrap = new Bootstrap($config);
50        $container->add(self::class, $this);
51        $this->addErrorHandler($container, $factory);
52    }
53
54    protected function addErrorHandler(Container $container, Factory $factory): void
55    {
56        if ($this->config->error->enabled) {
57            $this->core->errorHandler(
58                new ErrorHandler($this->config, $factory, new ContainerLogger($container))->create(),
59            );
60        }
61    }
62
63    /** @param array<string, mixed> $settings */
64    public static function create(string $root, array $settings = []): self
65    {
66        $config = new Config($root, $settings);
67
68        return new self(
69            $config,
70            new Nyholm(),
71            new Router($config->path->prefix),
72            new Container(),
73        );
74    }
75
76    public function boot(): self
77    {
78        if (!$this->booted) {
79            $this->core->load($this->bootstrap);
80            $this->core->addRoute($this->bootstrap->catchallRoute());
81            $this->booted = true;
82        }
83
84        return $this;
85    }
86
87    public function run(?Request $request = null): Response|false
88    {
89        $this->boot();
90
91        return $this->core->run($request);
92    }
93
94    public function config(): Config
95    {
96        return $this->config;
97    }
98
99    public function core(): CoreApp
100    {
101        return $this->core;
102    }
103
104    public function bootstrap(): Bootstrap
105    {
106        return $this->bootstrap;
107    }
108
109    /** @param class-string<Plugin\Plugin>|Plugin\Plugin $plugin */
110    public function plugin(string|Plugin\Plugin $plugin): self
111    {
112        $this->bootstrap->plugin($plugin);
113
114        return $this;
115    }
116
117    public function section(string $name): Section
118    {
119        return $this->bootstrap->section($name);
120    }
121
122    /** @param class-string<Collection> $class */
123    public function collection(string $class): Collection\Ref
124    {
125        return $this->bootstrap->collection($class);
126    }
127
128    /** @param class-string<IconProvider>|IconProvider $icons */
129    public function icons(string|IconProvider $icons, bool $replace = false): self
130    {
131        $this->bootstrap->icons($icons, $replace);
132
133        return $this;
134    }
135
136    public Dashboard $dashboard {
137        get => $this->bootstrap->dashboard;
138    }
139
140    public function navigation(): Navigation
141    {
142        return $this->bootstrap->navigation();
143    }
144
145    public function meta(): Types
146    {
147        return $this->bootstrap->meta();
148    }
149
150    /** @param class-string $class */
151    public function node(string $class): self
152    {
153        $this->bootstrap->node($class);
154
155        return $this;
156    }
157
158    /** @param class-string<Renderer> $class */
159    public function renderer(string $id, string $class): Entry
160    {
161        return $this->bootstrap->renderer($id, $class);
162    }
163
164    public function load(CorePlugin $plugin): self
165    {
166        $this->core->load($plugin);
167
168        return $this;
169    }
170
171    /** @param Closure(Router $router): void $creator */
172    public function routes(Closure $creator, string $cacheFile = '', bool $shouldCache = true): self
173    {
174        $this->core->routes($creator, $cacheFile, $shouldCache);
175
176        return $this;
177    }
178
179    public function addRoute(Route $route): Route
180    {
181        return $this->core->addRoute($route);
182    }
183
184    public function addGroup(Group $group): void
185    {
186        $group->register($this->core);
187    }
188
189    public function group(
190        string $patternPrefix,
191        Closure $createClosure,
192        string $namePrefix = '',
193    ): void {
194        $this->core->group($patternPrefix, $createClosure, $namePrefix);
195    }
196
197    public function staticRoute(string $prefix, string $path, string $name = ''): self
198    {
199        $this->core->staticRoute($prefix, $path, $name);
200
201        return $this;
202    }
203
204    public function before(Before $beforeHandler): self
205    {
206        $this->core->before($beforeHandler);
207
208        return $this;
209    }
210
211    /** @return list<Before> */
212    public function beforeHandlers(): array
213    {
214        return $this->core->beforeHandlers();
215    }
216
217    public function after(After $afterHandler): self
218    {
219        $this->core->after($afterHandler);
220
221        return $this;
222    }
223
224    /** @return list<After> */
225    public function afterHandlers(): array
226    {
227        return $this->core->afterHandlers();
228    }
229
230    /** @return list<Middleware> */
231    public function getMiddleware(): array
232    {
233        return $this->core->getMiddleware();
234    }
235
236    public function middleware(Middleware ...$middleware): self
237    {
238        $this->core->middleware(...$middleware);
239
240        return $this;
241    }
242
243    public function logger(Logger|callable $logger): self
244    {
245        $this->core->logger($logger);
246
247        return $this;
248    }
249
250    public function container(): Container
251    {
252        return $this->core->container();
253    }
254
255    public function factory(): Factory
256    {
257        return $this->core->factory();
258    }
259
260    public function router(): Router
261    {
262        return $this->core->router();
263    }
264
265    /**
266     * @param non-empty-string $key
267     * @param class-string|object $value
268     */
269    public function register(string $key, object|string $value): Entry
270    {
271        return $this->core->register($key, $value);
272    }
273
274    /** @param list<mixed> $args */
275    public function __call(string $method, array $args): mixed
276    {
277        $coreCallable = is_callable([$this->core, $method]);
278        $bootstrapCallable = is_callable([$this->bootstrap, $method]);
279
280        if ($coreCallable && $bootstrapCallable) {
281            throw new BadMethodCallException("Ambiguous CMS app method: {$method}");
282        }
283
284        if ($bootstrapCallable) {
285            return $this->bootstrap->{$method}(...$args);
286        }
287
288        if ($coreCallable) {
289            return $this->core->{$method}(...$args);
290        }
291
292        throw new BadMethodCallException("Unknown CMS app method: {$method}");
293    }
294}