|
| 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 | +namespaceSymfony\Component\Routing\DependencyInjection; |
| 13 | + |
| 14 | +useSymfony\Component\DependencyInjection\Argument\ServiceClosureArgument; |
| 15 | +useSymfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; |
| 16 | +useSymfony\Component\DependencyInjection\ContainerBuilder; |
| 17 | +useSymfony\Component\DependencyInjection\ContainerInterface; |
| 18 | +useSymfony\Component\DependencyInjection\Compiler\CompilerPassInterface; |
| 19 | +useSymfony\Component\DependencyInjection\Exception\InvalidArgumentException; |
| 20 | +useSymfony\Component\DependencyInjection\LazyProxy\InheritanceProxyHelper; |
| 21 | +useSymfony\Component\DependencyInjection\Reference; |
| 22 | +useSymfony\Component\DependencyInjection\ServiceLocator; |
| 23 | +useSymfony\Component\DependencyInjection\TypedReference; |
| 24 | + |
| 25 | +/** |
| 26 | + * Creates the service-locators required by ServiceArgumentValueResolver. |
| 27 | + * |
| 28 | + * @author Nicolas Grekas <p@tchwork.com> |
| 29 | + * |
| 30 | + * @experimental in version 3.3 |
| 31 | + */ |
| 32 | +class RoutingControllerPassimplements CompilerPassInterface |
| 33 | +{ |
| 34 | +private$resolverServiceId; |
| 35 | +private$controllerTag; |
| 36 | + |
| 37 | +publicfunction__construct($resolverServiceId ='argument_resolver.service',$controllerTag ='routing.controller') |
| 38 | + { |
| 39 | +$this->resolverServiceId =$resolverServiceId; |
| 40 | +$this->controllerTag =$controllerTag; |
| 41 | + } |
| 42 | + |
| 43 | +publicfunctionprocess(ContainerBuilder$container) |
| 44 | + { |
| 45 | +if (false ===$container->hasDefinition($this->resolverServiceId)) { |
| 46 | +return; |
| 47 | + } |
| 48 | + |
| 49 | +$serviceResolver =$container->getDefinition($this->resolverServiceId); |
| 50 | +$parameterBag =$container->getParameterBag(); |
| 51 | +$controllers =array(); |
| 52 | + |
| 53 | +foreach ($container->findTaggedServiceIds($this->controllerTag)as$id =>$tags) { |
| 54 | +$def =$container->getDefinition($id); |
| 55 | +$class =$def->getClass(); |
| 56 | +$isAutowired =$def->isAutowired(); |
| 57 | + |
| 58 | +if ($def->isAbstract()) { |
| 59 | +continue; |
| 60 | + } |
| 61 | + |
| 62 | +while (!$class &&$definstanceof ChildDefinition) { |
| 63 | +$def =$container->findDefinition($def->getParent()); |
| 64 | +$class =$def->getClass(); |
| 65 | + } |
| 66 | +$class =$parameterBag->resolveValue($class); |
| 67 | + |
| 68 | +if (!$r =$container->getReflectionClass($class)) { |
| 69 | +thrownewInvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.',$class,$id)); |
| 70 | + } |
| 71 | + |
| 72 | +$methods =array(); |
| 73 | +foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC)as$r) { |
| 74 | +$methods[strtolower($r->name)] =$r; |
| 75 | + } |
| 76 | + |
| 77 | +$actions =array(); |
| 78 | +$arguments =array(); |
| 79 | +foreach ($tagsas$attributes) { |
| 80 | +if (!isset($attributes['action'])) { |
| 81 | +thrownewInvalidArgumentException(sprintf('Service "%s" must define the "action" attribute on "routing.controller" tags.',$id)); |
| 82 | + } |
| 83 | +ksort($attributes); |
| 84 | +if (1 <count($attributes) &&array('action','argument','service') !==$r =array_keys(array_filter($attributes))) { |
| 85 | +thrownewInvalidArgumentException(sprintf('A "routing.controller" tag must have either one "action" or exactly three non-empty "action", "argument" and "service" attributes, "%s" given for service "%s".',implode('", "',$r),$id)); |
| 86 | + } |
| 87 | +$action =strtolower($attributes['action']); |
| 88 | + |
| 89 | +if (false !==strpos($action,'*')) { |
| 90 | +$regex ='/^'.str_replace('\*','.*',preg_quote($action,'/')).'$/'; |
| 91 | +$found =false; |
| 92 | + |
| 93 | +foreach ($methodsas$name =>$r) { |
| 94 | +if (preg_match($regex,$name)) { |
| 95 | +$actions[$name =$methods[$name]->name] =$r; |
| 96 | +$found =true; |
| 97 | + |
| 98 | +if (isset($attributes['argument']) && !isset($arguments[$name][$attributes['argument']])) { |
| 99 | +$arguments[$name][$attributes['argument']] =$attributes['service']; |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | +if (!$found) { |
| 105 | +$container->log($this,sprintf('No "action" found for service "%s": class "%s" has no public "%s()" methods.',$id,$class,$attributes['action'])); |
| 106 | + } |
| 107 | + }elseif (isset($methods[$action])) { |
| 108 | +$actions[$name =$methods[$action]->name] =$methods[$action]; |
| 109 | + |
| 110 | +if (isset($attributes['argument']) && !isset($arguments[$name][$attributes['argument']])) { |
| 111 | +$arguments[$name][$attributes['argument']] =$attributes['service']; |
| 112 | +$found =false; |
| 113 | + |
| 114 | +foreach ($methods[$action]->getParameters()as$r) { |
| 115 | +if ($attributes['argument'] ===$r->name) { |
| 116 | +$found =true; |
| 117 | +break; |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | +if (!$found) { |
| 122 | +thrownewInvalidArgumentException(sprintf('Invalid "routing.controller" tag for service "%s": method "%s()" has no "%s" argument on class "%s".',$id,$name,$attributes['argument'],$class)); |
| 123 | + } |
| 124 | + } |
| 125 | + }else { |
| 126 | +thrownewInvalidArgumentException(sprintf('Invalid "action" for service "%s": no public "%s()" method found on class "%s".',$id,$attributes['action'],$class)); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | +if (!$actions) { |
| 131 | +continue; |
| 132 | + } |
| 133 | + |
| 134 | +foreach ($actionsas$name =>$r) { |
| 135 | +$args =array(); |
| 136 | +foreach ($r->getParameters()as$p) { |
| 137 | +$type =$target = InheritanceProxyHelper::getTypeHint($r,$p,true); |
| 138 | +$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; |
| 139 | + |
| 140 | +if (isset($arguments[$name][$p->name])) { |
| 141 | +$target =$arguments[$name][$p->name]; |
| 142 | +if ('?' !==$target[0]) { |
| 143 | +$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; |
| 144 | + }elseif ('' ===$target = (string)substr($target,1)) { |
| 145 | +thrownewInvalidArgumentException(sprintf('A "routing.controller" tag must have non-empty "service" attributes for service "%s".',$id)); |
| 146 | + } |
| 147 | + }elseif (!$type) { |
| 148 | +continue; |
| 149 | + } |
| 150 | + |
| 151 | +$args[$p->name] =newServiceClosureArgument($type ?newTypedReference($target,$type,$invalidBehavior,false) :newReference($target,$invalidBehavior)); |
| 152 | + } |
| 153 | +if ($args) { |
| 154 | +$argsId =sprintf('arguments.%s:%s',$id,$name); |
| 155 | +$container->register($argsId, ServiceLocator::class)->addArgument($args)->setPublic(false)->setAutowired($isAutowired); |
| 156 | +$controllers[$id.':'.$name] =newReference($argsId); |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | +$serviceResolver->replaceArgument(0,newServiceLocatorArgument($controllers)); |
| 162 | + } |
| 163 | +} |