Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit df891df

Browse files
committed
[DependencyInjection] Handle env var placeholders in CheckTypeDeclarationsPass
1 parent bfe697b commit df891df

File tree

2 files changed

+128
-25
lines changed

2 files changed

+128
-25
lines changed

src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1515
use Symfony\Component\DependencyInjection\Container;
1616
use Symfony\Component\DependencyInjection\Definition;
17+
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
1718
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1819
use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException;
1920
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2021
use Symfony\Component\DependencyInjection\ExpressionLanguage;
2122
use Symfony\Component\DependencyInjection\Parameter;
23+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
2224
use Symfony\Component\DependencyInjection\Reference;
2325
use Symfony\Component\DependencyInjection\ServiceLocator;
2426
use Symfony\Component\ExpressionLanguage\Expression;
@@ -104,27 +106,29 @@ private function checkTypeDeclarations(Definition $checkedDefinition, \Reflectio
104106
$reflectionParameters = $reflectionFunction->getParameters();
105107
$checksCount = min($reflectionFunction->getNumberOfParameters(), \count($values));
106108

109+
$envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null;
110+
107111
for ($i = 0; $i < $checksCount; ++$i) {
108112
if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) {
109113
continue;
110114
}
111115

112-
$this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i]);
116+
$this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i], $envPlaceholderUniquePrefix);
113117
}
114118

115119
if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) {
116120
$variadicParameters = \array_slice($values, $lastParameter->getPosition());
117121

118122
foreach ($variadicParameters as $variadicParameter) {
119-
$this->checkType($checkedDefinition, $variadicParameter, $lastParameter);
123+
$this->checkType($checkedDefinition, $variadicParameter, $lastParameter, $envPlaceholderUniquePrefix);
120124
}
121125
}
122126
}
123127

124128
/**
125129
* @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
126130
*/
127-
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter): void
131+
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix): void
128132
{
129133
$type = $parameter->getType()->getName();
130134

@@ -174,12 +178,24 @@ private function checkType(Definition $checkedDefinition, $value, \ReflectionPar
174178
throw new InvalidParameterTypeException($this->currentId, $class, $parameter);
175179
}
176180

177-
if ($value instanceof Parameter) {
178-
$value = $this->container->getParameter($value);
179-
} elseif ($value instanceof Expression) {
181+
if ($value instanceof Expression) {
180182
$value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]);
181-
} elseif (\is_string($value) && '%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
182-
$value = $this->container->getParameter($match[1]);
183+
} elseif (\is_string($value)) {
184+
if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
185+
// Only array parameters are not inlined when dumped.
186+
$value = [];
187+
} elseif ($envPlaceholderUniquePrefix && false !== strpos($value, 'env_')) {
188+
// If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it.
189+
// We don't need to change the value because it is already a string.
190+
if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) {
191+
try {
192+
$value = $this->container->resolveEnvPlaceholders($value, true);
193+
} catch (EnvNotFoundException|RuntimeException $e) {
194+
// If an env placeholder cannot be resolved, we skip the validation.
195+
return;
196+
}
197+
}
198+
}
183199
}
184200

185201
if (null === $value && $parameter->allowsNull()) {

src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1616
use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
17+
use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass;
1718
use Symfony\Component\DependencyInjection\ContainerBuilder;
1819
use Symfony\Component\DependencyInjection\Definition;
20+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
21+
use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException;
22+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
1923
use Symfony\Component\DependencyInjection\Reference;
2024
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Bar;
2125
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall;
@@ -33,7 +37,7 @@ class CheckTypeDeclarationsPassTest extends TestCase
3337
{
3438
public function testProcessThrowsExceptionOnInvalidTypesConstructorArguments()
3539
{
36-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
40+
$this->expectException(InvalidArgumentException::class);
3741
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
3842

3943
$container = new ContainerBuilder();
@@ -47,7 +51,7 @@ public function testProcessThrowsExceptionOnInvalidTypesConstructorArguments()
4751

4852
public function testProcessThrowsExceptionOnInvalidTypesMethodCallArguments()
4953
{
50-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
54+
$this->expectException(InvalidArgumentException::class);
5155
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoo" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
5256

5357
$container = new ContainerBuilder();
@@ -61,7 +65,7 @@ public function testProcessThrowsExceptionOnInvalidTypesMethodCallArguments()
6165

6266
public function testProcessFailsWhenPassingNullToRequiredArgument()
6367
{
64-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
68+
$this->expectException(InvalidArgumentException::class);
6569
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct" accepts "stdClass", "NULL" passed.');
6670

6771
$container = new ContainerBuilder();
@@ -74,7 +78,7 @@ public function testProcessFailsWhenPassingNullToRequiredArgument()
7478

7579
public function testProcessThrowsExceptionWhenMissingArgumentsInConstructor()
7680
{
77-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
81+
$this->expectException(InvalidArgumentException::class);
7882
$this->expectExceptionMessage('Invalid definition for service "bar": "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct()" requires 1 arguments, 0 passed.');
7983

8084
$container = new ContainerBuilder();
@@ -111,7 +115,7 @@ public function testProcessRegisterWithClassName()
111115

112116
public function testProcessThrowsExceptionWhenMissingArgumentsInMethodCall()
113117
{
114-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
118+
$this->expectException(InvalidArgumentException::class);
115119
$this->expectExceptionMessage('Invalid definition for service "bar": "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoo()" requires 1 arguments, 0 passed.');
116120

117121
$container = new ContainerBuilder();
@@ -126,7 +130,7 @@ public function testProcessThrowsExceptionWhenMissingArgumentsInMethodCall()
126130

127131
public function testProcessVariadicFails()
128132
{
129-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
133+
$this->expectException(InvalidArgumentException::class);
130134
$this->expectExceptionMessage('Invalid definition for service "bar": argument 2 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoosVariadic" accepts "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo", "stdClass" passed.');
131135

132136
$container = new ContainerBuilder();
@@ -145,7 +149,7 @@ public function testProcessVariadicFails()
145149

146150
public function testProcessVariadicFailsOnPassingBadTypeOnAnotherArgument()
147151
{
148-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
152+
$this->expectException(InvalidArgumentException::class);
149153
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoosVariadic" accepts "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo", "stdClass" passed.');
150154

151155
$container = new ContainerBuilder();
@@ -209,7 +213,7 @@ public function testProcessSuccessWhenUsingOptionalArgumentWithGoodType()
209213

210214
public function testProcessFailsWhenUsingOptionalArgumentWithBadType()
211215
{
212-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
216+
$this->expectException(InvalidArgumentException::class);
213217
$this->expectExceptionMessage('Invalid definition for service "bar": argument 2 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoosOptional" accepts "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo", "stdClass" passed.');
214218

215219
$container = new ContainerBuilder();
@@ -239,7 +243,7 @@ public function testProcessSuccessWhenPassingNullToOptional()
239243

240244
public function testProcessSuccessWhenPassingNullToOptionalThatDoesNotAcceptNull()
241245
{
242-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
246+
$this->expectException(InvalidArgumentException::class);
243247
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarOptionalArgumentNotNull::__construct" accepts "int", "NULL" passed.');
244248

245249
$container = new ContainerBuilder();
@@ -252,7 +256,7 @@ public function testProcessSuccessWhenPassingNullToOptionalThatDoesNotAcceptNull
252256

253257
public function testProcessFailsWhenPassingBadTypeToOptional()
254258
{
255-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
259+
$this->expectException(InvalidArgumentException::class);
256260
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarOptionalArgument::__construct" accepts "stdClass", "string" passed.');
257261

258262
$container = new ContainerBuilder();
@@ -282,7 +286,7 @@ public function testProcessSuccessScalarType()
282286

283287
public function testProcessFailsOnPassingScalarTypeToConstructorTypedWithClass()
284288
{
285-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
289+
$this->expectException(InvalidArgumentException::class);
286290
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Bar::__construct" accepts "stdClass", "integer" passed.');
287291

288292
$container = new ContainerBuilder();
@@ -295,7 +299,7 @@ public function testProcessFailsOnPassingScalarTypeToConstructorTypedWithClass()
295299

296300
public function testProcessFailsOnPassingScalarTypeToMethodTypedWithClass()
297301
{
298-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
302+
$this->expectException(InvalidArgumentException::class);
299303
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setFoo" accepts "stdClass", "string" passed.');
300304

301305
$container = new ContainerBuilder();
@@ -310,7 +314,7 @@ public function testProcessFailsOnPassingScalarTypeToMethodTypedWithClass()
310314

311315
public function testProcessFailsOnPassingClassToScalarTypedParameter()
312316
{
313-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
317+
$this->expectException(InvalidArgumentException::class);
314318
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setScalars" accepts "int", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
315319

316320
$container = new ContainerBuilder();
@@ -370,7 +374,7 @@ public function testProcessSuccessWhenPassingArray()
370374

371375
public function testProcessSuccessWhenPassingIntegerToArrayTypedParameter()
372376
{
373-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException::class);
377+
$this->expectException(InvalidParameterTypeException::class);
374378
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall::setArray" accepts "array", "integer" passed.');
375379

376380
$container = new ContainerBuilder();
@@ -430,7 +434,7 @@ public function testProcessFactory()
430434

431435
public function testProcessFactoryFailsOnInvalidParameterType()
432436
{
433-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
437+
$this->expectException(InvalidArgumentException::class);
434438
$this->expectExceptionMessage('Invalid definition for service "bar": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo::createBarArguments" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
435439

436440
$container = new ContainerBuilder();
@@ -448,7 +452,7 @@ public function testProcessFactoryFailsOnInvalidParameterType()
448452

449453
public function testProcessFactoryFailsOnInvalidParameterTypeOptional()
450454
{
451-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
455+
$this->expectException(InvalidArgumentException::class);
452456
$this->expectExceptionMessage('Invalid definition for service "bar": argument 2 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo::createBarArguments" accepts "stdClass", "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\Foo" passed.');
453457

454458
$container = new ContainerBuilder();
@@ -558,7 +562,7 @@ public function testProcessDoesNotThrowsExceptionOnValidTypes()
558562

559563
public function testProcessThrowsOnIterableTypeWhenScalarPassed()
560564
{
561-
$this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class);
565+
$this->expectException(InvalidArgumentException::class);
562566
$this->expectExceptionMessage('Invalid definition for service "bar_call": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\BarMethodCall::setIterable" accepts "iterable", "integer" passed.');
563567

564568
$container = new ContainerBuilder();
@@ -571,6 +575,20 @@ public function testProcessThrowsOnIterableTypeWhenScalarPassed()
571575
$this->assertInstanceOf(\stdClass::class, $container->get('bar')->foo);
572576
}
573577

578+
public function testProcessResolveArrayParameters()
579+
{
580+
$container = new ContainerBuilder();
581+
$container->setParameter('ccc', ['foobar']);
582+
583+
$container
584+
->register('foobar', BarMethodCall::class)
585+
->addMethodCall('setArray', ['%ccc%']);
586+
587+
(new CheckTypeDeclarationsPass(true))->process($container);
588+
589+
$this->addToAssertionCount(1);
590+
}
591+
574592
public function testProcessResolveExpressions()
575593
{
576594
$container = new ContainerBuilder();
@@ -584,4 +602,73 @@ public function testProcessResolveExpressions()
584602

585603
$this->addToAssertionCount(1);
586604
}
605+
606+
public function testProcessHandleMixedEnvPlaceholder()
607+
{
608+
$this->expectException(InvalidArgumentException::class);
609+
$this->expectExceptionMessage('Invalid definition for service "foobar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall::setArray" accepts "array", "string" passed.');
610+
611+
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
612+
'ccc' => '%env(FOO)%',
613+
]));
614+
615+
$container
616+
->register('foobar', BarMethodCall::class)
617+
->addMethodCall('setArray', ['foo%ccc%']);
618+
619+
(new CheckTypeDeclarationsPass(true))->process($container);
620+
}
621+
622+
public function testProcessHandleMultipleEnvPlaceholder()
623+
{
624+
$this->expectException(InvalidArgumentException::class);
625+
$this->expectExceptionMessage('Invalid definition for service "foobar": argument 1 of "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall::setArray" accepts "array", "string" passed.');
626+
627+
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
628+
'ccc' => '%env(FOO)%',
629+
'fcy' => '%env(int:BAR)%'
630+
]));
631+
632+
$container
633+
->register('foobar', BarMethodCall::class)
634+
->addMethodCall('setArray', ['%ccc%%fcy%']);
635+
636+
(new CheckTypeDeclarationsPass(true))->process($container);
637+
}
638+
639+
public function testProcessHandleExistingEnvPlaceholder()
640+
{
641+
putenv('ARRAY={"foo":"bar"}');
642+
643+
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
644+
'ccc' => '%env(json:ARRAY)%',
645+
]));
646+
647+
$container
648+
->register('foobar', BarMethodCall::class)
649+
->addMethodCall('setArray', ['%ccc%']);
650+
651+
(new ResolveParameterPlaceHoldersPass())->process($container);
652+
(new CheckTypeDeclarationsPass(true))->process($container);
653+
654+
$this->addToAssertionCount(1);
655+
656+
putenv('ARRAY=');
657+
}
658+
659+
public function testProcessHandleNotFoundEnvPlaceholder()
660+
{
661+
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
662+
'ccc' => '%env(json:ARRAY)%',
663+
]));
664+
665+
$container
666+
->register('foobar', BarMethodCall::class)
667+
->addMethodCall('setArray', ['%ccc%']);
668+
669+
(new ResolveParameterPlaceHoldersPass())->process($container);
670+
(new CheckTypeDeclarationsPass(true))->process($container);
671+
672+
$this->addToAssertionCount(1);
673+
}
587674
}

0 commit comments

Comments
 (0)