Skip to content

Commit 4f493e1

Browse files
feature #31310 [DependencyInjection] Added option ignore_errors: not_found for imported config files (pulzarraider)
This PR was merged into the 4.4 branch. Discussion ---------- [DependencyInjection] Added option `ignore_errors: not_found` for imported config files | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | symfony/symfony-docs#11647 If someone want to add optional config file. The only available choice was to add `ignore_errors: true` option e.g. ``` imports: - { resource: parameters.yml, ignore_errors: true } ``` But this will hide all errors in imported file. We ran in many situations that broke our Symfony applications because we had a typo in this imported files. This PR introduce new possible value `not_found` for `ignore_errors` option. It can be used for optional config files like the `ignore_errors: true`, but it will ignore only the file non-existence, not the possible syntax errors inside. Usage: ``` imports: - { resource: parameters.yml, ignore_errors: not_found} ``` Commits ------- e0ee01c10d [DependencyInjection] Added option `ignore_errors: not_found` while importing config files
2 parents c790de1 + 4d5c3f3 commit 4f493e1

13 files changed

+140
-4
lines changed

Loader/Configurator/ContainerConfigurator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ final public function extension(string $namespace, array $config)
6060
$this->container->loadFromExtension($namespace, static::processValue($config));
6161
}
6262

63-
final public function import(string $resource, string $type = null, bool $ignoreErrors = false)
63+
final public function import(string $resource, string $type = null, $ignoreErrors = false)
6464
{
6565
$this->loader->setCurrentDir(\dirname($this->path));
6666
$this->loader->import($resource, $type, $ignoreErrors, $this->file);

Loader/FileLoader.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader;
1313

14+
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
15+
use Symfony\Component\Config\Exception\LoaderLoadException;
1416
use Symfony\Component\Config\FileLocatorInterface;
1517
use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
18+
use Symfony\Component\Config\Loader\Loader;
1619
use Symfony\Component\Config\Resource\GlobResource;
1720
use Symfony\Component\DependencyInjection\ChildDefinition;
1821
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -41,6 +44,42 @@ public function __construct(ContainerBuilder $container, FileLocatorInterface $l
4144
parent::__construct($locator);
4245
}
4346

47+
/**
48+
* {@inheritdoc}
49+
*
50+
* @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found
51+
* @param string|string[]|null $exclude Glob patterns to exclude from the import
52+
*/
53+
public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null/*, $exclude = null*/)
54+
{
55+
$args = \func_get_args();
56+
57+
if ($ignoreNotFound = 'not_found' === $ignoreErrors) {
58+
$args[2] = false;
59+
} elseif (!\is_bool($ignoreErrors)) {
60+
@trigger_error(sprintf('Invalid argument $ignoreErrors provided to %s::import(): boolean or "not_found" expected, %s given.', \get_class($this), \gettype($ignoreErrors)), E_USER_DEPRECATED);
61+
$args[2] = (bool) $ignoreErrors;
62+
}
63+
64+
try {
65+
parent::import(...$args);
66+
} catch (LoaderLoadException $e) {
67+
if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) {
68+
throw $e;
69+
}
70+
71+
foreach ($prev->getTrace() as $frame) {
72+
if ('import' === ($frame['function'] ?? null) && is_a($frame['class'] ?? '', Loader::class, true)) {
73+
break;
74+
}
75+
}
76+
77+
if ($args !== $frame['args']) {
78+
throw $e;
79+
}
80+
}
81+
}
82+
4483
/**
4584
* Registers a set of classes as services using PSR-4 for discovery.
4685
*

Loader/XmlFileLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private function parseImports(\DOMDocument $xml, string $file)
105105
$defaultDirectory = \dirname($file);
106106
foreach ($imports as $import) {
107107
$this->setCurrentDir($defaultDirectory);
108-
$this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, (bool) XmlUtils::phpize($import->getAttribute('ignore-errors')), $file);
108+
$this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, XmlUtils::phpize($import->getAttribute('ignore-errors')) ?: false, $file);
109109
}
110110
}
111111

Loader/YamlFileLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private function parseImports(array $content, string $file)
191191
}
192192

193193
$this->setCurrentDir($defaultDirectory);
194-
$this->import($import['resource'], isset($import['type']) ? $import['type'] : null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file);
194+
$this->import($import['resource'], $import['type'] ?? null, $import['ignore_errors'] ?? false, $file);
195195
}
196196
}
197197

Loader/schema/dic/services/services-1.0.xsd

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
]]></xsd:documentation>
7979
</xsd:annotation>
8080
<xsd:attribute name="resource" type="xsd:string" use="required" />
81-
<xsd:attribute name="ignore-errors" type="boolean" />
81+
<xsd:attribute name="ignore-errors" type="ignore_errors" />
8282
<xsd:attribute name="type" type="xsd:string" />
8383
</xsd:complexType>
8484

@@ -273,6 +273,12 @@
273273
</xsd:restriction>
274274
</xsd:simpleType>
275275

276+
<xsd:simpleType name="ignore_errors">
277+
<xsd:restriction base="xsd:string">
278+
<xsd:pattern value="(true|false|not_found)" />
279+
</xsd:restriction>
280+
</xsd:simpleType>
281+
276282
<xsd:simpleType name="invalid_sequence">
277283
<xsd:restriction base="xsd:string">
278284
<xsd:enumeration value="null" />
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
6+
<imports>
7+
<import resource="foo_fake.xml" ignore-errors="not_found" />
8+
</imports>
9+
</container>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
6+
<imports>
7+
<import resource="nonvalid.xml" ignore-errors="not_found" />
8+
</imports>
9+
</container>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
6+
<imports>
7+
<import resource="foo_fake.xml" />
8+
</imports>
9+
</container>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
imports:
2+
- { resource: foo_fake.yml, ignore_errors: not_found }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
imports:
2+
- { resource: nonvalid2.yml, ignore_errors: not_found }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
imports:
2+
- { resource: foo_fake.yml }

Tests/Loader/XmlFileLoaderTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,37 @@ public function testLoadImports()
183183

184184
// Bad import throws no exception due to ignore_errors value.
185185
$loader->load('services4_bad_import.xml');
186+
187+
// Bad import with nonexistent file throws no exception due to ignore_errors: not_found value.
188+
$loader->load('services4_bad_import_file_not_found.xml');
189+
190+
try {
191+
$loader->load('services4_bad_import_with_errors.xml');
192+
$this->fail('->load() throws a LoaderLoadException if the imported xml file configuration does not exist');
193+
} catch (\Exception $e) {
194+
$this->assertInstanceOf('Symfony\\Component\\Config\\Exception\\LoaderLoadException', $e, '->load() throws a LoaderLoadException if the imported xml file configuration does not exist');
195+
$this->assertRegExp(sprintf('#^The file "%1$s" does not exist \(in: .+\) in %1$s \(which is being imported from ".+%2$s"\)\.$#', 'foo_fake\.xml', 'services4_bad_import_with_errors\.xml'), $e->getMessage(), '->load() throws a LoaderLoadException if the imported xml file configuration does not exist');
196+
197+
$e = $e->getPrevious();
198+
$this->assertInstanceOf('Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException', $e, '->load() throws a FileLocatorFileNotFoundException if the imported xml file configuration does not exist');
199+
$this->assertRegExp(sprintf('#^The file "%s" does not exist \(in: .+\)\.$#', 'foo_fake\.xml'), $e->getMessage(), '->load() throws a FileLocatorFileNotFoundException if the imported xml file configuration does not exist');
200+
}
201+
202+
try {
203+
$loader->load('services4_bad_import_nonvalid.xml');
204+
$this->fail('->load() throws an LoaderLoadException if the imported configuration does not validate the XSD');
205+
} catch (\Exception $e) {
206+
$this->assertInstanceOf('Symfony\\Component\\Config\\Exception\\LoaderLoadException', $e, '->load() throws a LoaderLoadException if the imported configuration does not validate the XSD');
207+
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s": .+.$#', 'nonvalid\.xml'), $e->getMessage(), '->load() throws a LoaderLoadException if the imported configuration does not validate the XSD');
208+
209+
$e = $e->getPrevious();
210+
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
211+
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s": .+.$#', 'nonvalid\.xml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
212+
213+
$e = $e->getPrevious();
214+
$this->assertInstanceOf('Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException', $e, '->load() throws a XmlParsingException if the configuration does not validate the XSD');
215+
$this->assertStringStartsWith('[ERROR 1845] Element \'nonvalid\': No matching global declaration available for the validation root. (in', $e->getMessage(), '->load() throws a XmlParsingException if the loaded file does not validate the XSD');
216+
}
186217
}
187218

188219
public function testLoadAnonymousServices()

Tests/Loader/YamlFileLoaderTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,33 @@ public function testLoadImports()
135135

136136
// Bad import throws no exception due to ignore_errors value.
137137
$loader->load('services4_bad_import.yml');
138+
139+
// Bad import with nonexistent file throws no exception due to ignore_errors: not_found value.
140+
$loader->load('services4_bad_import_file_not_found.yml');
141+
142+
try {
143+
$loader->load('services4_bad_import_with_errors.yml');
144+
$this->fail('->load() throws a LoaderLoadException if the imported yaml file does not exist');
145+
} catch (\Exception $e) {
146+
$this->assertInstanceOf('Symfony\\Component\\Config\\Exception\\LoaderLoadException', $e, '->load() throws a LoaderLoadException if the imported yaml file does not exist');
147+
$this->assertRegExp(sprintf('#^The file "%1$s" does not exist \(in: .+\) in %1$s \(which is being imported from ".+%2$s"\)\.$#', 'foo_fake\.yml', 'services4_bad_import_with_errors\.yml'), $e->getMessage(), '->load() throws a LoaderLoadException if the imported yaml file does not exist');
148+
149+
$e = $e->getPrevious();
150+
$this->assertInstanceOf('Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException', $e, '->load() throws a FileLocatorFileNotFoundException if the imported yaml file does not exist');
151+
$this->assertRegExp(sprintf('#^The file "%s" does not exist \(in: .+\)\.$#', 'foo_fake\.yml'), $e->getMessage(), '->load() throws a FileLocatorFileNotFoundException if the imported yaml file does not exist');
152+
}
153+
154+
try {
155+
$loader->load('services4_bad_import_nonvalid.yml');
156+
$this->fail('->load() throws a LoaderLoadException if the tag in the imported yaml file is not valid');
157+
} catch (\Exception $e) {
158+
$this->assertInstanceOf('Symfony\\Component\\Config\\Exception\\LoaderLoadException', $e, '->load() throws a LoaderLoadException if the tag in the imported yaml file is not valid');
159+
$this->assertRegExp(sprintf('#^The service file ".+%1$s" is not valid\. It should contain an array\. Check your YAML syntax in .+%1$s \(which is being imported from ".+%2$s"\)\.$#', 'nonvalid2\.yml', 'services4_bad_import_nonvalid.yml'), $e->getMessage(), '->load() throws a LoaderLoadException if the tag in the imported yaml file is not valid');
160+
161+
$e = $e->getPrevious();
162+
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the tag in the imported yaml file is not valid');
163+
$this->assertRegExp(sprintf('#^The service file ".+%s" is not valid\. It should contain an array\. Check your YAML syntax\.$#', 'nonvalid2\.yml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the tag in the imported yaml file is not valid');
164+
}
138165
}
139166

140167
public function testLoadServices()

0 commit comments

Comments
 (0)