Passed
Push — master ( 91959b...1ef4c5 )
by Divine Niiquaye
12:11
created

AppBuilder::__call()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 13
ccs 0
cts 7
cp 0
crap 20
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
    public function __construct(bool $debug = true)
38
    {
39
        parent::__construct(Application::class);
40
41
        $this->parameters['debug'] = $debug;
42
        $this->set('http.router', new DI\Definition(Router::class))->autowire([Router::class, RouteMatcherInterface::class]);
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     *
48
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
49
     */
50
    public function pipe(object ...$middlewares): void
51
    {
52
        foreach ($middlewares as $middleware) {
53
            if ($middleware instanceof DI\Definitions\Reference) {
54
                continue;
55
            }
56
57
            $this->set('http.middleware.' . \spl_object_id($middleware), $middleware)->public(false)->tag('router.middleware');
58
        }
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     *
64
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
65
     */
66
    public function pipes(string $named, object ...$middlewares): void
67
    {
68
        $this->definition('http.router')->bind('pipes', \func_get_args());
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function generateUri(string $routeName, array $parameters = []): DI\Definitions\Statement
75
    {
76
        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...
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function match(string $pattern, array $methods = Route::DEFAULT_METHODS, $to = null)
83
    {
84
        $routes = $this->parameters['routes'] ??= new RouteCollection();
85
86
        return $routes->addRoute($pattern, $methods, $to)->getRoute();
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function post(string $pattern, $to = null)
93
    {
94
        return $this->match($pattern, [RequestMethodInterface::METHOD_POST], $to);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function put(string $pattern, $to = null)
101
    {
102
        return $this->match($pattern, [RequestMethodInterface::METHOD_PUT], $to);
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function delete(string $pattern, $to = null)
109
    {
110
        return $this->match($pattern, [RequestMethodInterface::METHOD_DELETE], $to);
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function options(string $pattern, $to = null)
117
    {
118
        return $this->match($pattern, [RequestMethodInterface::METHOD_OPTIONS], $to);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function patch(string $pattern, $to = null)
125
    {
126
        return $this->match($pattern, [RequestMethodInterface::METHOD_PATCH], $to);
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function group(string $prefix)
133
    {
134
        $routes = $this->parameters['routes'] ??= new RouteCollection();
135
136
        return $routes->group($prefix);
137
    }
138
139
    /**
140
     * Compiled container and build the application.
141
     *
142
     * supported $options config (defaults):
143
     * - cacheDir => composer's vendor dir, The directory where compiled application class will live in.
144
     * - shortArraySyntax => true, Controls whether [] or array() syntax should be used for an array.
145
     * - maxLineLength => 200, Max line of generated code in compiled container class.
146
     * - maxDefinitions => 500, Max definitions reach before splitting into traits.
147
     *
148
     * @param callable            $application write services in here
149
     * @param array<string,mixed> $options
150
     *
151
     * @throws \ReflectionException
152
     */
153
    public static function build(callable $application, array $options = []): Application
154
    {
155
        $debug = $options['debug'] ?? $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? false;
156
        $defFile = 'load_' . ($containerClass = 'App_' . ($debug ? 'Debug' : '') . 'Container') . '.php';
157
        $cacheDir = $options['cacheDir'] ?? \dirname((new \ReflectionClass(\Composer\Autoload\ClassLoader::class))->getFileName(), 2);
158
        $cache = new ConfigCache($cachePath = $cacheDir . '/' . $containerClass . '_' . \PHP_SAPI . '.php', $debug);
159
160
        if (!$cache->isFresh()) {
161
            $appBuilder = new static($debug);
162
163
            if ($debug && \interface_exists(\Tracy\IBarPanel::class)) {
164
                Debug\Tracy\ContainerPanel::$compilationTime = \microtime(true);
165
            }
166
167
            $application($appBuilder);
168
169
            // Autoload require hot paths ...
170
            $requiredPaths = $appBuilder->parameters['project.require_paths'] ?? [];
171
            unset($appBuilder->parameters['project.require_paths']);
172
173
            // Default node visitors.
174
            $appBuilder->addNodeVisitor(new DI\NodeVisitor\MethodsResolver());
175
            $appBuilder->addNodeVisitor(new DI\NodeVisitor\AutowiringResolver());
176
            $appBuilder->addNodeVisitor($splitter = new DI\NodeVisitor\DefinitionsSplitter($options['maxDefinitions'] ?? 500, $defFile));
177
178
            // Write the compiled application class to path.
179
            $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

179
            $cache->write(/** @scrutinizer ignore-type */ $appBuilder->compile($options + \compact('containerClass')), $appBuilder->getResources());
Loading history...
180
181
            require $splitter->buildTraits($cacheDir, $appBuilder->isDebug(), $requiredPaths);
182
        } else {
183
            require_once $cacheDir . '/' . $defFile;
184
        }
185
186
        require $cachePath;
187
188
        return new $containerClass();
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function compile(array $options = [])
195
    {
196
        $this->parameters['project.compiled_container_class'] = $options['containerClass'];
197
        unset($this->definitions['config.builder.loader_resolver']);
198
199
        if (isset($this->parameters['routes'])) {
200
            throw new ServiceCreationException(\sprintf('The %s extension needs to be registered before adding routes.', DI\Extensions\RoutingExtension::class));
201
        }
202
203
        return parent::compile($options);
204
    }
205
}
206