vendor/doctrine/doctrine-bundle/src/DataCollector/DoctrineDataCollector.php line 108

Open in your IDE?
  1. <?php
  2. namespace Doctrine\Bundle\DoctrineBundle\DataCollector;
  3. use Doctrine\DBAL\Types\Type;
  4. use Doctrine\ORM\Cache\CacheConfiguration;
  5. use Doctrine\ORM\Cache\Logging\CacheLoggerChain;
  6. use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
  7. use Doctrine\ORM\EntityManagerInterface;
  8. use Doctrine\ORM\Mapping\ClassMetadata;
  9. use Doctrine\ORM\Tools\SchemaValidator;
  10. use Doctrine\Persistence\ManagerRegistry;
  11. use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
  12. use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector as BaseCollector;
  13. use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\Response;
  16. use Throwable;
  17. use function array_map;
  18. use function array_sum;
  19. use function assert;
  20. use function count;
  21. use function usort;
  22. /**
  23.  * @psalm-type QueryType = array{
  24.  *    executionMS: float,
  25.  *    explainable: bool,
  26.  *    sql: string,
  27.  *    params: ?array<array-key, mixed>,
  28.  *    runnable: bool,
  29.  *    types: ?array<array-key, Type|int|string|null>,
  30.  * }
  31.  * @psalm-type DataType = array{
  32.  *    caches: array{
  33.  *       enabled: bool,
  34.  *       counts: array<"puts"|"hits"|"misses", int>,
  35.  *       log_enabled: bool,
  36.  *       regions: array<"puts"|"hits"|"misses", array<string, int>>,
  37.  *    },
  38.  *    connections: list<string>,
  39.  *    entities: array<string, array<class-string, array{class: class-string, file: false|string, line: false|int}>>,
  40.  *    errors: array<string, array<class-string, list<string>>>,
  41.  *    managers: list<string>,
  42.  *    queries: array<string, list<QueryType>>,
  43.  * }
  44.  * @psalm-property DataType $data
  45.  */
  46. class DoctrineDataCollector extends BaseCollector
  47. {
  48.     private ManagerRegistry $registry;
  49.     private ?int $invalidEntityCount null;
  50.     /**
  51.      * @var mixed[][]|null
  52.      * @psalm-var ?array<string, list<QueryType&array{count: int, index: int, executionPercent?: float}>>
  53.      */
  54.     private ?array $groupedQueries null;
  55.     private bool $shouldValidateSchema;
  56.     public function __construct(ManagerRegistry $registrybool $shouldValidateSchema true, ?DebugDataHolder $debugDataHolder null)
  57.     {
  58.         $this->registry             $registry;
  59.         $this->shouldValidateSchema $shouldValidateSchema;
  60.         parent::__construct($registry$debugDataHolder);
  61.     }
  62.     public function collect(Request $requestResponse $response, ?Throwable $exception null): void
  63.     {
  64.         parent::collect($request$response$exception);
  65.         $errors   = [];
  66.         $entities = [];
  67.         $caches   = [
  68.             'enabled' => false,
  69.             'log_enabled' => false,
  70.             'counts' => [
  71.                 'puts' => 0,
  72.                 'hits' => 0,
  73.                 'misses' => 0,
  74.             ],
  75.             'regions' => [
  76.                 'puts' => [],
  77.                 'hits' => [],
  78.                 'misses' => [],
  79.             ],
  80.         ];
  81.         foreach ($this->registry->getManagers() as $name => $em) {
  82.             assert($em instanceof EntityManagerInterface);
  83.             if ($this->shouldValidateSchema) {
  84.                 $entities[$name] = [];
  85.                 $factory   $em->getMetadataFactory();
  86.                 $validator = new SchemaValidator($em);
  87.                 assert($factory instanceof AbstractClassMetadataFactory);
  88.                 foreach ($factory->getLoadedMetadata() as $class) {
  89.                     assert($class instanceof ClassMetadata);
  90.                     if (isset($entities[$name][$class->getName()])) {
  91.                         continue;
  92.                     }
  93.                     $classErrors                        $validator->validateClass($class);
  94.                     $r                                  $class->getReflectionClass();
  95.                     $entities[$name][$class->getName()] = [
  96.                         'class' => $class->getName(),
  97.                         'file' => $r->getFileName(),
  98.                         'line' => $r->getStartLine(),
  99.                     ];
  100.                     if (empty($classErrors)) {
  101.                         continue;
  102.                     }
  103.                     $errors[$name][$class->getName()] = $classErrors;
  104.                 }
  105.             }
  106.             $emConfig   $em->getConfiguration();
  107.             $slcEnabled $emConfig->isSecondLevelCacheEnabled();
  108.             if (! $slcEnabled) {
  109.                 continue;
  110.             }
  111.             $caches['enabled'] = true;
  112.             $cacheConfiguration $emConfig->getSecondLevelCacheConfiguration();
  113.             assert($cacheConfiguration instanceof CacheConfiguration);
  114.             $cacheLoggerChain $cacheConfiguration->getCacheLogger();
  115.             assert($cacheLoggerChain instanceof CacheLoggerChain || $cacheLoggerChain === null);
  116.             if (! $cacheLoggerChain || ! $cacheLoggerChain->getLogger('statistics')) {
  117.                 continue;
  118.             }
  119.             $cacheLoggerStats $cacheLoggerChain->getLogger('statistics');
  120.             assert($cacheLoggerStats instanceof StatisticsCacheLogger);
  121.             $caches['log_enabled'] = true;
  122.             $caches['counts']['puts']   += $cacheLoggerStats->getPutCount();
  123.             $caches['counts']['hits']   += $cacheLoggerStats->getHitCount();
  124.             $caches['counts']['misses'] += $cacheLoggerStats->getMissCount();
  125.             foreach ($cacheLoggerStats->getRegionsPut() as $key => $value) {
  126.                 if (! isset($caches['regions']['puts'][$key])) {
  127.                     $caches['regions']['puts'][$key] = 0;
  128.                 }
  129.                 $caches['regions']['puts'][$key] += $value;
  130.             }
  131.             foreach ($cacheLoggerStats->getRegionsHit() as $key => $value) {
  132.                 if (! isset($caches['regions']['hits'][$key])) {
  133.                     $caches['regions']['hits'][$key] = 0;
  134.                 }
  135.                 $caches['regions']['hits'][$key] += $value;
  136.             }
  137.             foreach ($cacheLoggerStats->getRegionsMiss() as $key => $value) {
  138.                 if (! isset($caches['regions']['misses'][$key])) {
  139.                     $caches['regions']['misses'][$key] = 0;
  140.                 }
  141.                 $caches['regions']['misses'][$key] += $value;
  142.             }
  143.         }
  144.         $this->data['entities'] = $entities;
  145.         $this->data['errors']   = $errors;
  146.         $this->data['caches']   = $caches;
  147.         $this->groupedQueries   null;
  148.     }
  149.     /** @return array<string, array<class-string, array{class: class-string, file: false|string, line: false|int}>> */
  150.     public function getEntities()
  151.     {
  152.         return $this->data['entities'];
  153.     }
  154.     /** @return array<string, array<string, list<string>>> */
  155.     public function getMappingErrors()
  156.     {
  157.         return $this->data['errors'];
  158.     }
  159.     /** @return int */
  160.     public function getCacheHitsCount()
  161.     {
  162.         return $this->data['caches']['counts']['hits'];
  163.     }
  164.     /** @return int */
  165.     public function getCachePutsCount()
  166.     {
  167.         return $this->data['caches']['counts']['puts'];
  168.     }
  169.     /** @return int */
  170.     public function getCacheMissesCount()
  171.     {
  172.         return $this->data['caches']['counts']['misses'];
  173.     }
  174.     /** @return bool */
  175.     public function getCacheEnabled()
  176.     {
  177.         return $this->data['caches']['enabled'];
  178.     }
  179.     /**
  180.      * @return array<string, array<string, int>>
  181.      * @psalm-return array<"puts"|"hits"|"misses", array<string, int>>
  182.      */
  183.     public function getCacheRegions()
  184.     {
  185.         return $this->data['caches']['regions'];
  186.     }
  187.     /** @return array<string, int> */
  188.     public function getCacheCounts()
  189.     {
  190.         return $this->data['caches']['counts'];
  191.     }
  192.     /** @return int */
  193.     public function getInvalidEntityCount()
  194.     {
  195.         return $this->invalidEntityCount ??= array_sum(array_map('count'$this->data['errors']));
  196.     }
  197.     /**
  198.      * @return string[][]
  199.      * @psalm-return array<string, list<QueryType&array{count: int, index: int, executionPercent?: float}>>
  200.      */
  201.     public function getGroupedQueries()
  202.     {
  203.         if ($this->groupedQueries !== null) {
  204.             return $this->groupedQueries;
  205.         }
  206.         $this->groupedQueries = [];
  207.         $totalExecutionMS     0;
  208.         foreach ($this->data['queries'] as $connection => $queries) {
  209.             $connectionGroupedQueries = [];
  210.             foreach ($queries as $i => $query) {
  211.                 $key $query['sql'];
  212.                 if (! isset($connectionGroupedQueries[$key])) {
  213.                     $connectionGroupedQueries[$key]                = $query;
  214.                     $connectionGroupedQueries[$key]['executionMS'] = 0;
  215.                     $connectionGroupedQueries[$key]['count']       = 0;
  216.                     $connectionGroupedQueries[$key]['index']       = $i// "Explain query" relies on query index in 'queries'.
  217.                 }
  218.                 $connectionGroupedQueries[$key]['executionMS'] += $query['executionMS'];
  219.                 $connectionGroupedQueries[$key]['count']++;
  220.                 $totalExecutionMS += $query['executionMS'];
  221.             }
  222.             usort($connectionGroupedQueries, static function ($a$b) {
  223.                 if ($a['executionMS'] === $b['executionMS']) {
  224.                     return 0;
  225.                 }
  226.                 return $a['executionMS'] < $b['executionMS'] ? : -1;
  227.             });
  228.             $this->groupedQueries[$connection] = $connectionGroupedQueries;
  229.         }
  230.         foreach ($this->groupedQueries as $connection => $queries) {
  231.             foreach ($queries as $i => $query) {
  232.                 $this->groupedQueries[$connection][$i]['executionPercent'] =
  233.                     $this->executionTimePercentage($query['executionMS'], $totalExecutionMS);
  234.             }
  235.         }
  236.         return $this->groupedQueries;
  237.     }
  238.     private function executionTimePercentage(float $executionTimeMSfloat $totalExecutionTimeMS): float
  239.     {
  240.         if (! $totalExecutionTimeMS) {
  241.             return 0;
  242.         }
  243.         return $executionTimeMS $totalExecutionTimeMS 100;
  244.     }
  245.     /** @return int */
  246.     public function getGroupedQueryCount()
  247.     {
  248.         $count 0;
  249.         foreach ($this->getGroupedQueries() as $connectionGroupedQueries) {
  250.             $count += count($connectionGroupedQueries);
  251.         }
  252.         return $count;
  253.     }
  254. }