Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
80.00% |
8 / 10 |
CRAP | |
91.89% |
68 / 74 |
ViewsData | |
0.00% |
0 / 1 |
|
80.00% |
8 / 10 |
39.81 | |
91.89% |
68 / 74 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
6 / 6 |
|||
get | |
100.00% |
1 / 1 |
8 | |
100.00% |
20 / 20 |
|||
cacheGet | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
cacheSet | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
prepareCid | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getData | |
100.00% |
1 / 1 |
6 | |
100.00% |
15 / 15 |
|||
processEntityTypes | |
0.00% |
0 / 1 |
9.29 | |
44.44% |
4 / 9 |
|||
fetchBaseTables | |
100.00% |
1 / 1 |
9 | |
100.00% |
7 / 7 |
|||
anonymous function | |
100.00% |
1 / 1 |
5 | |
100.00% |
6 / 6 |
|||
clear | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\views\ViewsData. | |
*/ | |
namespace Drupal\views; | |
use Drupal\Component\Utility\NestedArray; | |
use Drupal\Core\Cache\Cache; | |
use Drupal\Core\Cache\CacheBackendInterface; | |
use Drupal\Core\Config\ConfigFactoryInterface; | |
use Drupal\Core\Extension\ModuleHandlerInterface; | |
use Drupal\Core\Language\LanguageManagerInterface; | |
/** | |
* Class to manage and lazy load cached views data. | |
* | |
* If a table is requested and cannot be loaded from cache, all data is then | |
* requested from cache. A table-specific cache entry will then be created for | |
* the requested table based on this cached data. Table data is only rebuilt | |
* when no cache entry for all table data can be retrieved. | |
*/ | |
class ViewsData { | |
/** | |
* The base cache ID to use. | |
* | |
* @var string | |
*/ | |
protected $baseCid = 'views_data'; | |
/** | |
* The cache backend to use. | |
* | |
* @var \Drupal\Core\Cache\CacheBackendInterface | |
*/ | |
protected $cacheBackend; | |
/** | |
* Table data storage. | |
* | |
* This is used for explicitly requested tables. | |
* | |
* @var array | |
*/ | |
protected $storage = array(); | |
/** | |
* All table storage data loaded from cache. | |
* | |
* This is used when all data has been loaded from the cache to prevent | |
* further cache get calls when rebuilding all data or for single tables. | |
* | |
* @var array | |
*/ | |
protected $allStorage = array(); | |
/** | |
* Whether the data has been fully loaded in this request. | |
* | |
* @var bool | |
*/ | |
protected $fullyLoaded = FALSE; | |
/** | |
* Whether or not to skip data caching and rebuild data each time. | |
* | |
* @var bool | |
*/ | |
protected $skipCache = FALSE; | |
/** | |
* The current language code. | |
* | |
* @var string | |
*/ | |
protected $langcode; | |
/** | |
* Stores a module manager to invoke hooks. | |
* | |
* @var \Drupal\Core\Extension\ModuleHandlerInterface | |
*/ | |
protected $moduleHandler; | |
/** | |
* The language manager | |
* | |
* @var \Drupal\Core\Language\LanguageManagerInterface | |
*/ | |
protected $languageManager; | |
/** | |
* Constructs this ViewsData object. | |
* | |
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend | |
* The cache backend to use. | |
* @param \Drupal\Core\Config\ConfigFactoryInterface $config | |
* The configuration factory object to use. | |
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | |
* The module handler class to use for invoking hooks. | |
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager | |
* The language manager. | |
*/ | |
public function __construct(CacheBackendInterface $cache_backend, ConfigFactoryInterface $config, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) { | |
$this->cacheBackend = $cache_backend; | |
$this->moduleHandler = $module_handler; | |
$this->languageManager = $language_manager; | |
$this->langcode = $this->languageManager->getCurrentLanguage()->getId(); | |
$this->skipCache = $config->get('views.settings')->get('skip_cache'); | |
} | |
/** | |
* Gets data for a particular table, or all tables. | |
* | |
* @param string|null $key | |
* The key of the cache entry to retrieve. Defaults to NULL, this will | |
* return all table data. | |
* | |
* @return array $data | |
* An array of table data. | |
*/ | |
public function get($key = NULL) { | |
if ($key) { | |
if (!isset($this->storage[$key])) { | |
// Prepare a cache ID for get and set. | |
$cid = $this->baseCid . ':' . $key; | |
$from_cache = FALSE; | |
if ($data = $this->cacheGet($cid)) { | |
$this->storage[$key] = $data->data; | |
$from_cache = TRUE; | |
} | |
// If there is no cached entry and data is not already fully loaded, | |
// rebuild. This will stop requests for invalid tables calling getData. | |
elseif (!$this->fullyLoaded) { | |
$this->allStorage = $this->getData(); | |
} | |
if (!$from_cache) { | |
if (!isset($this->allStorage[$key])) { | |
// Write an empty cache entry if no information for that table | |
// exists to avoid repeated cache get calls for this table and | |
// prevent loading all tables unnecessarily. | |
$this->storage[$key] = array(); | |
$this->allStorage[$key] = array(); | |
} | |
else { | |
$this->storage[$key] = $this->allStorage[$key]; | |
} | |
// Create a cache entry for the requested table. | |
$this->cacheSet($cid, $this->allStorage[$key]); | |
} | |
} | |
return $this->storage[$key]; | |
} | |
else { | |
if (!$this->fullyLoaded) { | |
$this->allStorage = $this->getData(); | |
} | |
// Set storage from allStorage outside of the fullyLoaded check to prevent | |
// cache calls on requests that have requested all data to get a single | |
// tables data. Make sure $this->storage is populated in this case. | |
$this->storage = $this->allStorage; | |
} | |
return $this->allStorage; | |
} | |
/** | |
* Gets data from the cache backend. | |
* | |
* @param string $cid | |
* The cache ID to return. | |
* | |
* @return mixed | |
* The cached data, if any. This will immediately return FALSE if the | |
* $skipCache property is TRUE. | |
*/ | |
protected function cacheGet($cid) { | |
if ($this->skipCache) { | |
return FALSE; | |
} | |
return $this->cacheBackend->get($this->prepareCid($cid)); | |
} | |
/** | |
* Sets data to the cache backend. | |
* | |
* @param string $cid | |
* The cache ID to set. | |
* @param mixed $data | |
* The data that will be cached. | |
*/ | |
protected function cacheSet($cid, $data) { | |
return $this->cacheBackend->set($this->prepareCid($cid), $data, Cache::PERMANENT, array('views_data', 'config:core.extension')); | |
} | |
/** | |
* Prepares the cache ID by appending a language code. | |
* | |
* @param string $cid | |
* The cache ID to prepare. | |
* | |
* @return string | |
* The prepared cache ID. | |
*/ | |
protected function prepareCid($cid) { | |
return $cid . ':' . $this->langcode; | |
} | |
/** | |
* Gets all data invoked by hook_views_data(). | |
* | |
* This is requested from the cache before being rebuilt. | |
* | |
* @return array | |
* An array of all data. | |
*/ | |
protected function getData() { | |
$this->fullyLoaded = TRUE; | |
if ($data = $this->cacheGet($this->baseCid)) { | |
return $data->data; | |
} | |
else { | |
$modules = $this->moduleHandler->getImplementations('views_data'); | |
$data = []; | |
foreach ($modules as $module) { | |
$views_data = $this->moduleHandler->invoke($module, 'views_data'); | |
// Set the provider key for each base table. | |
foreach ($views_data as &$table) { | |
if (isset($table['table']) && !isset($table['table']['provider'])) { | |
$table['table']['provider'] = $module; | |
} | |
} | |
$data = NestedArray::mergeDeep($data, $views_data); | |
} | |
$this->moduleHandler->alter('views_data', $data); | |
$this->processEntityTypes($data); | |
// Keep a record with all data. | |
$this->cacheSet($this->baseCid, $data); | |
return $data; | |
} | |
} | |
/** | |
* Links tables with 'entity type' to respective generic entity-type tables. | |
* | |
* @param array $data | |
* The array of data to alter entity data for, passed by reference. | |
*/ | |
protected function processEntityTypes(array &$data) { | |
foreach ($data as $table_name => $table_info) { | |
// Add in a join from the entity-table if an entity-type is given. | |
if (!empty($table_info['table']['entity type'])) { | |
$entity_table = 'views_entity_' . $table_info['table']['entity type']; | |
$data[$entity_table]['table']['join'][$table_name] = array( | |
'left_table' => $table_name, | |
); | |
$data[$entity_table]['table']['entity type'] = $table_info['table']['entity type']; | |
// Copy over the default table group if we have none yet. | |
if (!empty($table_info['table']['group']) && empty($data[$entity_table]['table']['group'])) { | |
$data[$entity_table]['table']['group'] = $table_info['table']['group']; | |
} | |
} | |
} | |
} | |
/** | |
* Fetches a list of all base tables available. | |
* | |
* @return array | |
* An array of base table data keyed by table name. Each item contains the | |
* following keys: | |
* - title: The title label for the base table. | |
* - help: The help text for the base table. | |
* - weight: The weight of the base table. | |
*/ | |
public function fetchBaseTables() { | |
$tables = array(); | |
foreach ($this->get() as $table => $info) { | |
if (!empty($info['table']['base'])) { | |
$tables[$table] = array( | |
'title' => $info['table']['base']['title'], | |
'help' => !empty($info['table']['base']['help']) ? $info['table']['base']['help'] : '', | |
'weight' => !empty($info['table']['base']['weight']) ? $info['table']['base']['weight'] : 0, | |
); | |
} | |
} | |
// Sorts by the 'weight' and then by 'title' element. | |
uasort($tables, function ($a, $b) { | |
if ($a['weight'] != $b['weight']) { | |
return $a['weight'] < $b['weight'] ? -1 : 1; | |
} | |
if ($a['title'] != $b['title']) { | |
return $a['title'] < $b['title'] ? -1 : 1; | |
} | |
return 0; | |
}); | |
return $tables; | |
} | |
/** | |
* Clears the class storage and cache. | |
*/ | |
public function clear() { | |
$this->storage = array(); | |
$this->allStorage = array(); | |
$this->fullyLoaded = FALSE; | |
Cache::invalidateTags(array('views_data')); | |
} | |
} |