Issues (36)

src/AppBuilder.php (2 issues)

1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Biurad opensource projects.
5
 *
6
 * @copyright 2019 Biurad Group (https://biurad.com/)
7
 * @license   https://opensource.org/licenses/BSD-3-Clause License
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Flange;
14
15
use Fig\Http\Message\RequestMethodInterface;
16
use Flight\Routing\Interfaces\RouteMatcherInterface;
17
use Flight\Routing\Interfaces\UrlGeneratorInterface;
18
use Flight\Routing\RouteCollection;
19
use Flight\Routing\Router;
20
use Rade\DI;
21
use Rade\DI\Exceptions\ServiceCreationException;
22
use Symfony\Component\Config\ConfigCache;
23
24
/**
25
 * Create a cacheable application.
26
 *
27
 * @author Divine Niiquaye Ibok <[email protected]>
28
 */
29
class AppBuilder extends DI\ContainerBuilder implements RouterInterface, KernelInterface
30
{
31
    use Traits\HelperTrait;
32
33
    public function __construct(bool $debug = true)
34
    {
35
        parent::__construct(Application::class);
36
37
        $this->parameters['debug'] = $debug;
38
        $this->set('http.router', new DI\Definition(Router::class))->typed(Router::class, RouteMatcherInterface::class, UrlGeneratorInterface::class);
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     *
44
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
45
     */
46
    public function pipe(object ...$middlewares): void
47
    {
48
        foreach ($middlewares as $middleware) {
49
            if ($middleware instanceof DI\Definitions\Reference) {
50
                continue;
51
            }
52
53
            $this->set('http.middleware.'.\spl_object_id($middleware), $middleware)->public(false)->tag('router.middleware');
54
        }
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     *
60
     * @param DI\Definitions\Reference|DI\Definitions\Statement|DI\Definition ...$middlewares
61
     */
62
    public function pipes(string $named, object ...$middlewares): void
63
    {
64
        $this->definition('http.router')->bind('pipes', \func_get_args());
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function generateUri(string $routeName, array $parameters = []): DI\Definitions\Statement
71
    {
72
        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 Flange\RouterInterface::generateUri() of Flight\Routing\Generator...Flight\Routing\RouteUri.

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...
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function match(string $pattern, array $methods = ['GET'], mixed $to = null)
79
    {
80
        return ($this->parameters['routes'] ??= new RouteCollection())->add($pattern, $methods, $to);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function post(string $pattern, mixed $to = null)
87
    {
88
        return $this->match($pattern, [RequestMethodInterface::METHOD_POST], $to);
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function put(string $pattern, mixed $to = null)
95
    {
96
        return $this->match($pattern, [RequestMethodInterface::METHOD_PUT], $to);
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function delete(string $pattern, mixed $to = null)
103
    {
104
        return $this->match($pattern, [RequestMethodInterface::METHOD_DELETE], $to);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function options(string $pattern, mixed $to = null)
111
    {
112
        return $this->match($pattern, [RequestMethodInterface::METHOD_OPTIONS], $to);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function patch(string $pattern, mixed $to = null)
119
    {
120
        return $this->match($pattern, [RequestMethodInterface::METHOD_PATCH], $to);
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function group(string $prefix, callable|RouteCollection|null $collection = null): RouteCollection
127
    {
128
        if (\is_callable($collection)) {
129
            throw new ServiceCreationException('Route grouping using callable is supported since application is compilable.');
130
        }
131
132
        return ($this->parameters['routes'] ??= new RouteCollection())->group($prefix, $collection);
133
    }
134
135
    /**
136
     * Compiled container and build the application.
137
     *
138
     * supported $options config (defaults):
139
     * - cacheDir => composer's vendor dir, The directory where compiled application class will live in.
140
     * - shortArraySyntax => true, Controls whether [] or array() syntax should be used for an array.
141
     * - maxLineLength => 200, Max line of generated code in compiled container class.
142
     * - maxDefinitions => 500, Max definitions reach before splitting into traits.
143
     *
144
     * @param callable            $application write services in here
145
     * @param array<string,mixed> $options
146
     *
147
     * @throws \ReflectionException
148
     */
149
    public static function build(callable $application, array $options = []): Application
150
    {
151
        $a = $options['cacheDir'] ?? \dirname((new \ReflectionClass(\Composer\Autoload\ClassLoader::class))->getFileName(), 2);
152
        $b = 'App_'.\substr(\md5($a), 0, 10).'.php';
153
154
        if (!($cache = new ConfigCache($a.'/'.$b, $debug = $options['debug'] ?? false))->isFresh()) {
155
            re_cache:
156
            if ($debug && \interface_exists(\Tracy\IBarPanel::class)) {
157
                Debug\Tracy\ContainerPanel::$compilationTime = \microtime(true);
158
            }
159
160
            $application($container = new static($debug));
161
            $requiredPaths = $container->parameters['project.require_paths'] ?? []; // Autoload require hot paths ...
162
            $containerClass = 'App_'.($debug ? 'Debug' : '').'Container';
163
164
            $container->addNodeVisitor(new DI\NodeVisitor\MethodsResolver());
165
            $container->addNodeVisitor(new DI\NodeVisitor\AutowiringResolver());
166
            $container->addNodeVisitor($splitter = new DI\NodeVisitor\DefinitionsSplitter($options['maxDefinitions'] ?? 500, $b));
167
168
            $cache->write('', $container->getResources());
169
            \file_put_contents($a.'/'.$containerClass.'_'.\PHP_SAPI.\PHP_OS.'.php', $container->compile($options + \compact('containerClass')));
170
            \file_put_contents(
171
                $splitter->buildTraits($a, $requiredPaths),
172
                \sprintf("\nrequire '%s/%s_'.\PHP_SAPI.\PHP_OS.'.php';\n\nreturn new \%2\$s();\n", $a, $containerClass),
173
                \FILE_APPEND | \LOCK_EX
174
            );
175
        }
176
177
        try {
178
            $container = include $cache->getPath();
179
180
            if (!$container instanceof Application) {
181
                goto re_cache;
182
            }
183
184
            return $container;
185
        } catch (\Error) {
186
            goto re_cache;
187
        }
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193
    public function compile(array $options = [])
194
    {
195
        unset($this->parameters['config.builder.loader_resolver'], $this->parameters['project.require_paths']);
196
197
        if (isset($this->parameters['routes'])) {
198
            throw new ServiceCreationException(\sprintf('The %s extension needs to be registered before adding routes.', DI\Extensions\RoutingExtension::class));
0 ignored issues
show
The type Rade\DI\Extensions\RoutingExtension was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
199
        }
200
201
        return parent::compile($options);
202
    }
203
}
204