Passed
Push — master ( f2e1d1...eea237 )
by Divine Niiquaye
03:30
created

AppBuilder   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 67
c 4
b 0
f 0
dl 0
loc 200
ccs 0
cts 77
cp 0
rs 9.92
wmc 31

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A pipe() 0 8 3
A group() 0 5 1
A generateUri() 0 3 1
A options() 0 3 1
A match() 0 5 1
A patch() 0 3 1
A put() 0 3 1
A delete() 0 3 1
A post() 0 3 1
A pipes() 0 3 1
A compile() 0 10 2
F build() 0 65 16
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade;
19
20
use Fig\Http\Message\RequestMethodInterface;
21
use Flight\Routing\Interfaces\RouteMatcherInterface;
22
use Flight\Routing\Interfaces\UrlGeneratorInterface;
23
use Flight\Routing\Route;
24
use Flight\Routing\RouteCollection;
25
use Flight\Routing\Router;
26
use Rade\DI\Exceptions\ServiceCreationException;
27
use Symfony\Component\Config\ConfigCache;
28
29
/**
30
 * Create a cacheable application.
31
 *
32
 * @author Divine Niiquaye Ibok <[email protected]>
33
 */
34
class AppBuilder extends DI\ContainerBuilder implements RouterInterface, KernelInterface
35
{
36
    use Traits\HelperTrait;
37
38
    public function __construct(bool $debug = true)
39
    {
40
        parent::__construct(Application::class);
41
42
        $this->parameters['debug'] = $debug;
43
        $this->set('http.router', new DI\Definition(Router::class))->autowire([Router::class, RouteMatcherInterface::class, UrlGeneratorInterface::class]);
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     *
49
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
50
     */
51
    public function pipe(object ...$middlewares): void
52
    {
53
        foreach ($middlewares as $middleware) {
54
            if ($middleware instanceof DI\Definitions\Reference) {
55
                continue;
56
            }
57
58
            $this->set('http.middleware.' . \spl_object_id($middleware), $middleware)->public(false)->tag('router.middleware');
59
        }
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     *
65
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
66
     */
67
    public function pipes(string $named, object ...$middlewares): void
68
    {
69
        $this->definition('http.router')->bind('pipes', \func_get_args());
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function generateUri(string $routeName, array $parameters = []): DI\Definitions\Statement
76
    {
77
        return new DI\Definitions\Statement([new DI\Definitions\Reference('http.router'), 'generateUri'], \func_get_args());
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Rade\DI\Defin...Uri'), func_get_args()) returns the type Rade\DI\Definitions\Statement which is incompatible with the return type mandated by Rade\RouterInterface::generateUri() of Rade\GeneratedUri.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function match(string $pattern, array $methods = Route::DEFAULT_METHODS, $to = null)
84
    {
85
        $routes = $this->parameters['routes'] ??= new RouteCollection();
86
87
        return $routes->add(new Route($pattern, $methods, $to), false)->getRoute();
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function post(string $pattern, $to = null)
94
    {
95
        return $this->match($pattern, [RequestMethodInterface::METHOD_POST], $to);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function put(string $pattern, $to = null)
102
    {
103
        return $this->match($pattern, [RequestMethodInterface::METHOD_PUT], $to);
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function delete(string $pattern, $to = null)
110
    {
111
        return $this->match($pattern, [RequestMethodInterface::METHOD_DELETE], $to);
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function options(string $pattern, $to = null)
118
    {
119
        return $this->match($pattern, [RequestMethodInterface::METHOD_OPTIONS], $to);
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function patch(string $pattern, $to = null)
126
    {
127
        return $this->match($pattern, [RequestMethodInterface::METHOD_PATCH], $to);
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function group(string $prefix)
134
    {
135
        $routes = $this->parameters['routes'] ??= new RouteCollection();
136
137
        return $routes->group($prefix);
138
    }
139
140
    /**
141
     * Compiled container and build the application.
142
     *
143
     * supported $options config (defaults):
144
     * - cacheDir => composer's vendor dir, The directory where compiled application class will live in.
145
     * - shortArraySyntax => true, Controls whether [] or array() syntax should be used for an array.
146
     * - maxLineLength => 200, Max line of generated code in compiled container class.
147
     * - maxDefinitions => 500, Max definitions reach before splitting into traits.
148
     *
149
     * @param callable            $application write services in here
150
     * @param array<string,mixed> $options
151
     *
152
     * @throws \ReflectionException
153
     */
154
    public static function build(callable $application, array $options = []): Application
155
    {
156
        $defFile = 'load_' . ($containerClass = 'App_' . (($debug = $options['debug'] ?? false) ? 'Debug' : '') . 'Container') . '.php';
157
        $cacheDir = $options['cacheDir'] ?? \dirname((new \ReflectionClass(\Composer\Autoload\ClassLoader::class))->getFileName(), 2);
158
        $cachePath = $cacheDir . '/' . $containerClass . '_' . \PHP_SAPI . \PHP_OS . \PHP_VERSION_ID . '.php';
159
160
        // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
161
        $errorLevel = \error_reporting(\E_ALL ^ \E_WARNING);
162
163
        try {
164
            if (\is_file($cachePath)) {
165
                include $cacheDir . '/' . $defFile;
166
                include $cachePath;
167
                $container = new $containerClass();
168
169
                if (!$debug || ($cache = new ConfigCache($cachePath, $debug))->isFresh()) {
170
                    \error_reporting($errorLevel);
171
172
                    return $container;
173
                }
174
            } else {
175
                \is_dir($cacheDir) ?: \mkdir($cacheDir, 0777, true);
176
            }
177
        } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
178
        }
179
180
        if ($debug && \interface_exists(\Tracy\IBarPanel::class)) {
181
            Debug\Tracy\ContainerPanel::$compilationTime = \microtime(true);
182
        }
183
184
        try {
185
            if ($lock = \fopen($cachePath . '.lock', 'w')) {
186
                \flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
187
188
                if (!\flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) {
189
                    \fclose($lock);
190
                    $lock = null;
191
                } elseif (isset($container) && \is_file($cachePath)) {
192
                    \flock($lock, \LOCK_UN);
193
                    \fclose($lock);
194
195
                    return new $containerClass();
196
                }
197
            }
198
        } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
199
        } finally {
200
            \error_reporting($errorLevel);
201
        }
202
203
        $application($container = new static($debug));
204
        $requiredPaths = $container->parameters['project.require_paths'] ?? []; // Autoload require hot paths ...
205
206
        $container->addNodeVisitor(new DI\NodeVisitor\MethodsResolver());
207
        $container->addNodeVisitor(new DI\NodeVisitor\AutowiringResolver());
208
        $container->addNodeVisitor($splitter = new DI\NodeVisitor\DefinitionsSplitter($options['maxDefinitions'] ?? 500, $defFile));
209
210
        if (!isset($cache)) {
211
            $cache = new ConfigCache($cachePath, $debug); // ... or create a new cache
212
        }
213
214
        $cache->write($container->compile($options + \compact('containerClass')), $container->getResources()); // Generate container class
215
        require $splitter->buildTraits($cacheDir, $debug, $requiredPaths);
216
        require $cachePath;
217
218
        return new $containerClass();
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224
    public function compile(array $options = [])
225
    {
226
        $this->parameters['project.compiled_container_class'] = $options['containerClass'];
227
        unset($this->definitions['config.builder.loader_resolver'], $this->parameters['project.require_paths']);
228
229
        if (isset($this->parameters['routes'])) {
230
            throw new ServiceCreationException(\sprintf('The %s extension needs to be registered before adding routes.', DI\Extensions\RoutingExtension::class));
231
        }
232
233
        return parent::compile($options);
234
    }
235
}
236