Passed
Push — main ( a387cb...339f6d )
by Cornelia
07:55
created

PhpFileLoader::executeCallback()   F

Complexity

Conditions 21
Paths 649

Size

Total Lines 86
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 86
rs 0.4875
c 0
b 0
f 0
cc 21
nc 649
nop 3

How to fix   Long Method    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
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\DependencyInjection\Loader;
13
14
use Symfony\Component\Config\Builder\ConfigBuilderGenerator;
15
use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface;
16
use Symfony\Component\Config\Builder\ConfigBuilderInterface;
17
use Symfony\Component\Config\FileLocatorInterface;
18
use Symfony\Component\DependencyInjection\Attribute\When;
19
use Symfony\Component\DependencyInjection\Attribute\WhenNot;
20
use Symfony\Component\DependencyInjection\Container;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
23
use Symfony\Component\DependencyInjection\Exception\LogicException;
24
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
25
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
26
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
27
28
/**
29
 * PhpFileLoader loads service definitions from a PHP file.
30
 *
31
 * The PHP file is required and the $container variable can be
32
 * used within the file to change the container.
33
 *
34
 * @author Fabien Potencier <[email protected]>
35
 */
36
class PhpFileLoader extends FileLoader
37
{
38
    protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false;
39
40
    public function __construct(
41
        ContainerBuilder $container,
42
        FileLocatorInterface $locator,
43
        ?string $env = null,
44
        private ?ConfigBuilderGeneratorInterface $generator = null,
45
        bool $prepend = false,
46
    ) {
47
        parent::__construct($container, $locator, $env, $prepend);
48
    }
49
50
    public function load(mixed $resource, ?string $type = null): mixed
51
    {
52
        // the container and loader variables are exposed to the included file below
53
        $container = $this->container;
54
        $loader = $this;
55
56
        $path = $this->locator->locate($resource);
57
        $this->setCurrentDir(\dirname($path));
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type array; however, parameter $path of dirname() 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

57
        $this->setCurrentDir(\dirname(/** @scrutinizer ignore-type */ $path));
Loading history...
58
        $this->container->fileExists($path);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type array; however, parameter $path of Symfony\Component\Depend...erBuilder::fileExists() 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

58
        $this->container->fileExists(/** @scrutinizer ignore-type */ $path);
Loading history...
59
60
        // the closure forbids access to the private scope in the included file
61
        $load = \Closure::bind(function ($path, $env) use ($container, $loader, $resource, $type) {
62
            return include $path;
63
        }, $this, ProtectedPhpFileLoader::class);
64
65
        try {
66
            $callback = $load($path, $this->env);
67
68
            if (\is_object($callback) && \is_callable($callback)) {
69
                $this->executeCallback($callback, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $path);
0 ignored issues
show
Bug introduced by
$callback of type object is incompatible with the type callable expected by parameter $callback of Symfony\Component\Depend...ader::executeCallback(). ( Ignorable by Annotation )

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

69
                $this->executeCallback(/** @scrutinizer ignore-type */ $callback, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $path);
Loading history...
Bug introduced by
It seems like $path can also be of type array; however, parameter $path of Symfony\Component\Depend...ader::executeCallback() 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

69
                $this->executeCallback($callback, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), /** @scrutinizer ignore-type */ $path);
Loading history...
Bug introduced by
It seems like $path can also be of type array; however, parameter $path of Symfony\Component\Depend...igurator::__construct() 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

69
                $this->executeCallback($callback, new ContainerConfigurator($this->container, $this, $this->instanceof, /** @scrutinizer ignore-type */ $path, $resource, $this->env), $path);
Loading history...
70
            }
71
        } finally {
72
            $this->instanceof = [];
73
            $this->registerAliasesForSinglyImplementedInterfaces();
74
        }
75
76
        return null;
77
    }
78
79
    public function supports(mixed $resource, ?string $type = null): bool
80
    {
81
        if (!\is_string($resource)) {
82
            return false;
83
        }
84
85
        if (null === $type && 'php' === pathinfo($resource, \PATHINFO_EXTENSION)) {
86
            return true;
87
        }
88
89
        return 'php' === $type;
90
    }
91
92
    /**
93
     * Resolve the parameters to the $callback and execute it.
94
     */
95
    private function executeCallback(callable $callback, ContainerConfigurator $containerConfigurator, string $path): void
96
    {
97
        $callback = $callback(...);
98
        $arguments = [];
99
        $configBuilders = [];
100
        $r = new \ReflectionFunction($callback);
101
102
        $excluded = true;
103
        $whenAttributes = $r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF);
104
        $notWhenAttributes = $r->getAttributes(WhenNot::class, \ReflectionAttribute::IS_INSTANCEOF);
105
106
        if ($whenAttributes && $notWhenAttributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $notWhenAttributes of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $whenAttributes of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
107
            throw new LogicException('Using both #[When] and #[WhenNot] attributes on the same target is not allowed.');
108
        }
109
110
        if (!$whenAttributes && !$notWhenAttributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $notWhenAttributes of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $whenAttributes of type ReflectionAttribute[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
111
            $excluded = false;
112
        }
113
114
        foreach ($whenAttributes as $attribute) {
115
            if ($this->env === $attribute->newInstance()->env) {
116
                $excluded = false;
117
                break;
118
            }
119
        }
120
121
        foreach ($notWhenAttributes as $attribute) {
122
            if ($excluded = $this->env === $attribute->newInstance()->env) {
123
                break;
124
            }
125
        }
126
127
        if ($excluded) {
128
            return;
129
        }
130
131
        foreach ($r->getParameters() as $parameter) {
132
            $reflectionType = $parameter->getType();
133
            if (!$reflectionType instanceof \ReflectionNamedType) {
134
                throw new \InvalidArgumentException(\sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s" or "%s").', $parameter->getName(), $path, ContainerConfigurator::class, ContainerBuilder::class));
135
            }
136
            $type = $reflectionType->getName();
137
138
            switch ($type) {
139
                case ContainerConfigurator::class:
140
                    $arguments[] = $containerConfigurator;
141
                    break;
142
                case ContainerBuilder::class:
143
                    $arguments[] = $this->container;
144
                    break;
145
                case FileLoader::class:
146
                case self::class:
147
                    $arguments[] = $this;
148
                    break;
149
                case 'string':
150
                    if (null !== $this->env && 'env' === $parameter->getName()) {
151
                        $arguments[] = $this->env;
152
                        break;
153
                    }
154
                    // no break
155
                default:
156
                    try {
157
                        $configBuilder = $this->configBuilder($type);
158
                    } catch (InvalidArgumentException|\LogicException $e) {
159
                        throw new \InvalidArgumentException(\sprintf('Could not resolve argument "%s" for "%s".', $type.' $'.$parameter->getName(), $path), 0, $e);
160
                    }
161
                    $configBuilders[] = $configBuilder;
162
                    $arguments[] = $configBuilder;
163
            }
164
        }
165
166
        // Force load ContainerConfigurator to make env(), param() etc available.
167
        class_exists(ContainerConfigurator::class);
168
169
        ++$this->importing;
170
        try {
171
            $callback(...$arguments);
172
        } finally {
173
            --$this->importing;
174
        }
175
176
        foreach ($configBuilders as $configBuilder) {
177
            $this->loadExtensionConfig($configBuilder->getExtensionAlias(), ContainerConfigurator::processValue($configBuilder->toArray()));
178
        }
179
180
        $this->loadExtensionConfigs();
181
    }
182
183
    /**
184
     * @param string $namespace FQCN string for a class implementing ConfigBuilderInterface
185
     */
186
    private function configBuilder(string $namespace): ConfigBuilderInterface
187
    {
188
        if (!class_exists(ConfigBuilderGenerator::class)) {
189
            throw new \LogicException('You cannot use the config builder as the Config component is not installed. Try running "composer require symfony/config".');
190
        }
191
192
        if (null === $this->generator) {
193
            throw new \LogicException('You cannot use the ConfigBuilders without providing a class implementing ConfigBuilderGeneratorInterface.');
194
        }
195
196
        // If class exists and implements ConfigBuilderInterface
197
        if (class_exists($namespace) && is_subclass_of($namespace, ConfigBuilderInterface::class)) {
198
            return new $namespace();
199
        }
200
201
        // If it does not start with Symfony\Config\ we don't know how to handle this
202
        if (!str_starts_with($namespace, 'Symfony\\Config\\')) {
203
            throw new InvalidArgumentException(\sprintf('Could not find or generate class "%s".', $namespace));
204
        }
205
206
        // Try to get the extension alias
207
        $alias = Container::underscore(substr($namespace, 15, -6));
208
209
        if (str_contains($alias, '\\')) {
210
            throw new InvalidArgumentException('You can only use "root" ConfigBuilders from "Symfony\\Config\\" namespace. Nested classes like "Symfony\\Config\\Framework\\CacheConfig" cannot be used.');
211
        }
212
213
        if (!$this->container->hasExtension($alias)) {
214
            $extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions()));
215
            throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($namespace, null, $alias, $extensions));
216
        }
217
218
        $extension = $this->container->getExtension($alias);
219
        if (!$extension instanceof ConfigurationExtensionInterface) {
220
            throw new \LogicException(\sprintf('You cannot use the config builder for "%s" because the extension does not implement "%s".', $namespace, ConfigurationExtensionInterface::class));
221
        }
222
223
        $configuration = $extension->getConfiguration([], $this->container);
224
        $loader = $this->generator->build($configuration);
0 ignored issues
show
Bug introduced by
It seems like $configuration can also be of type null; however, parameter $configuration of Symfony\Component\Config...ratorInterface::build() does only seem to accept Symfony\Component\Config...\ConfigurationInterface, 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

224
        $loader = $this->generator->build(/** @scrutinizer ignore-type */ $configuration);
Loading history...
225
226
        return $loader();
227
    }
228
}
229
230
/**
231
 * @internal
232
 */
233
final class ProtectedPhpFileLoader extends PhpFileLoader
234
{
235
}
236