class DashboardController in Purge 8.3
Configuration dashboard for configuring the cache invalidation pipeline.
Hierarchy
- class \Drupal\Core\Controller\ControllerBase implements ContainerInjectionInterface uses LoggerChannelTrait, MessengerTrait, LinkGeneratorTrait, RedirectDestinationTrait, UrlGeneratorTrait, StringTranslationTrait
- class \Drupal\purge_ui\Controller\DashboardController
Expanded class hierarchy of DashboardController
File
- modules/
purge_ui/ src/ Controller/ DashboardController.php, line 21
Namespace
Drupal\purge_ui\ControllerView source
class DashboardController extends ControllerBase {
/**
* The 'purge.diagnostics' service.
*
* @var \Drupal\purge\Plugin\Purge\DiagnosticCheck\DiagnosticsServiceInterface
*/
protected $purgeDiagnostics;
/**
* The 'purge.invalidation.factory' service.
*
* @var \Drupal\purge\Plugin\Purge\Invalidation\InvalidationsServiceInterface
*/
protected $purgeInvalidationFactory;
/**
* The 'purge.processors' service.
*
* @var \Drupal\purge\Plugin\Purge\Processor\ProcessorsServiceInterface
*/
protected $purgeProcessors;
/**
* The 'purge.purgers' service.
*
* @var \Drupal\purge\Plugin\Purge\Purger\PurgersServiceInterface
*/
protected $purgePurgers;
/**
* The 'purge.queue' service.
*
* @var \Drupal\purge\Plugin\Purge\Queue\QueueServiceInterface
*/
protected $purgeQueue;
/**
* The 'purge.queuers' service.
*
* @var \Drupal\purge\Plugin\Purge\Queuer\QueuersServiceInterface
*/
protected $purgeQueuers;
/**
* The current request from the request stack.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Central listing of code-used aliases and the routes we open modals for.
*
* @var string[]
*/
protected $routes = [
'logging' => 'purge_ui.logging_config_form',
'purger_add' => 'purge_ui.purger_add_form',
'purger_detail' => 'purge_ui.purger_detail_form',
'purger_config' => 'purge_ui.purger_config_form',
'purger_configd' => 'purge_ui.purger_config_dialog_form',
'purger_delete' => 'purge_ui.purger_delete_form',
'purger_up' => 'purge_ui.purger_move_up_form',
'purger_down' => 'purge_ui.purger_move_down_form',
'processor_add' => 'purge_ui.processor_add_form',
'processor_detail' => 'purge_ui.processor_detail_form',
'processor_config' => 'purge_ui.processor_config_form',
'processor_configd' => 'purge_ui.processor_config_dialog_form',
'processor_delete' => 'purge_ui.processor_delete_form',
'queuer_add' => 'purge_ui.queuer_add_form',
'queuer_detail' => 'purge_ui.queuer_detail_form',
'queuer_config' => 'purge_ui.queuer_config_form',
'queuer_configd' => 'purge_ui.queuer_config_dialog_form',
'queuer_delete' => 'purge_ui.queuer_delete_form',
'queue_detail' => 'purge_ui.queue_detail_form',
'queue_change' => 'purge_ui.queue_change_form',
'queue_browser' => 'purge_ui.queue_browser_form',
'queue_empty' => 'purge_ui.queue_empty_form',
];
/**
* Construct a DashboardController object.
*
* @param \Drupal\purge\Plugin\Purge\DiagnosticCheck\DiagnosticsServiceInterface $purge_diagnostics
* Diagnostics service that reports any preliminary issues regarding purge.
* @param \Drupal\purge\Plugin\Purge\Invalidation\InvalidationsServiceInterface $purge_invalidation_factory
* The invalidation objects factory service.
* @param \Drupal\purge\Plugin\Purge\Processor\ProcessorsServiceInterface $purge_processors
* The purge processors service.
* @param \Drupal\purge\Plugin\Purge\Purger\PurgersServiceInterface $purge_purgers
* The purgers service.
* @param \Drupal\purge\Plugin\Purge\Queue\QueueServiceInterface $purge_queue
* The purge queue service.
* @param \Drupal\purge\Plugin\Purge\Queuer\QueuersServiceInterface $purge_queuers
* The purge queuers service.
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request from the request stack.
*/
public final function __construct(DiagnosticsServiceInterface $purge_diagnostics, InvalidationsServiceInterface $purge_invalidation_factory, ProcessorsServiceInterface $purge_processors, PurgersServiceInterface $purge_purgers, QueueServiceInterface $purge_queue, QueuersServiceInterface $purge_queuers, Request $request) {
$this->purgeDiagnostics = $purge_diagnostics;
$this->purgeInvalidationFactory = $purge_invalidation_factory;
$this->purgeProcessors = $purge_processors;
$this->purgePurgers = $purge_purgers;
$this->purgeQueue = $purge_queue;
$this->purgeQueuers = $purge_queuers;
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container
->get('purge.diagnostics'), $container
->get('purge.invalidation.factory'), $container
->get('purge.processors'), $container
->get('purge.purgers'), $container
->get('purge.queue'), $container
->get('purge.queuers'), $container
->get('request_stack')
->getCurrentRequest());
}
/**
* Build all dashboard sections.
*
* @return array
* The render array.
*/
public function build() {
$build = [
'#theme' => [
'purge_ui_dashboard',
],
'#attached' => [
'library' => [
'purge_ui/dashboard',
],
],
];
$build['info'] = [
'#type' => 'item',
'#markup' => $this
->t('When content on your website changes, your purge setup will take care of refreshing external caching systems and CDNs.'),
];
$build['logging'] = $this
->buildLoggingSection();
$build['diagnostics'] = $this
->buildDiagnosticReport();
$build['purgers'] = $this
->buildPurgers();
$build['queue'] = $this
->buildQueuersQueueProcessors();
return $build;
}
/**
* Add a section devoted to log configuration.
*
* @return array
* The render array.
*/
protected function buildLoggingSection() {
extract($this
->getRenderLocals());
// phpcs:disable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable -- PHP's extract() isn't understood by this sniffer..
$build = $details($this
->t('Logging'));
$build['#open'] = $this->request
->get('edit-logging', FALSE);
$build['configure'] = $buttonlink($this
->t("Configure logging behavior"), 'logging', '90%');
// phpcs:enable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable -- PHP's extract() isn't understood by this sniffer..
return $build;
}
/**
* Add a visual report on the current state of the purge module.
*
* @return array
* The render array.
*/
protected function buildDiagnosticReport() {
extract($this
->getRenderLocals());
// phpcs:disable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable -- PHP's extract() isn't understood by this sniffer..
$build = $fieldset($this
->t('Status'));
$build['report'] = [
'#theme' => 'purge_ui_diagnostics',
'#diagnostics' => $this->purgeDiagnostics
->toRequirementsArray($this->purgeDiagnostics),
];
// phpcs:enable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable
return $build;
}
/**
* Manage purgers and visualize the types they support.
*
* @return array
* The render array.
*/
protected function buildPurgers() {
extract($this
->getRenderLocals());
// phpcs:disable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable -- PHP's extract() isn't understood by this sniffer..
$build = $details($this
->t('Cache Invalidation'));
$build['#description'] = $p($this
->t("Each layer of caching on top of your site is cleared by a purger. Purgers are provided by third-party modules and support one or more types of cache invalidation."));
$build['t'] = $table([
'layer' => $this
->t('Caching layer'),
]);
foreach ($this->purgeInvalidationFactory
->getPlugins() as $type) {
$label = $type['label'];
if (strlen($type['label']) > 4) {
$label = Unicode::truncate($type['label'], 1, FALSE);
}
$build['t']['#header'][$type['id']] = [
'data' => $label,
'title' => $this
->t('@type - @description', [
'@type' => $type['label'],
'@description' => $type['description'],
]),
'class' => [
in_array($type['id'], [
'tag',
'path',
'url',
]) ? RESPONSIVE_PRIORITY_MEDIUM : RESPONSIVE_PRIORITY_LOW,
],
];
}
// Visualize Drupal core as part of the cache invalidation onion.
$row_new($build['t'], '_drupal');
foreach ($build['t']['#header'] as $type => $definition) {
if ($type === 'layer') {
$row_set($build['t'], '_drupal', $type, $cell_markup($b($this
->t("Drupal Origin"))));
}
elseif ($type === 'tag') {
$checkmark = $cell_checked($this
->t('Supported'));
$checkmark['data']['#attributes']['supports'] = 'drupal-' . $type;
$row_set($build['t'], '_drupal', $type, $checkmark);
}
}
// Iterate the purgers and add controls and checkmarks.
$definitions = $this->purgePurgers
->getPlugins();
$types_by_purger = $this->purgePurgers
->getTypesByPurger();
$enabled = $this->purgePurgers
->getPluginsEnabled();
$rindex = 1;
foreach ($this->purgePurgers
->getLabels() as $id => $label) {
$row_new($build['t'], $id);
// Add checkmarks to visualize which purgers support invalidating what.
foreach ($build['t']['#header'] as $type => $definition) {
if (in_array($type, $types_by_purger[$id])) {
$checkmark = $cell_checked($this
->t('Supported'));
$checkmark['data']['#attributes']['supports'] = $id . '-' . $type;
$row_set($build['t'], $id, $type, $checkmark);
}
}
// Build the operation links from which users can select common actions.
$definition = $definitions[$enabled[$id]];
$ops = [];
$ops['detail'] = $button($label, [
'purger_detail',
'id' => $id,
]);
if (isset($definition['configform']) && !empty($definition['configform'])) {
$ops['configure'] = $button($this
->t("Configure"), [
'purger_configd',
'id' => $id,
]);
}
$ops['delete'] = $button($this
->t("Delete"), [
'purger_delete',
'id' => $id,
]);
if (count($enabled) !== 1) {
if ($rindex !== 1) {
$ops['up'] = $button($this
->t("Move up"), [
'purger_up',
'id' => $id,
]);
}
if ($rindex !== count($enabled)) {
$ops['down'] = $button($this
->t("Move down"), [
'purger_down',
'id' => $id,
]);
}
}
// Render the operation links into the 'layer' cell.
$row_set($build['t'], $id, 'layer', $cell_ops($ops));
$rindex++;
// Add another visual layer if the purger comes with a cooldown time.
if ($sec = $this->purgePurgers
->capacityTracker()
->getCooldownTime($id)) {
$row_new($build['t'], $id . '_wait');
$row_set($build['t'], $id . '_wait', 'layer', $cell_markup($i($this
->t("@sec seconds cooldown", [
'@sec' => $sec,
]))));
}
}
// Add two rows - the first is just spacing - with a button to add purgers.
$row_new($build['t'], '_add');
if (count($this->purgePurgers
->getPluginsAvailable())) {
$row_set($build['t'], '_add', 'layer', $cell_ops([
$button($this
->t("Add purger"), 'purger_add'),
]));
}
elseif (!count($this->purgePurgers
->getPluginsEnabled())) {
$row_set($build['t'], '_add', 'layer', $cell_markup($b($this
->t("Please install a module to add at least one purger."))));
}
// Add a row that visualizes www as part of the cache invalidation onion.
$row_new($build['t'], '_www');
$row_set($build['t'], '_www', 'layer', $cell_markup($b($this
->t("Public Endpoint"))));
// phpcs:enable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable
return $build;
}
/**
* Manage queuers, the queue itself and processors.
*
* @return array
* The render array.
*/
protected function buildQueuersQueueProcessors() {
extract($this
->getRenderLocals());
// phpcs:disable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable -- PHP's extract() isn't understood by this sniffer..
$build = $details($this
->t('Queue'));
$build['#description'] = $p($this
->t("The queue holds items that need refreshing, hold your mouse over the column titles for more details."));
$build['#open'] = $this->request
->get('edit-queue', FALSE) || !count($this->purgeQueuers) || !count($this->purgeProcessors);
$build['t'] = $table([
'queuers' => [
'data' => $this
->t('Queuers'),
'title' => $this
->t('Queuers add items to the queue upon certain events, that processors process later on.'),
],
'queue' => [
'data' => $this
->t('Queue'),
'title' => $this
->t("The queue holds 'invalidation items', which instruct what needs to be invalidated from external caches."),
],
'processors' => [
'data' => $this
->t('Processors'),
'title' => $this
->t('Processors are responsible for emptying the queue and putting the purgers to work each time they process. Processors can work the queue constantly or at timed intervals, it is up to you to configure a policy that makes sense for the traffic nature of your website. Multiple processors will not lead to any parallel-processing or conflicts, instead it simply means the queue is checked more often.'),
],
]);
// Build vertical columns for the queuers, queue and processors.
$cols = [];
$cols['queuers'] = [];
foreach ($this->purgeQueuers as $queuer) {
$definition = $queuer
->getPluginDefinition();
$id = $queuer
->getPluginId();
$ops = [];
$ops['detail'] = $button($queuer
->getLabel(), [
'queuer_detail',
'id' => $id,
]);
if (isset($definition['configform']) && !empty($definition['configform'])) {
$ops['configure'] = $button($this
->t("Configure"), [
'queuer_configd',
'id' => $id,
]);
}
$ops['delete'] = $button($this
->t("Delete"), [
'queuer_delete',
'id' => $id,
]);
$cols['queuers'][] = $cell_ops($ops);
}
$ops = [];
$ops['detail'] = $button($this->purgeQueue
->getLabel(), 'queue_detail');
$ops['browser'] = $button($this
->t('Inspect'), 'queue_browser', '900');
$ops['change'] = $button($this
->t('Change engine'), 'queue_change', '900');
$ops['empty'] = $button($this
->t('Empty'), 'queue_empty');
$cols['queue'][] = $cell_ops($ops);
$cols['processors'] = [];
foreach ($this->purgeProcessors as $processor) {
$definition = $processor
->getPluginDefinition();
$id = $processor
->getPluginId();
$ops = [];
$ops['detail'] = $button($processor
->getLabel(), [
'processor_detail',
'id' => $id,
]);
if (isset($definition['configform']) && !empty($definition['configform'])) {
$ops['configure'] = $button($this
->t("Configure"), [
'processor_configd',
'id' => $id,
]);
}
$ops['delete'] = $button($this
->t("Delete"), [
'processor_delete',
'id' => $id,
]);
$cols['processors'][] = $cell_ops($ops);
}
$col_equalize($cols);
// Add one last row with 'Add ...' buttons.
$col_equalize($cols, 1);
if (count($this->purgeQueuers
->getPluginsAvailable())) {
$cols['queuers'][] = $cell_ops([
$button($this
->t("Add queuer"), 'queuer_add'),
]);
}
elseif (!count($this->purgeQueuers)) {
$cols['queuers'][] = $cell_markup($b($this
->t("Please install a module to add at least one queuer.")));
}
if (count($this->purgeProcessors
->getPluginsAvailable())) {
$cols['processors'][] = $cell_ops([
$button($this
->t("Add processor"), 'processor_add'),
]);
}
elseif (!count($this->purgeProcessors)) {
$cols['processors'][] = $cell_markup($b($this
->t("Please install a module to add at least one processor.")));
}
$col_equalize($cols);
// Now transform the columns into table rows.
foreach ($cols as $col => $rows) {
foreach ($rows as $n => $row) {
if (!$row_isset($build['t'], $n)) {
$row_new($build['t'], $n, $cell());
}
$row_set($build['t'], $n, $col, $row);
}
}
// phpcs:enable DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable
return $build;
}
/**
* Helper for dealing with render arrays more easily.
*
* With complexer render arrays with many levels deep - especially in tables -
* it becomes much easier to read and write using this collection of template
* variables and closures that return common render array elements.
*
* The variables can be imported like this:
* @code
* extract($this->getRenderLocals());
* @endcode
*
* @return array
* The render array.
*/
protected function getRenderLocals() {
// phpcs:disable DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable -- PHP's extract() isn't understood by this sniffer..
$details = function ($title) {
return [
'#type' => 'details',
'#title' => $title,
'#open' => TRUE,
];
};
$fieldset = function ($title) {
return [
'#type' => 'fieldset',
'#title' => $title,
'#attributes' => [],
];
};
// Buttons and operation links.
$url = function ($route) {
if (is_array($route)) {
$args = $route;
$route = array_shift($args);
return Url::fromRoute($this->routes[$route], $args);
}
else {
return Url::fromRoute($this->routes[$route]);
}
};
$button = function ($title, $route, $width = '60%') use ($url) {
return [
'title' => $title,
'url' => $url($route),
'attributes' => [
'class' => [
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => $width,
]),
],
];
};
$buttonlink = function ($title, $route, $width = '60%') use ($url) {
return [
'#type' => 'link',
'#title' => $title,
'#url' => $url($route),
'#attributes' => [
'class' => [
'use-ajax',
'button',
'button--small',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => $width,
]),
],
];
};
// Table management.
$table = function ($header = []) {
return [
'#type' => 'table',
'#responsive' => TRUE,
'#header' => $header,
];
};
$cell = function ($cell = []) {
return [
'data' => $cell,
];
};
$cell_checked = function ($title = '') use ($cell) {
return $cell([
'#theme' => 'image',
'#width' => 18,
'#height' => 18,
'#uri' => 'core/misc/icons/73b355/check.svg',
'#alt' => $title,
'#title' => $title,
]);
};
$cell_markup = function ($markup) use ($cell) {
return $cell([
'#markup' => $markup,
]);
};
$cell_spacer = $cell_markup(' ');
$cell_ops = function ($links) use ($cell) {
return $cell([
'#type' => 'operations',
'#links' => $links,
]);
};
$col_equalize = function (&$cols, $extrarows = 0) use ($cell_spacer) {
$rowstotal = 1;
foreach ($cols as $col => $rows) {
if (($rowcount = count($rows)) > $rowstotal) {
$rowstotal = $rowcount;
}
}
$rowstotal = $rowstotal + $extrarows;
foreach ($cols as $col => $rows) {
if ($missing = $rowstotal - count($rows)) {
for ($i = 0; $i < $missing; $i++) {
$cols[$col][] = $cell_spacer;
}
}
}
};
$row_isset = function ($table, $row) {
return isset($table['#rows'][$row]);
};
$row_set = function (&$table, $row, $col, $value) {
$table['#rows'][$row]['data'][$col] = $value;
};
$row_new = function (&$table, $row) use ($cell_spacer, $row_set) {
foreach ($table['#header'] as $col => $definition) {
$row_set($table, $row, $col, $cell_spacer);
}
};
// Simple shorthand tag wrapping closures.
$tag = function ($tag, $content) {
return '<' . $tag . '>' . $content . '</' . $tag . '>';
};
$b = function ($content) use ($tag) {
return $tag('b', $content);
};
$i = function ($content) use ($tag) {
return $tag('i', $content);
};
$p = function ($content) use ($tag) {
return $tag('p', $content);
};
// Return locally defined variables so extract() can easily unpack.
// phpcs:enable DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
return get_defined_vars();
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ControllerBase:: |
protected | property | The configuration factory. | |
ControllerBase:: |
protected | property | The current user service. | 1 |
ControllerBase:: |
protected | property | The entity form builder. | |
ControllerBase:: |
protected | property | The entity manager. | |
ControllerBase:: |
protected | property | The entity type manager. | |
ControllerBase:: |
protected | property | The form builder. | 2 |
ControllerBase:: |
protected | property | The key-value storage. | 1 |
ControllerBase:: |
protected | property | The language manager. | 1 |
ControllerBase:: |
protected | property | The module handler. | 2 |
ControllerBase:: |
protected | property | The state service. | |
ControllerBase:: |
protected | function | Returns the requested cache bin. | |
ControllerBase:: |
protected | function | Retrieves a configuration object. | |
ControllerBase:: |
private | function | Returns the service container. | |
ControllerBase:: |
protected | function | Returns the current user. | 1 |
ControllerBase:: |
protected | function | Retrieves the entity form builder. | |
ControllerBase:: |
protected | function | Retrieves the entity manager service. | |
ControllerBase:: |
protected | function | Retrieves the entity type manager. | |
ControllerBase:: |
protected | function | Returns the form builder service. | 2 |
ControllerBase:: |
protected | function | Returns a key/value storage collection. | 1 |
ControllerBase:: |
protected | function | Returns the language manager service. | 1 |
ControllerBase:: |
protected | function | Returns the module handler. | 2 |
ControllerBase:: |
protected | function |
Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait:: |
|
ControllerBase:: |
protected | function | Returns the state storage service. | |
DashboardController:: |
protected | property | The 'purge.diagnostics' service. | |
DashboardController:: |
protected | property | The 'purge.invalidation.factory' service. | |
DashboardController:: |
protected | property | The 'purge.processors' service. | |
DashboardController:: |
protected | property | The 'purge.purgers' service. | |
DashboardController:: |
protected | property | The 'purge.queue' service. | |
DashboardController:: |
protected | property | The 'purge.queuers' service. | |
DashboardController:: |
protected | property | The current request from the request stack. | |
DashboardController:: |
protected | property | Central listing of code-used aliases and the routes we open modals for. | |
DashboardController:: |
public | function | Build all dashboard sections. | |
DashboardController:: |
protected | function | Add a visual report on the current state of the purge module. | |
DashboardController:: |
protected | function | Add a section devoted to log configuration. | |
DashboardController:: |
protected | function | Manage purgers and visualize the types they support. | |
DashboardController:: |
protected | function | Manage queuers, the queue itself and processors. | |
DashboardController:: |
public static | function |
Instantiates a new instance of this class. Overrides ControllerBase:: |
|
DashboardController:: |
protected | function | Helper for dealing with render arrays more easily. | |
DashboardController:: |
final public | function | Construct a DashboardController object. | |
LinkGeneratorTrait:: |
protected | property | The link generator. | 1 |
LinkGeneratorTrait:: |
protected | function | Returns the link generator. | |
LinkGeneratorTrait:: |
protected | function | Renders a link to a route given a route name and its parameters. | |
LinkGeneratorTrait:: |
public | function | Sets the link generator service. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
UrlGeneratorTrait:: |
protected | property | The url generator. | |
UrlGeneratorTrait:: |
protected | function | Returns the URL generator service. | |
UrlGeneratorTrait:: |
public | function | Sets the URL generator service. | |
UrlGeneratorTrait:: |
protected | function | Generates a URL or path for a specific route based on the given parameters. |