Commit 01567a63 authored by webchick's avatar webchick

Issue #1867782 by damiankloip, tim.plunkett: Replace _views_fetch_data() with cache class.

parent dc6c7a4b
......@@ -128,7 +128,7 @@ function get_base_table() {
$relationships = $this->view->display_handler->getOption('relationships');
if (!empty($relationships[$this->options['relationship']])) {
$options = $relationships[$this->options['relationship']];
$data = views_fetch_data($options['table']);
$data = drupal_container()->get('views.views_data')->get($options['table']);
$this->base_table = $data[$options['field']]['relationship']['base'];
}
}
......
......@@ -40,7 +40,7 @@ public function query() {
$this->ensureMyTable();
// First, relate our base table to the current base table to the
// field, using the base table's id field to the field's column.
$views_data = views_fetch_data($this->table);
$views_data = drupal_container()->get('views.views_data')->get($this->table);
$left_field = $views_data['table']['base']['field'];
$first = array(
......
......@@ -71,9 +71,6 @@ function setUp() {
);
$this->nodes[] = $this->drupalCreateNode($edit);
}
// Reset views data cache.
$this->clearViewsCaches();
}
/**
......@@ -82,7 +79,7 @@ function setUp() {
* We check data structure for both node and node revision tables.
*/
function testViewsData() {
$data = views_fetch_data();
$data = drupal_container()->get('views.views_data')->get();
// Check the table and the joins of the first field.
// Attached to node only.
......
......@@ -74,14 +74,4 @@ function setUpInstances($bundle = 'page') {
}
}
/**
* Clear all views caches and static caches which are required for the patch.
*/
function clearViewsCaches() {
// Reset views data cache.
drupal_static_reset('_views_fetch_data_cache');
drupal_static_reset('_views_fetch_data_recursion_protected');
drupal_static_reset('_views_fetch_data_fully_loaded');
}
}
......@@ -52,8 +52,6 @@ protected function setUp() {
$this->setUpInstances();
$this->clearViewsCaches();
// Create some nodes.
$this->nodes = array();
for ($i = 0; $i < 3; $i++) {
......
<?php
/**
* @file
* Load Views' data so that it knows what is available to build queries from.
*/
/**
* Fetch Views' data from the cache.
*
* $param string|null $table
* (optional) The name of the table for which to fetch Views' data. If
* NULL, data for all tables will be retrieved
* @param bool $reset
* (optional) Whether to rebuild the cache. Defaults to FALSE.
*
* @return array
* An associative array of views data for the given table. If $table is
* NULL, the array will be keyed by table name, with each key corresponding
* to the data array for that table.
*
* @see hook_views_data()
*/
function _views_fetch_data($table = NULL, $reset = FALSE) {
$cache = &drupal_static(__FUNCTION__ . '_cache');
$recursion_protection = &drupal_static(__FUNCTION__ . '_recursion_protected');
$fully_loaded = &drupal_static(__FUNCTION__ . '_fully_loaded');
if ($reset) {
$cache = NULL;
$fully_loaded = FALSE;
}
if ($table) {
if (!isset($cache[$table])) {
$cid = 'views_data:' . $table;
$data = views_cache_get($cid, TRUE);
if (!empty($data->data)) {
$cache[$table] = $data->data;
}
else {
// No cache entry, rebuild.
$cache = _views_fetch_data_build();
$fully_loaded = TRUE;
}
}
if (isset($cache[$table])) {
return $cache[$table];
}
}
else {
if (!$fully_loaded) {
$data = views_cache_get('views_data', TRUE);
if (!empty($data->data)) {
$cache = $data->data;
}
if (empty($cache)) {
$cache = _views_fetch_data_build();
}
$fully_loaded = TRUE;
}
return $cache;
}
// Return an empty array if there is no match.
return array();
}
/**
* Build, set the views data cache if empty and return the views data.
*
* @return array
* The views_data of all tables.
*/
function _views_fetch_data_build() {
$cache = module_invoke_all('views_data');
foreach (module_implements('views_data_alter') as $module) {
$function = $module . '_views_data_alter';
$function($cache);
}
_views_data_process_entity_types($cache);
// Keep a record with all data.
views_cache_set('views_data', $cache, TRUE);
// Save data in seperate cache entries.
foreach ($cache as $key => $data) {
$cid = 'views_data:' . $key;
views_cache_set($cid, $data, TRUE);
}
return $cache;
}
/**
* Links tables having an 'entity type' specified to the respective generic entity-type tables.
*/
function _views_data_process_entity_types(&$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'];
}
}
}
}
/**
* Set a cached item in the views cache.
*
* This is just a convenience wrapper around cache_set().
*
* @param $cid
* The cache ID of the data to store.
* @param $data
* The data to store in the cache. Complex data types will be automatically serialized before insertion.
* Strings will be stored as plain text and not serialized.
* @param $use_language
* If TRUE, the data will be cached specific to the currently active language.
*/
function views_cache_set($cid, $data, $use_language = FALSE) {
if (config('views.settings')->get('skip_cache')) {
return;
}
if ($use_language) {
$cid .= ':' . language(LANGUAGE_TYPE_INTERFACE)->langcode;
}
cache('views_info')->set($cid, $data);
}
/**
* Return data from the persistent views cache.
*
* This is just a convenience wrapper around cache_get().
*
* @param int $cid
* The cache ID of the data to retrieve.
* @param bool $use_language
* If TRUE, the data will be requested specific to the currently active language.
*
* @return stdClass|bool
* The cache or FALSE on failure.
*/
function views_cache_get($cid, $use_language = FALSE) {
if (config('views.settings')->get('skip_cache')) {
return FALSE;
}
if ($use_language) {
$cid .= ':' . language(LANGUAGE_TYPE_INTERFACE)->langcode;
}
return cache('views_info')->get($cid);
}
......@@ -37,8 +37,9 @@ public function getDerivativeDefinition($derivative_id, array $base_plugin_defin
public function getDerivativeDefinitions(array $base_plugin_definition) {
$base_tables = array_keys(views_fetch_base_tables());
$this->derivatives = array();
$views_data = drupal_container()->get('views.views_data');
foreach ($base_tables as $table) {
$views_info = views_fetch_data($table);
$views_info = $views_data->get($table);
if (empty($views_info['table']['wizard_id'])) {
$this->derivatives[$table] = array(
'id' => 'standard',
......
......@@ -770,7 +770,7 @@ public static function getTimezone() {
* @return Drupal\views\Plugin\views\join\JoinPluginBase
*/
public static function getTableJoin($table, $base_table) {
$data = views_fetch_data($table);
$data = drupal_container()->get('views.views_data')->get($table);
if (isset($data['table']['join'][$base_table])) {
$join_info = $data['table']['join'][$base_table];
if (!empty($join_info['join_id'])) {
......@@ -815,10 +815,10 @@ public function getEntityType() {
// If the user has configured a relationship on the handler take that into
// account.
if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
$views_data = views_fetch_data($this->view->relationship->table);
$views_data = drupal_container()->get('views.views_data')->get($this->view->relationship->table);
}
else {
$views_data = views_fetch_data($this->view->storage->get('base_table'));
$views_data = drupal_container()->get('views.views_data')->get($this->view->storage->get('base_table'));
}
if (isset($views_data['table']['entity type'])) {
......
......@@ -127,7 +127,6 @@ public function initDisplay(ViewExecutable $view, array &$display, array &$optio
unset($options['defaults']);
}
views_include('cache');
// Cache for unpackOptions, but not if we are in the ui.
static $unpack_options = array();
if (empty($view->editing)) {
......@@ -799,7 +798,7 @@ public function getPlugin($type) {
// Query plugins allow specifying a specific query class per base table.
if ($type == 'query') {
$views_data = views_fetch_data($this->view->storage->get('base_table'));
$views_data = drupal_container()->get('views.views_data')->get($this->view->storage->get('base_table'));
$name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query';
}
......
......@@ -1444,7 +1444,7 @@ function execute(ViewExecutable $view) {
$count_query->addMetaData('view', $view);
if (empty($this->options['disable_sql_rewrite'])) {
$base_table_data = views_fetch_data($this->view->storage->get('base_table'));
$base_table_data = drupal_container()->get('views.views_data')->get($this->view->storage->get('base_table'));
if (isset($base_table_data['table']['base']['access query tag'])) {
$access_tag = $base_table_data['table']['base']['access query tag'];
$query->addTag($access_tag);
......@@ -1542,7 +1542,8 @@ function execute(ViewExecutable $view) {
function get_entity_tables() {
// Start with the base table.
$entity_tables = array();
$base_table_data = views_fetch_data($this->view->storage->get('base_table'));
$views_data = drupal_container()->get('views.views_data');
$base_table_data = $views_data->get($this->view->storage->get('base_table'));
if (isset($base_table_data['table']['entity type'])) {
$entity_tables[$this->view->storage->get('base_table')] = array(
'base' => $this->view->storage->get('base_table'),
......@@ -1553,7 +1554,7 @@ function get_entity_tables() {
}
// Include all relationships.
foreach ($this->view->relationship as $relationship_id => $relationship) {
$table_data = views_fetch_data($relationship->definition['base']);
$table_data = $views_data->get($relationship->definition['base']);
if (isset($table_data['table']['entity type'])) {
$entity_tables[$relationship->alias] = array(
'base' => $relationship->definition['base'],
......
......@@ -93,7 +93,7 @@ public function buildOptionsForm(&$form, &$form_state) {
foreach ($sorts as $sort_id => $sort) {
$sort_options[$sort_id] = "$sort[group]: $sort[title]";
}
$base_table_data = views_fetch_data($this->definition['base']);
$base_table_data = drupal_container()->get('views.views_data')->get($this->definition['base']);
$form['subquery_sort'] = array(
'#type' => 'select',
......@@ -219,7 +219,7 @@ function left_query($options) {
$temp_view->args[] = '**CORRELATED**';
// Add the base table ID field.
$views_data = views_fetch_data($this->definition['base']);
$views_data = drupal_container()->get('views.views_data')->get($this->definition['base']);
$base_field = $views_data['table']['base']['field'];
$temp_view->addItem('default', 'field', $this->definition['base'], $this->definition['field']);
......@@ -341,7 +341,7 @@ function condition_namespace($string) {
*/
public function query() {
// Figure out what base table this relationship brings to the party.
$table_data = views_fetch_data($this->definition['base']);
$table_data = drupal_container()->get('views.views_data')->get($this->definition['base']);
$base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
$this->ensureMyTable();
......
......@@ -119,7 +119,7 @@ public function buildOptionsForm(&$form, &$form_state) {
*/
public function query() {
// Figure out what base table this relationship brings to the party.
$table_data = views_fetch_data($this->definition['base']);
$table_data = drupal_container()->get('views.views_data')->get($this->definition['base']);
$base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
$this->ensureMyTable();
......
......@@ -73,7 +73,7 @@ public function buildOptionsForm(&$form, &$form_state) {
$relationship_handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
// If this relationship is valid for this type, add it to the list.
$data = views_fetch_data($relationship['table']);
$data = drupal_container()->get('views.views_data')->get($relationship['table']);
$base = $data[$relationship['field']]['relationship']['base'];
if ($base == $this->base_table) {
$relationship_handler->init($executable, $relationship);
......
......@@ -755,7 +755,7 @@ protected function default_display_options() {
// Add a least one field so the view validates and the user has a preview.
// The base field can provide a default in its base settings; otherwise,
// choose the first field with a field handler.
$data = views_fetch_data($this->base_table);
$data = drupal_container()->get('views.views_data')->get($this->base_table);
if (isset($data['table']['base']['defaults']['field'])) {
$field = $data['table']['base']['defaults']['field'];
}
......@@ -836,7 +836,7 @@ protected function default_display_filters_user(array $form, array &$form_state)
}
}
}
$table_data = views_fetch_data($table);
$table_data = drupal_container()->get('views.views_data')->get($table);
// If the 'in' operator is being used, map the values to an array.
$handler = $table_data[$bundle_key]['filter']['id'];
$handler_definition = drupal_container()->get('plugin.manager.views.filter')->getDefinition($handler);
......@@ -922,7 +922,7 @@ protected function default_display_sorts_user($form, $form_state) {
// created from node, but the wizard type is another base table, make
// sure it is not added. This usually don't happen if you have js
// enabled.
$data = views_fetch_data($table);
$data = drupal_container()->get('views.views_data')->get($table);
if (isset($data[$column]['sort'])) {
$sorts[$column] = array(
'id' => $column,
......
......@@ -53,7 +53,7 @@ public static function getInfo() {
*/
public function testHandlers() {
$object_types = array_keys(ViewExecutable::viewsHandlerTypes());
foreach (views_fetch_data() as $base_table => $info) {
foreach (drupal_container()->get('views.views_data')->get() as $base_table => $info) {
if (!isset($info['table']['base'])) {
continue;
}
......
......@@ -219,7 +219,7 @@ protected function formatViewOptions(array $views = array()) {
* Ensure that a certain handler is a instance of a certain table/field.
*/
function assertInstanceHandler($handler, $table, $field, $id) {
$table_data = views_fetch_data($table);
$table_data = drupal_container()->get('views.views_data')->get($table);
$field_data = $table_data[$field][$id];
$this->assertEqual($field_data['id'], $handler->getPluginId());
......
......@@ -31,16 +31,12 @@ public function testViewsFetchData() {
$table_name = 'views_test_data';
$expected_data = $this->viewsData();
$data = views_fetch_data($table_name);
$data = drupal_container()->get('views.views_data')->get($table_name);
$this->assertEqual($data, $expected_data[$table_name], 'Make sure fetching views data by table works as expected.');
$data = views_fetch_data();
$data = drupal_container()->get('views.views_data')->get();
$this->assertTrue(isset($data[$table_name]), 'Make sure the views_test_data info appears in the total views data.');
$this->assertEqual($data[$table_name], $expected_data[$table_name], 'Make sure the views_test_data has the expected values.');
$data = views_fetch_data(NULL, TRUE);
$this->assertTrue(isset($data[$table_name]), 'Make sure the views_fetch_data appears in the total views data with reset = TRUE.');
$this->assertEqual($data[$table_name], $expected_data[$table_name], 'Make sure the views_test_data has the expected values.');
}
/**
......
......@@ -946,7 +946,7 @@ public function initQuery() {
}
// Create and initialize the query object.
$views_data = views_fetch_data($this->storage->get('base_table'));
$views_data = drupal_container()->get('views.views_data')->get($this->storage->get('base_table'));
$this->storage->set('base_field', !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '');
if (!empty($views_data['table']['base']['database'])) {
$this->base_database = $views_data['table']['base']['database'];
......
......@@ -9,6 +9,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Reference;
use Drupal\views\ViewExecutable;
/**
......@@ -24,6 +25,16 @@ public function build(ContainerBuilder $container) {
$container->register("plugin.manager.views.$type", 'Drupal\views\Plugin\ViewsPluginManager')
->addArgument($type);
}
$container
->register('cache.views_info', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('views_info');
$container->register('views.views_data', 'Drupal\views\ViewsDataCache')
->addArgument(new Reference('cache.views_info'))
->addArgument(new Reference('config.factory'));
}
}
<?php
/**
* @file
* Contains \Drupal\views\ViewsDataCache.
*/
namespace Drupal\views;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Cache\CacheBackendInterface;
/**
* Class to manage and lazy load cached views data.
*/
class ViewsDataCache {
/**
* The base cache ID to use.
*
* @var string
*/
protected $baseCid = 'views_data';
/**
* The cache backend to use.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheBackend;
/**
* Storage for the data itself.
*
* @var array
*/
protected $storage = array();
/**
* The configuration factory object.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $config;
/**
* The current language code.
*
* @var string
*/
protected $langcode;
/**
* 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;
/**
* Whether the cache should be rebuilt. This is set when getData() is called.
*
* @var bool
*/
protected $rebuildCache;
public function __construct(CacheBackendInterface $cache_backend, ConfigFactory $config) {
$this->config = $config;
$this->cacheBackend = $cache_backend;
$this->langcode = language(LANGUAGE_TYPE_INTERFACE)->langcode;
$this->skipCache = $this->config->get('views.settings')->get('skip_cache');
}
/**
* Gets cached data for a particular key, or rebuilds if necessary.
*
* @param string|null $key
* The key of the cache entry to retrieve. Defaults to NULL.
*
* @return array $data
* The cached data.
*/
public function get($key = NULL) {
if ($key) {
if (!isset($this->storage[$key])) {
$cid = $this->baseCid . ':' . $key;
$data = $this->cacheGet($cid);
if (!empty($data->data)) {
$this->storage[$key] = $data->data;
}
else {
// No cache entry, rebuild.
$this->storage = $this->getData();
$this->fullyLoaded = TRUE;
}
}
if (isset($this->storage[$key])) {
return $this->storage[$key];
}
}
else {
if (!$this->fullyLoaded) {
$data = $this->cacheGet($this->baseCid);
if (!empty($data->data)) {
$this->storage = $data->data;
}
else {
$this->storage = $this->getData();
}
$this->fullyLoaded = TRUE;
}
}
return $this->storage;
}
/**
* Sets the data in the cache backend for a cache key.
*
* @param string $key
* The cache key to set.
* @param mixed $value
* The value to set for this key.
*/
public function set($key, $value) {
if ($this->skipCache) {
return FALSE;
}
$key .= ':' . $this->langcode;
$this->cacheBackend->set($key, $value);
}
/**
* 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;
}
$cid .= ':' . $this->langcode;
return $this->cacheBackend->get($cid);
}
/**
* Gets all data invoked by hook_views_data().
*
* @return array
* An array of all data.
*/
protected function getData() {
$data = module_invoke_all('views_data');
drupal_alter('views_data', $data);
$this->processEntityTypes($data);
$this->rebuildCache = TRUE;
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'];
}
}
}
}
/**
* Destructs the ViewDataCache object.
*/
public function __destruct() {
if ($this->rebuildCache && !empty($this->storage)) {
// Keep a record with all data.
$this->set($this->baseCid, $this->storage);
// Save data in seperate cache entries.
foreach ($this->storage as $table => $data) {
$cid = $this->baseCid . ':' . $table;
$this->set($cid, $data);
}
}
}
}
......@@ -101,7 +101,7 @@
* Describe data tables (or the equivalent) to Views.
*
* The data described with this hook is fetched and retrieved by
* views_fetch_data().
* drupal_container()->get('views.views_data')->get().
*
* @return array