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