Passed
Push — master ( 89de4a...2786a5 )
by Divine Niiquaye
03:21
created

AppBuilder::build()   B

Complexity

Conditions 10
Paths 26

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 10
eloc 28
c 5
b 0
f 0
nc 26
nop 2
dl 0
loc 44
ccs 0
cts 29
cp 0
crap 110
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        $containerClass = 'App_' . (($debug = $options['debug'] ?? false) ? 'Debug' : '') . 'Container';
157
        $a = 'load_' . $containerClass . ($hashFile = '_' . \PHP_SAPI . \PHP_OS . '.php');
158
        $b = $options['cacheDir'] ?? \dirname((new \ReflectionClass(\Composer\Autoload\ClassLoader::class))->getFileName(), 2);
159
        $errorLevel = \error_reporting(\E_ALL ^ \E_WARNING); //ignore "include" failures - don't use "@" to prevent silencing fatal errors
160
161
        try {
162
            if (
163
                \is_object($c = include $b . '/' . $a) &&
164
                (!$debug || ($cache = new ConfigCache($b . '/' . $containerClass . $hashFile, $debug))->isFresh())
165
            ) {
166
                \error_reporting($errorLevel);
167
168
                return $c;
169
            }
170
        } catch (\Throwable $e) {
171
            $c = null;
172
        }
173
        \error_reporting($errorLevel); // restore error reporting
174
175
        if ($debug && \interface_exists(\Tracy\IBarPanel::class)) {
176
            Debug\Tracy\ContainerPanel::$compilationTime = \microtime(true);
177
        }
178
179
        $application($container = new static($debug));
180
        $requiredPaths = $container->parameters['project.require_paths'] ?? []; // Autoload require hot paths ...
181
182
        $container->addNodeVisitor(new DI\NodeVisitor\MethodsResolver());
183
        $container->addNodeVisitor(new DI\NodeVisitor\AutowiringResolver());
184
        $container->addNodeVisitor($splitter = new DI\NodeVisitor\DefinitionsSplitter($options['maxDefinitions'] ?? 500, $a));
185
186
        if (!isset($cache)) {
187
            $cache = new ConfigCache($b . '/' . $containerClass . $hashFile, $debug); // ... or create a new cache
188
        }
189
190
        $cache->write($container->compile($options + \compact('containerClass')), $container->getResources());
191
        \file_put_contents(
192
            $initialize = $splitter->buildTraits($b, $debug, $requiredPaths),
193
            \sprintf("require '%s';\n\nreturn new \%s();\n", $cache->getPath(), $containerClass),
194
            \FILE_APPEND | \LOCK_EX
195
        );
196
197
        return $c ?: require $initialize;
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function compile(array $options = [])
204
    {
205
        $this->parameters['project.compiled_container_class'] = $options['containerClass'];
206
        unset($this->definitions['config.builder.loader_resolver'], $this->parameters['project.require_paths']);
207
208
        if (isset($this->parameters['routes'])) {
209
            throw new ServiceCreationException(\sprintf('The %s extension needs to be registered before adding routes.', DI\Extensions\RoutingExtension::class));
210
        }
211
212
        return parent::compile($options);
213
    }
214
}
215