diff --git a/composer.json b/composer.json index 04e378caf..f7f73da00 100644 --- a/composer.json +++ b/composer.json @@ -45,10 +45,10 @@ "require-dev": { "doctrine/coding-standard": "^14", "doctrine/orm": "^3.4.4", - "phpstan/phpstan": "2.1.1", + "phpstan/phpstan": "^2.1.13", "phpstan/phpstan-phpunit": "2.0.3", "phpstan/phpstan-strict-rules": "^2", - "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-symfony": "^2.0.9", "phpunit/phpunit": "^12.3.10", "psr/log": "^3.0", "symfony/doctrine-messenger": "^6.4 || ^7.0 || ^8.0", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 36d34a370..692096747 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,7 +1,7 @@ includes: - vendor/phpstan/phpstan-symfony/extension.neon parameters: - level: 7 + level: 8 reportUnmatchedIgnoredErrors: true paths: - config diff --git a/src/Controller/ProfilerController.php b/src/Controller/ProfilerController.php index 3ea193134..69f62f997 100644 --- a/src/Controller/ProfilerController.php +++ b/src/Controller/ProfilerController.php @@ -40,7 +40,11 @@ public function explainAction(string $token, string $connectionName, int $query) { $this->profiler->disable(); - $profile = $this->profiler->loadProfile($token); + $profile = $this->profiler->loadProfile($token); + if ($profile === null) { + return new Response('Profile not found.', 404); + } + $collector = $profile->getCollector('db'); assert($collector instanceof DoctrineDataCollector); diff --git a/src/DataCollector/DoctrineDataCollector.php b/src/DataCollector/DoctrineDataCollector.php index 88e436cc2..55f63fc70 100644 --- a/src/DataCollector/DoctrineDataCollector.php +++ b/src/DataCollector/DoctrineDataCollector.php @@ -55,7 +55,7 @@ * types: ?array, * count: int, * index: int, - * executionPercent?: float + * executionPercent: float * } * @phpstan-type GroupedQueriesType = array> * @phpstan-property DataType $data @@ -77,6 +77,10 @@ public function __construct( private readonly bool $shouldValidateSchema = true, DebugDataHolder|null $debugDataHolder = null, ) { + if ($debugDataHolder === null) { + $debugDataHolder = new DebugDataHolder(); + } + parent::__construct($registry, $debugDataHolder); } @@ -304,14 +308,13 @@ public function getGroupedQueries(): array return $a['executionMS'] < $b['executionMS'] ? 1 : -1; }); - $this->groupedQueries[$connection] = $connectionGroupedQueries; - } - foreach ($this->groupedQueries as $connection => $queries) { - foreach ($queries as $i => $query) { - $this->groupedQueries[$connection][$i]['executionPercent'] = + foreach ($connectionGroupedQueries as $i => $query) { + $connectionGroupedQueries[$i]['executionPercent'] = $this->executionTimePercentage($query['executionMS'], $totalExecutionMS); } + + $this->groupedQueries[$connection] = $connectionGroupedQueries; } return $this->groupedQueries; diff --git a/src/DependencyInjection/Compiler/MiddlewaresPass.php b/src/DependencyInjection/Compiler/MiddlewaresPass.php index df884b043..1502d07d6 100644 --- a/src/DependencyInjection/Compiler/MiddlewaresPass.php +++ b/src/DependencyInjection/Compiler/MiddlewaresPass.php @@ -64,7 +64,8 @@ public function process(ContainerBuilder $container): void ); $middlewareRefs[$id] = [new Reference($childId), ++$i]; - if (! is_subclass_of($abstractDef->getClass(), ConnectionNameAwareInterface::class)) { + $class = $abstractDef->getClass(); + if ($class === null || ! is_subclass_of($class, ConnectionNameAwareInterface::class)) { continue; } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index f2591c7b1..b74499458 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -67,7 +67,6 @@ private function addDbalSection(ArrayNodeDefinition $node): void // Key that should not be rewritten to the connection config $excludedKeys = ['default_connection' => true, 'driver_schemes' => true, 'driver_scheme' => true, 'types' => true, 'type' => true]; - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $node ->children() ->arrayNode('dbal') @@ -167,7 +166,6 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition $this->configureDbalDriverNode($connectionNode); - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $connectionNode ->fixXmlConfig('option') ->fixXmlConfig('mapping_type') @@ -214,7 +212,6 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition ->scalarNode('result_cache')->end() ->end(); - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $replicaNode = $connectionNode ->children() ->arrayNode('replicas') @@ -232,7 +229,6 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition */ private function configureDbalDriverNode(ArrayNodeDefinition $node): void { - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $node ->validate() ->always(static function (array $values) { @@ -370,7 +366,6 @@ private function addOrmSection(ArrayNodeDefinition $node): void 'controller_resolver' => true, ]; - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $node ->children() ->arrayNode('orm') @@ -518,7 +513,6 @@ private function getOrmEntityListenersNode(): NodeDefinition return ['entities' => $entities]; }; - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $node ->beforeNormalization() // Yaml normalization @@ -564,7 +558,6 @@ private function getOrmEntityManagersNode(): ArrayNodeDefinition $treeBuilder = new TreeBuilder('entity_managers'); $node = $treeBuilder->getRootNode(); - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $node ->requiresAtLeastOneElement() ->useAttributeAsKey('name') @@ -739,7 +732,6 @@ private function getOrmCacheDriverNode(string $name): ArrayNodeDefinition $treeBuilder = new TreeBuilder($name); $node = $treeBuilder->getRootNode(); - /** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */ $node ->beforeNormalization() ->ifString() diff --git a/src/DependencyInjection/DoctrineExtension.php b/src/DependencyInjection/DoctrineExtension.php index 92087b024..9bd55a435 100644 --- a/src/DependencyInjection/DoctrineExtension.php +++ b/src/DependencyInjection/DoctrineExtension.php @@ -171,9 +171,11 @@ private function loadMappingInformation(array $objectManager, ContainerBuilder $ throw new InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName)); } - $mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container, $bundleMetadata['path']); - if (! $mappingConfig) { - continue; + if ($bundleMetadata !== null) { + $mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container, $bundleMetadata['path']); + if (! $mappingConfig) { + continue; + } } } elseif (! $mappingConfig['type']) { $mappingConfig['type'] = 'attribute'; diff --git a/src/DoctrineBundle.php b/src/DoctrineBundle.php index e27277708..02d6856cd 100644 --- a/src/DoctrineBundle.php +++ b/src/DoctrineBundle.php @@ -78,6 +78,10 @@ public function process(ContainerBuilder $container): void public function shutdown(): void { + if ($this->container === null) { + return; + } + // Clear all entity managers to clear references to entities for GC if ($this->container->hasParameter('doctrine.entity_managers')) { foreach ($this->container->getParameter('doctrine.entity_managers') as $id) { diff --git a/src/Twig/DoctrineExtension.php b/src/Twig/DoctrineExtension.php index ca466250b..bcf33af5d 100644 --- a/src/Twig/DoctrineExtension.php +++ b/src/Twig/DoctrineExtension.php @@ -7,6 +7,7 @@ use Doctrine\SqlFormatter\HtmlHighlighter; use Doctrine\SqlFormatter\NullHighlighter; use Doctrine\SqlFormatter\SqlFormatter; +use RuntimeException; use Stringable; use Symfony\Component\VarDumper\Cloner\Data; use Twig\Extension\AbstractExtension; @@ -24,11 +25,15 @@ use function is_array; use function is_bool; use function is_string; +use function preg_last_error; use function preg_match; use function preg_replace_callback; +use function sprintf; use function strtoupper; use function substr; +use const PREG_NO_ERROR; + /** * This class contains the needed functions in order to do the query highlighting * @@ -116,7 +121,7 @@ public function replaceQueryParameters(string $query, array|Data $parameters): s $i = 0; - return preg_replace_callback( + $result = preg_replace_callback( '/(? $params Connection parameters + * @param array{url?: string, path?: string, driver: string} $params Connection parameters * @psalm-param Params $params * * @return Stub&Container diff --git a/tests/DataCollector/DoctrineDataCollectorTest.php b/tests/DataCollector/DoctrineDataCollectorTest.php index 6fd7593b3..8541f7e2d 100644 --- a/tests/DataCollector/DoctrineDataCollectorTest.php +++ b/tests/DataCollector/DoctrineDataCollectorTest.php @@ -146,9 +146,11 @@ public function testGetGroupedQueries(): void */ private function createEntityMetadata(string $entityFQCN): ClassMetadata { - $metadata = new ClassMetadata($entityFQCN); - $metadata->name = $entityFQCN; - $metadata->reflClass = new ReflectionClass('stdClass'); + $metadata = new ClassMetadata($entityFQCN); + $metadata->name = $entityFQCN; + /** @var ReflectionClass $stdClassReflection */ + $stdClassReflection = new ReflectionClass('stdClass'); + $metadata->reflClass = $stdClassReflection; return $metadata; } diff --git a/tests/DependencyInjection/XMLSchemaTest.php b/tests/DependencyInjection/XMLSchemaTest.php index c2b9aea9e..37410b578 100644 --- a/tests/DependencyInjection/XMLSchemaTest.php +++ b/tests/DependencyInjection/XMLSchemaTest.php @@ -41,28 +41,34 @@ public function testValidateSchema(string $file): void $dbalElements = $dom->getElementsByTagNameNS($xmlns, 'dbal'); if ($dbalElements->length) { - $dbalDom = new DOMDocument('1.0', 'UTF-8'); - $dbalNode = $dbalDom->importNode($dbalElements->item(0)); - $configNode = $dbalDom->createElementNS($xmlns, 'config'); - $configNode->appendChild($dbalNode); - $dbalDom->appendChild($configNode); + $dbalDom = new DOMDocument('1.0', 'UTF-8'); + $dbalElement = $dbalElements->item(0); + if ($dbalElement !== null) { + $dbalNode = $dbalDom->importNode($dbalElement); + $configNode = $dbalDom->createElementNS($xmlns, 'config'); + $configNode->appendChild($dbalNode); + $dbalDom->appendChild($configNode); - $ret = $dbalDom->schemaValidate(__DIR__ . '/../../config/schema/doctrine-1.0.xsd'); - $this->assertTrue($ret, 'DoctrineBundle Dependency Injection XMLSchema did not validate this XML instance.'); - $found = true; + $ret = $dbalDom->schemaValidate(__DIR__ . '/../../config/schema/doctrine-1.0.xsd'); + $this->assertTrue($ret, 'DoctrineBundle Dependency Injection XMLSchema did not validate this XML instance.'); + $found = true; + } } $ormElements = $dom->getElementsByTagNameNS($xmlns, 'orm'); if ($ormElements->length) { $ormDom = new DOMDocument('1.0', 'UTF-8'); - $ormNode = $ormDom->importNode($ormElements->item(0)); - $configNode = $ormDom->createElementNS($xmlns, 'config'); - $configNode->appendChild($ormNode); - $ormDom->appendChild($configNode); + $ormElement = $ormElements->item(0); + if ($ormElement !== null) { + $ormNode = $ormDom->importNode($ormElement); + $configNode = $ormDom->createElementNS($xmlns, 'config'); + $configNode->appendChild($ormNode); + $ormDom->appendChild($configNode); - $ret = $ormDom->schemaValidate(__DIR__ . '/../../config/schema/doctrine-1.0.xsd'); - $this->assertTrue($ret, 'DoctrineBundle Dependency Injection XMLSchema did not validate this XML instance.'); - $found = true; + $ret = $ormDom->schemaValidate(__DIR__ . '/../../config/schema/doctrine-1.0.xsd'); + $this->assertTrue($ret, 'DoctrineBundle Dependency Injection XMLSchema did not validate this XML instance.'); + $found = true; + } } $this->assertTrue($found, 'Neither nor elements found in given XML. Are namespaces configured correctly?');