├── .github └── FUNDING.yml ├── LICENSE ├── composer.json ├── src └── DependencyInjection │ └── CompilerPass │ └── AutoBindParameterCompilerPass.php └── tests ├── DependencyInjection └── CompilerPass │ └── AutoBindParameterCompilerPassTest.php ├── HttpKernel └── AutoBindParameterHttpKernel.php ├── Source └── SomeServiceWithParameter.php └── config └── auto_bind_parameter.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: tomasvotruba 3 | custom: https://www.paypal.me/rectorphp 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | --------------- 3 | 4 | Copyright (c) 2018 Tomas Votruba (http://tomasvotruba.com) 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symplify/auto-bind-parameter", 3 | "description": "Auto bind parameters for your Symfony applications", 4 | "license": "MIT", 5 | "require": { 6 | "php": ">=7.2", 7 | "nette/utils": "^3.0", 8 | "symfony/http-kernel": "^4.4|^5.1", 9 | "symfony/dependency-injection": "^4.4|^5.1" 10 | }, 11 | "require-dev": { 12 | "phpunit/phpunit": "^8.5|^9.0", 13 | "symplify/package-builder": "^8.4" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Symplify\\AutoBindParameter\\": "src" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Symplify\\AutoBindParameter\\Tests\\": "tests" 23 | } 24 | }, 25 | "extra": { 26 | "branch-alias": { 27 | "dev-master": "8.4-dev" 28 | } 29 | }, 30 | "minimum-stability": "dev", 31 | "prefer-stable": true 32 | } 33 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/AutoBindParameterCompilerPass.php: -------------------------------------------------------------------------------- 1 | createBoundArgumentsFromParameterBag($containerBuilder->getParameterBag()); 29 | 30 | foreach ($containerBuilder->getDefinitions() as $definition) { 31 | if ($this->shouldSkipDefinition($definition)) { 32 | continue; 33 | } 34 | 35 | $reflectionClass = $containerBuilder->getReflectionClass($definition->getClass()); 36 | if ($reflectionClass === null) { 37 | continue; 38 | } 39 | 40 | $constructorReflection = $reflectionClass->getConstructor(); 41 | if ($constructorReflection === null) { 42 | continue; 43 | } 44 | 45 | // exclude non-scalar parameters 46 | $parameterNamesToExclude = $this->resolveMethodReflectionNonScalarArgumentNames($constructorReflection); 47 | $parameterNamesToExclude = array_flip($parameterNamesToExclude); 48 | $bindings = array_diff_key($boundArguments, $parameterNamesToExclude); 49 | 50 | // config binding has priority over default one 51 | $bindings = array_merge($definition->getBindings(), $bindings); 52 | 53 | $definition->setBindings($bindings); 54 | } 55 | } 56 | 57 | /** 58 | * @return BoundArgument[] 59 | */ 60 | private function createBoundArgumentsFromParameterBag(ParameterBagInterface $parameterBag): array 61 | { 62 | $boundArguments = []; 63 | foreach ($parameterBag->all() as $name => $value) { 64 | // not ready to autowire 65 | if (! is_string($name)) { 66 | continue; 67 | } 68 | 69 | if (Strings::contains($name, '.') || Strings::contains($name, 'env(')) { 70 | continue; 71 | } 72 | 73 | $boundArgument = new BoundArgument($value); 74 | 75 | // set used so it doesn't end on exceptions 76 | [$value, $identifier] = $boundArgument->getValues(); 77 | $boundArgument->setValues([$value, $identifier, true]); 78 | 79 | $parameterGuess = '$' . $this->undescoredToCamelCase($name); 80 | $boundArguments[$parameterGuess] = $boundArgument; 81 | } 82 | 83 | return $boundArguments; 84 | } 85 | 86 | private function shouldSkipDefinition(Definition $definition): bool 87 | { 88 | if ($definition->isAbstract()) { 89 | return true; 90 | } 91 | 92 | if ($definition instanceof ChildDefinition && $definition->getClass() === null) { 93 | return true; 94 | } 95 | return $definition->getClass() === null && $definition->getFactory() === null; 96 | } 97 | 98 | /** 99 | * @return string[] 100 | */ 101 | private function resolveMethodReflectionNonScalarArgumentNames(ReflectionMethod $reflectionMethod): array 102 | { 103 | $argumentNames = []; 104 | foreach ($reflectionMethod->getParameters() as $reflectionParameter) { 105 | $typeName = $this->getReflectionParameterTypeString($reflectionParameter); 106 | 107 | // probably not scalar type 108 | if (isset($typeName[0]) && (Strings::contains($typeName, '\\') || ctype_upper($typeName[0]))) { 109 | // '$' to be consistent with bind parameter naming 110 | $argumentNames[] = '$' . $reflectionParameter->name; 111 | } 112 | } 113 | 114 | return $argumentNames; 115 | } 116 | 117 | /** 118 | * @see https://stackoverflow.com/a/2792045/1348344 119 | */ 120 | private function undescoredToCamelCase(string $string): string 121 | { 122 | $string = str_replace('_', '', ucwords($string, '_')); 123 | 124 | return lcfirst($string); 125 | } 126 | 127 | private function getReflectionParameterTypeString(ReflectionParameter $reflectionParameter): string 128 | { 129 | $returnType = $reflectionParameter->getType(); 130 | if ($returnType !== null) { 131 | // various PHP version compatible 132 | $bareType = method_exists($returnType, 'getName') ? $returnType->getName() : (string) $returnType; 133 | return ($returnType->allowsNull() ? '?' : '') . $bareType; 134 | } 135 | 136 | return ''; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/DependencyInjection/CompilerPass/AutoBindParameterCompilerPassTest.php: -------------------------------------------------------------------------------- 1 | bootKernel(AutoBindParameterHttpKernel::class); 16 | 17 | /** @var SomeServiceWithParameter $someServiceWithParameter */ 18 | $someServiceWithParameter = self::$container->get(SomeServiceWithParameter::class); 19 | 20 | $this->assertSame('Johny', $someServiceWithParameter->getSuperName()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/HttpKernel/AutoBindParameterHttpKernel.php: -------------------------------------------------------------------------------- 1 | load(__DIR__ . '/../config/auto_bind_parameter.yaml'); 18 | } 19 | 20 | public function getCacheDir(): string 21 | { 22 | return sys_get_temp_dir() . '/auto_bind_parameter_test'; 23 | } 24 | 25 | public function getLogDir(): string 26 | { 27 | return sys_get_temp_dir() . '/auto_bind_parameter_test_log'; 28 | } 29 | 30 | /** 31 | * @return BundleInterface[] 32 | */ 33 | public function registerBundles(): iterable 34 | { 35 | return []; 36 | } 37 | 38 | protected function build(ContainerBuilder $containerBuilder): void 39 | { 40 | $containerBuilder->addCompilerPass(new AutoBindParameterCompilerPass()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Source/SomeServiceWithParameter.php: -------------------------------------------------------------------------------- 1 | superName = $superName; 17 | } 18 | 19 | public function getSuperName(): string 20 | { 21 | return $this->superName; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/config/auto_bind_parameter.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | super_name: 'Johny' 3 | 4 | services: 5 | _defaults: 6 | public: true 7 | autowire: true 8 | 9 | Symplify\AutoBindParameter\Tests\Source\: 10 | resource: "../Source" 11 | --------------------------------------------------------------------------------