Test Failed
Push — master ( f1767b...06a3a6 )
by Divine Niiquaye
03:30
created

AppBuilder::compile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 5
cp 0
crap 6
rs 10
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\Route;
23
use Flight\Routing\RouteCollection;
24
use Flight\Routing\Router;
25
use Rade\DI\Exceptions\ServiceCreationException;
26
use Symfony\Component\Config\ConfigCache;
27
28
/**
29
 * Create a cacheable application.
30
 *
31
 * @author Divine Niiquaye Ibok <[email protected]>
32
 */
33
class AppBuilder extends DI\ContainerBuilder implements RouterInterface, KernelInterface
34
{
35
    use Traits\HelperTrait;
36
37
    private int $routeIndex = -1;
38
    private string $routesId = 'router.collection_a';
39
40
    public function __construct(bool $debug = true)
41
    {
42
        parent::__construct(Application::class);
43
44
        $this->parameters['debug'] = $debug;
45
        $this->set('http.router', new DI\Definition(Router::class))->autowire([Router::class, RouteMatcherInterface::class]);
46
    }
47
48
    /**
49
     * @param array<int,mixed> $arguments
50
     *
51
     * @return $this
52
     */
53
    public function __call(string $name, array $arguments)
54
    {
55
        if (!(isset($this->parameters['routes'][$this->routeIndex]) || \method_exists(Route::class, $name))) {
56
            throw new \BadMethodCallException(\sprintf('Call to undefined method %s::%s()', __CLASS__, $name));
57
        }
58
59
        if (1 === \count($arguments)) {
60
            $arguments = $arguments[0];
61
        }
62
63
        $this->parameters['routes'][$this->routeIndex][$name] = $arguments;
64
65
        return $this;
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     *
71
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
72
     */
73
    public function pipe(object ...$middlewares): void
74
    {
75
        foreach ($middlewares as $middleware) {
76
            if ($middleware instanceof DI\Definitions\Reference) {
77
                continue;
78
            }
79
80
            $this->autowire('http.middleware.' . \spl_object_id($middleware), $middleware)->public(false);
81
        }
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     *
87
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
88
     */
89
    public function pipes(string $named, object ...$middlewares): void
90
    {
91
        $this->definition('http.router')->bind('pipes', \func_get_args());
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97
    public function generateUri(string $routeName, array $parameters = []): DI\Definitions\Statement
98
    {
99
        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...
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function match(string $pattern, array $methods = Route::DEFAULT_METHODS, $to = null)
106
    {
107
        $this->parameters['routes'][++$this->routeIndex] = ['path' => $pattern, 'method' => $methods, 'run' => $to];
108
109
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Rade\AppBuilder which is incompatible with the return type mandated by Rade\RouterInterface::match() of Flight\Routing\Route.

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...
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function post(string $pattern, $to = null)
116
    {
117
        return $this->match($pattern, [RequestMethodInterface::METHOD_POST], $to);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->match($pat...ace::METHOD_POST), $to) returns the type Rade\AppBuilder which is incompatible with the return type mandated by Rade\RouterInterface::post() of Flight\Routing\Route.

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...
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function put(string $pattern, $to = null)
124
    {
125
        return $this->match($pattern, [RequestMethodInterface::METHOD_PUT], $to);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->match($pat...face::METHOD_PUT), $to) returns the type Rade\AppBuilder which is incompatible with the return type mandated by Rade\RouterInterface::put() of Flight\Routing\Route.

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...
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function delete(string $pattern, $to = null)
132
    {
133
        return $this->match($pattern, [RequestMethodInterface::METHOD_DELETE], $to);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->match($pat...e::METHOD_DELETE), $to) returns the type Rade\AppBuilder which is incompatible with the return type mandated by Rade\RouterInterface::delete() of Flight\Routing\Route.

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...
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function options(string $pattern, $to = null)
140
    {
141
        return $this->match($pattern, [RequestMethodInterface::METHOD_OPTIONS], $to);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->match($pat...::METHOD_OPTIONS), $to) returns the type Rade\AppBuilder which is incompatible with the return type mandated by Rade\RouterInterface::options() of Flight\Routing\Route.

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...
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function patch(string $pattern, $to = null)
148
    {
149
        return $this->match($pattern, [RequestMethodInterface::METHOD_PATCH], $to);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->match($pat...ce::METHOD_PATCH), $to) returns the type Rade\AppBuilder which is incompatible with the return type mandated by Rade\RouterInterface::patch() of Flight\Routing\Route.

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...
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function group(string $prefix)
156
    {
157
        $this->set($this->routesId, new DI\Definition(RouteCollection::class, [$prefix]));
158
        $this->parameters['routes'][++$this->routeIndex] = ['bind' => '@' . $this->routesId];
159
160
        ++$this->routesId;
161
162
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Rade\AppBuilder which is incompatible with the return type mandated by Rade\RouterInterface::group() of Flight\Routing\RouteCollection.

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...
163
    }
164
165
    /**
166
     * Compiled container and build the application.
167
     *
168
     * supported $options config (defaults):
169
     * - cacheDir => composer's vendor dir, The directory where compiled application class will live in.
170
     * - shortArraySyntax => true, Controls whether [] or array() syntax should be used for an array.
171
     * - maxLineLength => 200, Max line of generated code in compiled container class.
172
     * - maxDefinitions => 500, Max definitions reach before splitting into traits.
173
     *
174
     * @param callable            $application write services in here
175
     * @param array<string,mixed> $options
176
     *
177
     * @throws \ReflectionException
178
     */
179
    public static function build(callable $application, array $options = []): Application
180
    {
181
        $debug = $options['debug'] ?? $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? false;
182
        $defFile = 'load_' . ($containerClass = 'App_' . ($debug ? 'Debug' : '') . 'Container') . '.php';
183
        $cacheDir = $options['cacheDir'] ?? \dirname((new \ReflectionClass(\Composer\Autoload\ClassLoader::class))->getFileName(), 2);
184
        $cache = new ConfigCache($cachePath = $cacheDir . '/' . $containerClass . '_' . \PHP_SAPI . '.php', $debug);
185
186
        if (!$cache->isFresh()) {
187
            $appBuilder = new static($debug);
188
189
            if ($debug && \interface_exists(\Tracy\IBarPanel::class)) {
190
                Debug\Tracy\ContainerPanel::$compilationTime = \microtime(true);
191
            }
192
193
            $application($appBuilder);
194
195
            // Autoload require hot paths ...
196
            $requiredPaths = $appBuilder->parameters['project.require_paths'] ?? [];
197
            unset($appBuilder->parameters['project.require_paths']);
198
199
            // Default node visitors.
200
            $appBuilder->addNodeVisitor(new DI\NodeVisitor\MethodsResolver());
201
            $appBuilder->addNodeVisitor(new DI\NodeVisitor\AutowiringResolver());
202
            $appBuilder->addNodeVisitor($splitter = new DI\NodeVisitor\DefinitionsSplitter($options['maxDefinitions'] ?? 500, $defFile));
203
204
            // Write the compiled application class to path.
205
            $cache->write($appBuilder->compile($options + \compact('containerClass')), $appBuilder->getResources());
0 ignored issues
show
Bug introduced by
It seems like $appBuilder->compile($op...pact('containerClass')) can also be of type PhpParser\Node[]; however, parameter $content of Symfony\Component\Config...kerConfigCache::write() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

205
            $cache->write(/** @scrutinizer ignore-type */ $appBuilder->compile($options + \compact('containerClass')), $appBuilder->getResources());
Loading history...
206
207
            require $splitter->buildTraits($cacheDir, $appBuilder->isDebug(), $requiredPaths);
208
        } else {
209
            require_once $cacheDir . '/' . $defFile;
210
        }
211
212
        require $cachePath;
213
214
        return new $containerClass();
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function compile(array $options = [])
221
    {
222
        $this->parameters['project.compiled_container_class'] = $options['containerClass'];
223
        unset($this->definitions['config.builder.loader_resolver']);
224
225
        if (isset($this->parameters['routes'])) {
226
            throw new ServiceCreationException(\sprintf('The %s extension needs to be registered before adding routes.', DI\Extensions\RoutingExtension::class));
227
        }
228
229
        return parent::compile($options);
230
    }
231
}
232