Skip to content

Commit 47e9a0f

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

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy