From 5e53558c97941d5b22e58e2a1a8cfcde95e06b3a Mon Sep 17 00:00:00 2001 From: patriziawacht <47242136+patriziawacht@users.noreply.github.com> Date: Mon, 9 Mar 2020 07:36:04 +0100 Subject: [PATCH] check services from cached container service file (#7) * load with $config ContaimerServicefile, parse service<=>class * fix psalm messages, optimize code * debug plugin-config take only for symfony-psalm-plugin, some optimizations cache servicemap * Housekeeping fix psalm * Update ClassHandler.php Co-authored-by: Farhad Safarov --- src/Handler/ClassHandler.php | 59 ++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/Handler/ClassHandler.php b/src/Handler/ClassHandler.php index 926713d..2950816 100644 --- a/src/Handler/ClassHandler.php +++ b/src/Handler/ClassHandler.php @@ -22,7 +22,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface; class ClassHandler implements AfterClassLikeAnalysisInterface, AfterMethodCallAnalysisInterface { /** - * {@inheritDoc} + * @psalm-var array + */ + private static $classServiceMap = []; + + /** + * {@inheritdoc} */ public static function afterStatementAnalysis(Node\Stmt\ClassLike $stmt, ClassLikeStorage $classlike_storage, StatementsSource $statements_source, Codebase $codebase, array &$file_replacements = []) { @@ -43,7 +48,7 @@ class ClassHandler implements AfterClassLikeAnalysisInterface, AfterMethodCallAn } /** - * {@inheritDoc} + * {@inheritdoc} */ public static function afterMethodCallAnalysis( Expr $expr, @@ -63,6 +68,15 @@ class ClassHandler implements AfterClassLikeAnalysisInterface, AfterMethodCallAn $className = (string) $expr->args[0]->value->class->getAttribute('resolvedName'); $return_type_candidate = new Union([new TNamedObject($className)]); } + if (!count(self::$classServiceMap)) { + self::$classServiceMap = self::loadServiceFile($codebase); + } + if ($return_type_candidate && count(self::$classServiceMap) && $expr->args[0]->value instanceof Node\Scalar\String_) { + $serviceName = (string) $expr->args[0]->value->value; + if (isset(self::$classServiceMap[$serviceName])) { + $return_type_candidate = new Union([new TNamedObject((string) self::$classServiceMap[$serviceName])]); + } + } break; case 'Symfony\Component\HttpFoundation\Request::getcontent': if ($return_type_candidate) { @@ -85,4 +99,45 @@ class ClassHandler implements AfterClassLikeAnalysisInterface, AfterMethodCallAn break; } } + + /** + * @todo don't check every time if containerXml config is not set. + * @psalm-return array + */ + private static function loadServiceFile(Codebase $codebase): array + { + $classServiceMap = []; + if (count($codebase->config->getPluginClasses())) { + foreach ($codebase->config->getPluginClasses() as $pluginClass) { + if ($pluginClass['class'] === str_replace('Handler', 'Plugin', __NAMESPACE__)) { + $simpleXmlConfig = $pluginClass['config']; + } + } + } + + if (isset($simpleXmlConfig) && $simpleXmlConfig instanceof \SimpleXMLElement) { + $serviceFilePath = (string) $simpleXmlConfig->containerXml; + if (!file_exists($serviceFilePath)) { + return []; + } + $xml = simplexml_load_file($serviceFilePath); + if (!$xml->services instanceof \SimpleXMLElement) { + return $classServiceMap; + } + $services = $xml->services; + /** @psalm-suppress MixedAssignment */ + if (count($services)) { + foreach ($services->service as $serviceObj) { + if (isset($serviceObj) && $serviceObj instanceof \SimpleXMLElement) { + $serviceAttributes = $serviceObj->attributes(); + if ($serviceAttributes && isset($serviceAttributes['id']) && isset($serviceAttributes['class'])) { + $classServiceMap[(string) $serviceAttributes['id']] = (string) $serviceAttributes['class']; + } + } + } + } + } + + return $classServiceMap; + } }