webprofiler-10.0.x-dev/src/DataCollector/ThemeDataCollector.php

src/DataCollector/ThemeDataCollector.php
<?php

declare(strict_types=1);

namespace Drupal\webprofiler\DataCollector;

use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\Component;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Drupal\webprofiler\Theme\ThemeNegotiatorWrapper;
use League\CommonMark\CommonMarkConverter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Twig\Markup;
use Twig\Profiler\Dumper\HtmlDumper;
use Twig\Profiler\Profile;
use Twig\TwigFilter;
use Twig\TwigFunction;

/**
 * Collects theme data.
 */
class ThemeDataCollector extends DataCollector implements HasPanelInterface, LateDataCollectorInterface {

  use StringTranslationTrait, DataCollectorTrait, PanelTrait;

  /**
   * Used to store twig computed data between method calls.
   *
   * @var array|null
   */
  private ?array $computed = NULL;

  /**
   * The twig profile.
   *
   * @var \Twig\Profiler\Profile|null
   */
  private ?Profile $profile = NULL;

  /**
   * ThemeDataCollector constructor.
   *
   * @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
   *   The theme manager.
   * @param \Drupal\Core\Theme\ThemeNegotiatorInterface $themeNegotiator
   *   The theme negotiator.
   * @param \Drupal\Core\Template\TwigEnvironment $twig
   *   The Twig service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   The module handler.
   * @param \Twig\Profiler\Profile $profile
   *   The twig profile.
   */
  public function __construct(
    private readonly ThemeManagerInterface $themeManager,
    private readonly ThemeNegotiatorInterface $themeNegotiator,
    private readonly TwigEnvironment $twig,
    public ModuleHandlerInterface $moduleHandler,
    Profile $profile,
  ) {
    $this->profile = $profile;
    $this->data['components'] = [];
  }

  /**
   * {@inheritdoc}
   */
  public function getName(): string {
    return 'theme';
  }

  /**
   * Reset the collected data.
   */
  public function reset(): void {
    $this->data = [];
  }

  /**
   * {@inheritdoc}
   */
  public function collect(Request $request, Response $response, ?\Throwable $exception = NULL): void {
    $activeTheme = $this->themeManager->getActiveTheme();

    $this->data['activeTheme'] = [
      'name' => $activeTheme->getName(),
      'path' => $activeTheme->getPath(),
      'engine' => $activeTheme->getEngine(),
      'owner' => $activeTheme->getOwner(),
      'baseThemes' => $activeTheme->getBaseThemeExtensions(),
      'extension' => $activeTheme->getExtension(),
      'librariesOverride' => $activeTheme->getLibrariesOverride(),
      'librariesExtend' => $activeTheme->getLibrariesExtend(),
      'libraries' => $activeTheme->getLibraries(),
      'regions' => $activeTheme->getRegions(),
    ];

    $this->data['twig_extensions'] = [
      'filters' => \array_map(function (TwigFilter $filter) {
        return [
          'name' => $filter->getName(),
          'callable' => $this->getTwigCallableContext($filter->getCallable()),
        ];
      }, $this->twig->getFilters()),
      'functions' => \array_map(function (TwigFunction $function) {
        return [
          'name' => $function->getName(),
          'callable' => $this->getTwigCallableContext($function->getCallable()),
        ];
      }, $this->twig->getFunctions()),
      'globals' => $this->twig->getGlobals(),
    ];

    if ($this->themeNegotiator instanceof ThemeNegotiatorWrapper) {
      $theme_negotiator = $this->themeNegotiator->getNegotiator();

      if ($theme_negotiator != NULL) {
        $this->data['negotiator'] = [
          'class' => $this->getMethodData($theme_negotiator, 'determineActiveTheme'),
        ];
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function lateCollect(): void {
    $this->data['twig'] = \serialize($this->profile);
  }

  /**
   * Add an SDC component to the data collector.
   *
   * @param \Drupal\Core\Plugin\Component $component
   *   The SDC component to add.
   */
  public function addComponent(Component $component): void {
    if (!isset($this->data['components'][$component->getPluginId()])) {
      $converter = new CommonMarkConverter();

      $this->data['components'][$component->getPluginId()] = [
        'name' => $component->metadata->name,
        'status' => $component->metadata->status,
        'path' => $component->metadata->path,
        'documentation' => $converter->convert($component->metadata->documentation),
        'group' => $component->metadata->group,
        'thumbnail' => $component->metadata->getThumbnailPath(),
        'plugin_id' => $component->getPluginId(),
        'provider' => $component->getPluginDefinition()['provider'],
        'library' => $component->library,
        'props' => $component->getPluginDefinition()['props'] ?? [],
        'slots' => $component->getPluginDefinition()['slots'] ?? [],
        'count' => 1,
      ];
    }
    else {
      $this->data['components'][$component->getPluginId()]['count']++;
    }
  }

  /**
   * Return the active theme.
   *
   * @return array
   *   The active theme.
   */
  public function getActiveTheme(): array {
    return $this->data['activeTheme'];
  }

  /**
   * Return the theme negotiator.
   *
   * @return array
   *   The theme negotiator.
   */
  public function getThemeNegotiator(): array {
    return $this->data['negotiator'];
  }

  /**
   * Return the time spent by the twig rendering process, in seconds.
   *
   * @return float
   *   The time spent by the twig rendering process, in seconds.
   */
  public function getTime(): float {
    return $this->getProfile()->getDuration() * 1000;
  }

  /**
   * Return the number of twig templates rendered.
   *
   * @return int
   *   The number of twig templates rendered.
   */
  public function getTemplateCount(): int {
    return $this->getComputedData('template_count');
  }

  /**
   * Return the number of twig blocks rendered.
   *
   * @return int
   *   The number of twig blocks rendered.
   */
  public function getBlockCount(): int {
    return $this->getComputedData('block_count');
  }

  /**
   * Return the number of twig macros rendered.
   *
   * @return int
   *   The number of twig macros rendered.
   */
  public function getMacroCount(): int {
    return $this->getComputedData('macro_count');
  }

  /**
   * Return the number of twig filters.
   *
   * @return int
   *   The number of twig filters.
   */
  public function getTwigFiltersCount(): int {
    return \count($this->data['twig_extensions']['filters']);
  }

  /**
   * Return the number of twig functions.
   *
   * @return int
   *   The number of twig functions.
   */
  public function getTwigFunctionsCount(): int {
    return \count($this->data['twig_extensions']['functions']);
  }

  /**
   * Return TRUE if the SDC module is enabled.
   *
   * @return bool
   *   TRUE if the SDC module is enabled.
   */
  public function hasComponentsModule(): bool {
    return $this->moduleHandler->moduleExists('sdc');
  }

  /**
   * Return the number of SDC components.
   *
   * @return int
   *   The number of SDC components.
   */
  public function getComponentsCount(): int {
    return \count($this->data['components']);
  }

  /**
   * {@inheritdoc}
   */
  public function getPanel(): array {
    $filters = $this->data['twig_extensions']['filters'];
    \ksort($filters);

    $functions = $this->data['twig_extensions']['functions'];
    \ksort($functions);

    $tabs = [];

    $tabs[] = [
      'label' => 'Theme data',
      'content' => [
        '#type' => 'inline_template',
        '#template' => '{{ data|raw }}',
        '#context' => [
          'data' => $this->dumpData($this->cloneVar($this->data['activeTheme'])),
        ],
      ],
    ];

    $tabs[] = [
      'label' => 'Twig filters',
      'content' => [
        '#theme' => 'webprofiler_dashboard_section',
        '#data' => [
          '#type' => 'table',
          '#header' => [
            $this->t('Name'),
            $this->t('Callable'),
          ],
          '#rows' => $filters,
          '#attributes' => [
            'class' => [
              'webprofiler__table',
            ],
          ],
          '#sticky' => TRUE,
        ],
      ],
    ];

    $tabs[] = [
      'label' => 'Twig functions',
      'content' => [
        '#theme' => 'webprofiler_dashboard_section',
        '#data' => [
          '#type' => 'table',
          '#header' => [
            $this->t('Name'),
            $this->t('Callable'),
          ],
          '#rows' => $functions,
          '#attributes' => [
            'class' => [
              'webprofiler__table',
            ],
          ],
          '#sticky' => TRUE,
        ],
      ],
    ];

    $tabs[] = [
      'label' => 'Twig globals',
      'content' => [
        '#theme' => 'webprofiler_dashboard_section',
        '#data' => [
          '#type' => 'table',
          '#header' => [
            $this->t('Name'),
            $this->t('Callable'),
          ],
          '#rows' => $this->data['twig_extensions']['globals'],
          '#attributes' => [
            'class' => [
              'webprofiler__table',
            ],
          ],
          '#sticky' => TRUE,
        ],
      ],
    ];

    $tabs[] = [
      'label' => 'Rendering Call Graph',
      'content' => [
        '#theme' => 'webprofiler_dashboard_section',
        '#data' => [
          '#type' => 'inline_template',
          '#template' => '<div id="twig-dump">{{ data|raw }}</div>',
          '#context' => [
            'data' => (string) $this->getHtmlCallGraph(),
          ],
        ],
      ],
    ];

    if ($this->moduleHandler->moduleExists('sdc')) {
      $tabs[] = [
        'label' => 'Components',
        'content' => $this->renderComponents($this->data['components']),
      ];
    }

    return [
      '#theme' => 'webprofiler_dashboard_tabs',
      '#tabs' => $tabs,
    ];
  }

  /**
   * Render the twig call graph.
   *
   * @return \Twig\Markup
   *   The twig call graph.
   */
  private function getHtmlCallGraph(): Markup {
    $dumper = new HtmlDumper();

    return new Markup($dumper->dump($this->getProfile()), 'UTF-8');
  }

  /**
   * Render the SDC components.
   *
   * @param array $components
   *   The SDC components.
   *
   * @return array
   *   The render array of the SDC components.
   */
  private function renderComponents(array $components): array {
    return [
      '#theme' => 'webprofiler_dashboard_components',
      '#components' => $components,
    ];
  }

  /**
   * Return the twig profile, deserialized from data, if needed.
   *
   * @return \Twig\Profiler\Profile
   *   The twig profile, deserialized from data, if needed.
   */
  private function getProfile(): Profile {
    return $this->profile ??= \unserialize(
      $this->data['twig'],
      ['allowed_classes' => ['\Twig\Profiler\Profile', Profile::class]],
    );
  }

  /**
   * Return a specific computed data.
   *
   * @param string $index
   *   The index of the data to return.
   *
   * @return mixed
   *   The computed data.
   */
  private function getComputedData(string $index): mixed {
    $this->computed ??= $this->computeData($this->getProfile());

    return $this->computed[$index];
  }

  /**
   * Compute the data from the twig profile.
   *
   * @param \Twig\Profiler\Profile $profile
   *   The twig profile.
   *
   * @return array
   *   The computed data.
   */
  private function computeData(Profile $profile): array {
    $data = [
      'template_count' => 0,
      'block_count' => 0,
      'macro_count' => 0,
    ];

    $templates = [];
    foreach ($profile as $p) {
      $d = $this->computeData($p);

      $data['template_count'] += ($p->isTemplate() ? 1 : 0) + $d['template_count'];
      $data['block_count'] += ($p->isBlock() ? 1 : 0) + $d['block_count'];
      $data['macro_count'] += ($p->isMacro() ? 1 : 0) + $d['macro_count'];

      if ($p->isTemplate()) {
        if (!isset($templates[$p->getTemplate()])) {
          $templates[$p->getTemplate()] = 1;
        }
        else {
          $templates[$p->getTemplate()]++;
        }
      }

      foreach ($d['templates'] as $template => $count) {
        if (!isset($templates[$template])) {
          $templates[$template] = $count;
        }
        else {
          $templates[$template] += $count;
        }
      }
    }
    $data['templates'] = $templates;

    return $data;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc