vendor/doctrine/doctrine-bundle/DataCollector/DoctrineDataCollector.php line 77

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