diff --git a/README.md b/README.md index 275dfe2b..5e118e82 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ vendor/bin/psalm-plugin enable psalm/plugin-symfony - Detect `ContainerInterface::get()` result type. Works better if you [configure](#configuration) compiled container XML file. - Detect return type of console arguments (`InputInterface::getArgument()`) and options (`InputInterface::getOption()`). Enforces to use InputArgument and InputOption constants as a part of best practise. +- Detects correct Doctrine repository class if entities are configured with annotations. - Fixes `PossiblyInvalidArgument` for `Symfony\Component\HttpFoundation\Request::getContent`. The plugin calculates real return type by checking the given argument and marks return type as either string or resource. - Detect return type of `Symfony\Component\HttpFoundation\HeaderBag::get` (by checking third argument for < Symfony 4.4) @@ -49,6 +50,17 @@ Default file for Symfony versions: - Symfony 4: var/cache/dev/srcApp_KernelDevDebugContainer.xml - Symfony 5: var/cache/dev/App_KernelDevDebugContainer.xml +Multiple container files can be configured. In this case, first valid file is taken into account. +If none of the given files is valid, configuration exception is thrown. +Example: + +```xml + + var/cache/dev/App_KernelDevDebugContainer.xml + var/cache/dev/App_KernelTestDebugContainer.xml + +``` + ### Credits - Plugin created by [@seferov](https://github.com/seferov) diff --git a/psalm.xml b/psalm.xml index 34bee50c..70f87f4c 100644 --- a/psalm.xml +++ b/psalm.xml @@ -11,7 +11,6 @@ - diff --git a/src/Plugin.php b/src/Plugin.php index 6b3b42d3..477cd1f3 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -3,7 +3,6 @@ namespace Psalm\SymfonyPsalmPlugin; use Doctrine\Common\Annotations\AnnotationRegistry; -use Psalm\Exception\ConfigException; use Psalm\Plugin\PluginEntryPointInterface; use Psalm\Plugin\RegistrationInterface; use Psalm\SymfonyPsalmPlugin\Handler\ClassHandler; @@ -35,17 +34,12 @@ public function __invoke(RegistrationInterface $api, SimpleXMLElement $config = } if (isset($config->containerXml)) { - $containerXmlPath = realpath((string) $config->containerXml); - if (!$containerXmlPath) { - throw new ConfigException(sprintf('Container XML file (%s) does not exits', $containerXmlPath)); - } - - ContainerHandler::init(new ContainerMeta($containerXmlPath)); + ContainerHandler::init(new ContainerMeta((array) $config->containerXml)); } $api->registerHooksFromClass(ContainerHandler::class); - foreach (glob(__DIR__ . '/Stubs/*.stubphp') as $stubFilePath) { + foreach (glob(__DIR__.'/Stubs/*.stubphp') as $stubFilePath) { $api->addStubFile($stubFilePath); } } diff --git a/src/Symfony/ContainerMeta.php b/src/Symfony/ContainerMeta.php index 62d5edb6..f2c7d081 100644 --- a/src/Symfony/ContainerMeta.php +++ b/src/Symfony/ContainerMeta.php @@ -18,36 +18,9 @@ class ContainerMeta */ private $classNames = []; - public function __construct(string $containerXmlPath) + public function __construct(array $containerXmlPaths) { - if (!file_exists($containerXmlPath)) { - throw new ConfigException('Container xml file not found at '.$containerXmlPath); - } - - $xml = simplexml_load_file($containerXmlPath); - if (!$xml->services instanceof \SimpleXMLElement) { - throw new ConfigException('Not a valid container xml file'); - } - - /** @psalm-var \SimpleXMLElement $serviceXml */ - foreach ($xml->services->service as $serviceXml) { - /** @psalm-var \SimpleXMLElement $serviceAttributes */ - $serviceAttributes = $serviceXml->attributes(); - - $className = (string) $serviceAttributes->class; - - if ($className) { - $this->classNames[] = $className; - } - - $service = new Service((string) $serviceAttributes->id, $className); - if (isset($serviceAttributes->alias)) { - $service->setAlias((string) $serviceAttributes->alias); - } - $service->setIsPublic('false' !== (string) $serviceAttributes->public); - - $this->add($service); - } + $this->init($containerXmlPaths); } public function get(string $id): ?Service @@ -76,4 +49,44 @@ public function getClassNames(): array { return $this->classNames; } + + private function init(array $containerXmlPaths): void + { + /** @var string $containerXmlPath */ + foreach ($containerXmlPaths as $containerXmlPath) { + $xmlPath = realpath((string) $containerXmlPath); + if (!$xmlPath || !file_exists($xmlPath)) { + continue; + } + + $xml = simplexml_load_file($xmlPath); + if (!$xml->services instanceof \SimpleXMLElement) { + throw new ConfigException($xmlPath.' is not a valid container xml file'); + } + + /** @psalm-var \SimpleXMLElement $serviceXml */ + foreach ($xml->services->service as $serviceXml) { + /** @psalm-var \SimpleXMLElement $serviceAttributes */ + $serviceAttributes = $serviceXml->attributes(); + + $className = (string) $serviceAttributes->class; + + if ($className) { + $this->classNames[] = $className; + } + + $service = new Service((string) $serviceAttributes->id, $className); + if (isset($serviceAttributes->alias)) { + $service->setAlias((string) $serviceAttributes->alias); + } + $service->setIsPublic('false' !== (string) $serviceAttributes->public); + + $this->add($service); + } + + return; + } + + throw new ConfigException('Container xml file(s) not found at '); + } } diff --git a/tests/unit/Symfony/ContainerMetaTest.php b/tests/unit/Symfony/ContainerMetaTest.php index dc73f998..f7ef7335 100644 --- a/tests/unit/Symfony/ContainerMetaTest.php +++ b/tests/unit/Symfony/ContainerMetaTest.php @@ -19,7 +19,7 @@ class ContainerMetaTest extends TestCase public function setUp() { - $this->containerMeta = new ContainerMeta(__DIR__.'/../../acceptance/container.xml'); + $this->containerMeta = new ContainerMeta([__DIR__.'/../../acceptance/container.xml']); } public function tearDown() @@ -71,7 +71,7 @@ public function publicServices() public function testInvalidFile() { $this->expectException(ConfigException::class); - $this->containerMeta = new ContainerMeta('non-existent-file.xml'); + $this->containerMeta = new ContainerMeta(['non-existent-file.xml']); } /** @@ -81,4 +81,14 @@ public function testNonExistentService() { $this->assertNull($this->containerMeta->get('non-existent-service')); } + + /** + * @testdox one valid, one invalid file should not raise an issue + */ + public function testBothValidAndInvalidArray() + { + $containerMeta = new ContainerMeta(['non-existent-file.xml', __DIR__.'/../../acceptance/container.xml']); + $service = $containerMeta->get('service_container'); + $this->assertSame('Symfony\Component\DependencyInjection\ContainerInterface', $service->getClassName()); + } }