Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
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\HttpKernel\DependencyInjection;
13
 
14
use Symfony\Component\DependencyInjection\Attribute\Autowire;
15
use Symfony\Component\DependencyInjection\Attribute\Target;
16
use Symfony\Component\DependencyInjection\ChildDefinition;
17
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
18
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
19
use Symfony\Component\DependencyInjection\ContainerBuilder;
20
use Symfony\Component\DependencyInjection\ContainerInterface;
21
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
22
use Symfony\Component\DependencyInjection\Reference;
23
use Symfony\Component\DependencyInjection\TypedReference;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
use Symfony\Component\HttpFoundation\Session\SessionInterface;
27
use Symfony\Component\VarExporter\ProxyHelper;
28
 
29
/**
30
 * Creates the service-locators required by ServiceValueResolver.
31
 *
32
 * @author Nicolas Grekas <p@tchwork.com>
33
 */
34
class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
35
{
36
    public function process(ContainerBuilder $container)
37
    {
38
        if (!$container->hasDefinition('argument_resolver.service') && !$container->hasDefinition('argument_resolver.not_tagged_controller')) {
39
            return;
40
        }
41
 
42
        $parameterBag = $container->getParameterBag();
43
        $controllers = [];
44
 
45
        $publicAliases = [];
46
        foreach ($container->getAliases() as $id => $alias) {
47
            if ($alias->isPublic() && !$alias->isPrivate()) {
48
                $publicAliases[(string) $alias][] = $id;
49
            }
50
        }
51
 
52
        $emptyAutowireAttributes = class_exists(Autowire::class) ? null : [];
53
 
54
        foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) {
55
            $def = $container->getDefinition($id);
56
            $def->setPublic(true);
57
            $class = $def->getClass();
58
            $autowire = $def->isAutowired();
59
            $bindings = $def->getBindings();
60
 
61
            // resolve service class, taking parent definitions into account
62
            while ($def instanceof ChildDefinition) {
63
                $def = $container->findDefinition($def->getParent());
64
                $class = $class ?: $def->getClass();
65
                $bindings += $def->getBindings();
66
            }
67
            $class = $parameterBag->resolveValue($class);
68
 
69
            if (!$r = $container->getReflectionClass($class)) {
70
                throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
71
            }
72
 
73
            // get regular public methods
74
            $methods = [];
75
            $arguments = [];
76
            foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) {
77
                if ('setContainer' === $r->name) {
78
                    continue;
79
                }
80
                if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) {
81
                    $methods[strtolower($r->name)] = [$r, $r->getParameters()];
82
                }
83
            }
84
 
85
            // validate and collect explicit per-actions and per-arguments service references
86
            foreach ($tags as $attributes) {
87
                if (!isset($attributes['action']) && !isset($attributes['argument']) && !isset($attributes['id'])) {
88
                    $autowire = true;
89
                    continue;
90
                }
91
                foreach (['action', 'argument', 'id'] as $k) {
92
                    if (!isset($attributes[$k][0])) {
93
                        throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "controller.service_arguments" %s for service "%s".', $k, json_encode($attributes, \JSON_UNESCAPED_UNICODE), $id));
94
                    }
95
                }
96
                if (!isset($methods[$action = strtolower($attributes['action'])])) {
97
                    throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "controller.service_arguments" for service "%s": no public "%s()" method found on class "%s".', $id, $attributes['action'], $class));
98
                }
99
                [$r, $parameters] = $methods[$action];
100
                $found = false;
101
 
102
                foreach ($parameters as $p) {
103
                    if ($attributes['argument'] === $p->name) {
104
                        if (!isset($arguments[$r->name][$p->name])) {
105
                            $arguments[$r->name][$p->name] = $attributes['id'];
106
                        }
107
                        $found = true;
108
                        break;
109
                    }
110
                }
111
 
112
                if (!$found) {
113
                    throw new InvalidArgumentException(sprintf('Invalid "controller.service_arguments" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $id, $r->name, $attributes['argument'], $class));
114
                }
115
            }
116
 
117
            foreach ($methods as [$r, $parameters]) {
118
                /** @var \ReflectionMethod $r */
119
 
120
                // create a per-method map of argument-names to service/type-references
121
                $args = [];
122
                foreach ($parameters as $p) {
123
                    /** @var \ReflectionParameter $p */
124
                    $type = preg_replace('/(^|[(|&])\\\\/', '\1', $target = ltrim(ProxyHelper::exportType($p) ?? '', '?'));
125
                    $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
126
                    $autowireAttributes = $autowire ? $emptyAutowireAttributes : [];
127
 
128
                    if (isset($arguments[$r->name][$p->name])) {
129
                        $target = $arguments[$r->name][$p->name];
130
                        if ('?' !== $target[0]) {
131
                            $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE;
132
                        } elseif ('' === $target = (string) substr($target, 1)) {
133
                            throw new InvalidArgumentException(sprintf('A "controller.service_arguments" tag must have non-empty "id" attributes for service "%s".', $id));
134
                        } elseif ($p->allowsNull() && !$p->isOptional()) {
135
                            $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
136
                        }
137
                    } elseif (isset($bindings[$bindingName = $type.' $'.$name = Target::parseName($p)]) || isset($bindings[$bindingName = '$'.$name]) || isset($bindings[$bindingName = $type])) {
138
                        $binding = $bindings[$bindingName];
139
 
140
                        [$bindingValue, $bindingId, , $bindingType, $bindingFile] = $binding->getValues();
141
                        $binding->setValues([$bindingValue, $bindingId, true, $bindingType, $bindingFile]);
142
 
143
                        $args[$p->name] = $bindingValue;
144
 
145
                        continue;
146
                    } elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class)) && (!$type || '\\' !== $target[0]))) {
147
                        continue;
148
                    } elseif (is_subclass_of($type, \UnitEnum::class)) {
149
                        // do not attempt to register enum typed arguments if not already present in bindings
150
                        continue;
151
                    } elseif (!$p->allowsNull()) {
152
                        $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE;
153
                    }
154
 
155
                    if (Request::class === $type || SessionInterface::class === $type || Response::class === $type) {
156
                        continue;
157
                    }
158
 
159
                    if ($autowireAttributes) {
160
                        $value = $autowireAttributes[0]->newInstance()->value;
161
 
162
                        if ($value instanceof Reference) {
163
                            $args[$p->name] = $type ? new TypedReference($value, $type, $invalidBehavior, $p->name) : new Reference($value, $invalidBehavior);
164
                        } else {
165
                            $args[$p->name] = new Reference('.value.'.$container->hash($value));
166
                            $container->register((string) $args[$p->name], 'mixed')
167
                                ->setFactory('current')
168
                                ->addArgument([$value]);
169
                        }
170
 
171
                        continue;
172
                    }
173
 
174
                    if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) {
175
                        $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type);
176
 
177
                        // see if the type-hint lives in the same namespace as the controller
178
                        if (0 === strncmp($type, $class, strrpos($class, '\\'))) {
179
                            $message .= ' Did you forget to add a use statement?';
180
                        }
181
 
182
                        $container->register($erroredId = '.errored.'.$container->hash($message), $type)
183
                            ->addError($message);
184
 
185
                        $args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE);
186
                    } else {
187
                        $target = preg_replace('/(^|[(|&])\\\\/', '\1', $target);
188
                        $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior);
189
                    }
190
                }
191
                // register the maps as a per-method service-locators
192
                if ($args) {
193
                    $controllers[$id.'::'.$r->name] = ServiceLocatorTagPass::register($container, $args);
194
 
195
                    foreach ($publicAliases[$id] ?? [] as $alias) {
196
                        $controllers[$alias.'::'.$r->name] = clone $controllers[$id.'::'.$r->name];
197
                    }
198
                }
199
            }
200
        }
201
 
202
        $controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers);
203
 
204
        if ($container->hasDefinition('argument_resolver.service')) {
205
            $container->getDefinition('argument_resolver.service')
206
                ->replaceArgument(0, $controllerLocatorRef);
207
        }
208
 
209
        if ($container->hasDefinition('argument_resolver.not_tagged_controller')) {
210
            $container->getDefinition('argument_resolver.not_tagged_controller')
211
                ->replaceArgument(0, $controllerLocatorRef);
212
        }
213
 
214
        $container->setAlias('argument_resolver.controller_locator', (string) $controllerLocatorRef);
215
    }
216
}