Update to Drupal 8.0.0 beta 14. For more information, see https://drupal.org/node/2544542

This commit is contained in:
Pantheon Automation 2015-08-27 12:03:05 -07:00 committed by Greg Anderson
parent 3b2511d96d
commit 81ccda77eb
2155 changed files with 54307 additions and 46870 deletions

View file

@ -25,6 +25,9 @@ use Drupal\Core\Session\AccountInterface;
*
* When using ::orIf() and ::andIf(), cacheability metadata will be merged
* accordingly as well.
*
* @todo Use RefinableCacheableDependencyInterface and the corresponding trait in
* https://www.drupal.org/node/2526326.
*/
abstract class AccessResult implements AccessResultInterface, CacheableDependencyInterface {
@ -316,10 +319,12 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* The entity whose cache tag to set on the access result.
*
* @return $this
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
* ::addCacheableDependency() instead.
*/
public function cacheUntilEntityChanges(EntityInterface $entity) {
$this->addCacheTags($entity->getCacheTags());
return $this;
return $this->addCacheableDependency($entity);
}
/**
@ -329,9 +334,33 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* The configuration object whose cache tag to set on the access result.
*
* @return $this
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
* ::addCacheableDependency() instead.
*/
public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
$this->addCacheTags($configuration->getCacheTags());
return $this->addCacheableDependency($configuration);
}
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|object $other_object
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*/
public function addCacheableDependency($other_object) {
if ($other_object instanceof CacheableDependencyInterface) {
$this->contexts = Cache::mergeContexts($this->contexts, $other_object->getCacheContexts());
$this->tags = Cache::mergeTags($this->tags, $other_object->getCacheTags());
$this->maxAge = Cache::mergeMaxAges($this->maxAge, $other_object->getCacheMaxAge());
}
else {
$this->maxAge = 0;
}
return $this;
}

View file

@ -7,7 +7,7 @@
namespace Drupal\Core\Access;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
use Symfony\Component\Routing\Route;
@ -36,7 +36,7 @@ class RouteProcessorCsrf implements OutboundRouteProcessorInterface {
/**
* {@inheritdoc}
*/
public function processOutbound($route_name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
if ($route->hasRequirement('_csrf_token')) {
$path = ltrim($route->getPath(), '/');
// Replace the path parameters with values from the parameters array.
@ -45,13 +45,44 @@ class RouteProcessorCsrf implements OutboundRouteProcessorInterface {
}
// Adding this to the parameters means it will get merged into the query
// string when the route is compiled.
$parameters['token'] = $this->csrfToken->get($path);
if ($cacheable_metadata) {
// Tokens are per user and per session, so not cacheable.
// @todo Improve in https://www.drupal.org/node/2351015.
$cacheable_metadata->setCacheMaxAge(0);
if (!$bubbleable_metadata) {
$parameters['token'] = $this->csrfToken->get($path);
}
else {
// Generate a placeholder and a render array to replace it.
$placeholder = hash('sha1', $path);
$placeholder_render_array = [
'#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
];
// Instead of setting an actual CSRF token as the query string, we set
// the placeholder, which will be replaced at the very last moment. This
// ensures links with CSRF tokens don't break cacheability.
$parameters['token'] = $placeholder;
$bubbleable_metadata->addAttachments(['placeholders' => [$placeholder => $placeholder_render_array]]);
}
}
}
/**
* #lazy_builder callback; gets a CSRF token for the given path.
*
* @param string $path
* The path to get a CSRF token for.
*
* @return array
* A renderable array representing the CSRF token.
*/
public function renderPlaceholderCsrfToken($path) {
return [
'#markup' => $this->csrfToken->get($path),
// Tokens are per session.
'#cache' => [
'contexts' => [
'session',
],
],
];
}
}

View file

@ -164,15 +164,15 @@ class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
$resource_commands = array();
if ($css_assets) {
$css_render_array = $this->cssCollectionRenderer->render($css_assets);
$resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
$resource_commands[] = new AddCssCommand((string) $this->renderer->renderPlain($css_render_array));
}
if ($js_assets_header) {
$js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
$resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
$resource_commands[] = new PrependCommand('head', (string) $this->renderer->renderPlain($js_header_render_array));
}
if ($js_assets_footer) {
$js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
$resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
$resource_commands[] = new AppendCommand('body', (string) $this->renderer->renderPlain($js_footer_render_array));
}
foreach (array_reverse($resource_commands) as $resource_command) {
$response->addCommand($resource_command, TRUE);

View file

@ -37,10 +37,10 @@ trait CommandWithAttachedAssetsTrait {
if (is_array($this->content)) {
$html = \Drupal::service('renderer')->renderRoot($this->content);
$this->attachedAssets = AttachedAssets::createFromRenderArray($this->content);
return $html;
return (string) $html;
}
else {
return $this->content;
return (string) $this->content;
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\Core\Authentication\AuthenticationCollector.
*/
namespace Drupal\Core\Authentication;
/**
* A collector class for authentication providers.
*/
class AuthenticationCollector implements AuthenticationCollectorInterface {
/**
* Array of all registered authentication providers, keyed by ID.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
*/
protected $providers;
/**
* Array of all providers and their priority.
*
* @var array
*/
protected $providerOrders = [];
/**
* Sorted list of registered providers.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
*/
protected $sortedProviders;
/**
* List of providers which are allowed on routes with no _auth option.
*
* @var string[]
*/
protected $globalProviders;
/**
* {@inheritdoc}
*/
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE) {
$this->providers[$provider_id] = $provider;
$this->providerOrders[$priority][$provider_id] = $provider;
// Force the providers to be re-sorted.
$this->sortedProviders = NULL;
if ($global) {
$this->globalProviders[$provider_id] = TRUE;
}
}
/**
* {@inheritdoc}
*/
public function isGlobal($provider_id) {
return isset($this->globalProviders[$provider_id]);
}
/**
* {@inheritdoc}
*/
public function getProvider($provider_id) {
return isset($this->providers[$provider_id]) ? $this->providers[$provider_id] : NULL;
}
/**
* {@inheritdoc}
*/
public function getSortedProviders() {
if (!isset($this->sortedProviders)) {
// Sort the providers according to priority.
krsort($this->providerOrders);
// Merge nested providers from $this->providers into $this->sortedProviders.
$this->sortedProviders = [];
foreach ($this->providerOrders as $providers) {
$this->sortedProviders = array_merge($this->sortedProviders, $providers);
}
}
return $this->sortedProviders;
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\Core\Authentication\AuthenticationCollectorInterface.
*/
namespace Drupal\Core\Authentication;
/**
* Interface for collectors of registered authentication providers.
*/
interface AuthenticationCollectorInterface {
/**
* Adds a provider to the array of registered providers.
*
* @param \Drupal\Core\Authentication\AuthenticationProviderInterface $provider
* The provider object.
* @param string $provider_id
* Identifier of the provider.
* @param int $priority
* (optional) The provider's priority.
* @param bool $global
* (optional) TRUE if the provider is to be applied globally on all routes.
* Defaults to FALSE.
*/
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE);
/**
* Returns whether a provider is considered global.
*
* @param string $provider_id
* The provider ID.
*
* @return bool
* TRUE if the provider is global, FALSE otherwise.
*
* @see \Drupal\Core\Authentication\AuthenticationCollectorInterface::addProvider
*/
public function isGlobal($provider_id);
/**
* Returns an authentication provider.
*
* @param string $provider_id
* The provider ID.
*
* @return \Drupal\Core\Authentication\AuthenticationProviderInterface|NULL
* The authentication provider which matches the ID.
*/
public function getProvider($provider_id);
/**
* Returns the sorted array of authentication providers.
*
* @return \Drupal\Core\Authentication\AuthenticationProviderInterface[]
* An array of authentication provider objects.
*/
public function getSortedProviders();
}

View file

@ -20,69 +20,23 @@ use Symfony\Component\HttpFoundation\Request;
*
* If no provider set an active user then the user is set to anonymous.
*/
class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationProviderFilterInterface, AuthenticationProviderChallengeInterface, AuthenticationManagerInterface {
class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationProviderFilterInterface, AuthenticationProviderChallengeInterface {
/**
* Array of all registered authentication providers, keyed by ID.
* The authentication provider collector.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
* @var \Drupal\Core\Authentication\AuthenticationCollectorInterface
*/
protected $providers;
protected $authCollector;
/**
* Array of all providers and their priority.
* Creates a new authentication manager instance.
*
* @var array
* @param \Drupal\Core\Authentication\AuthenticationCollectorInterface $auth_collector
* The authentication provider collector.
*/
protected $providerOrders = array();
/**
* Sorted list of registered providers.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
*/
protected $sortedProviders;
/**
* List of providers which implement the filter interface.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface[]
*/
protected $filters;
/**
* List of providers which implement the challenge interface.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface[]
*/
protected $challengers;
/**
* List of providers which are allowed on routes with no _auth option.
*
* @var string[]
*/
protected $globalProviders;
/**
* {@inheritdoc}
*/
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE) {
$this->providers[$provider_id] = $provider;
$this->providerOrders[$priority][$provider_id] = $provider;
// Force the builders to be re-sorted.
$this->sortedProviders = NULL;
if ($provider instanceof AuthenticationProviderFilterInterface) {
$this->filters[$provider_id] = $provider;
}
if ($provider instanceof AuthenticationProviderChallengeInterface) {
$this->challengers[$provider_id] = $provider;
}
if ($global) {
$this->globalProviders[$provider_id] = TRUE;
}
public function __construct(AuthenticationCollectorInterface $auth_collector) {
$this->authCollector = $auth_collector;
}
/**
@ -97,7 +51,13 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
*/
public function authenticate(Request $request) {
$provider_id = $this->getProvider($request);
return $this->providers[$provider_id]->authenticate($request);
$provider = $this->authCollector->getProvider($provider_id);
if ($provider) {
return $provider->authenticate($request);
}
return NULL;
}
/**
@ -110,7 +70,7 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
$result = $this->applyFilter($request, $authenticated, $this->getProvider($request));
}
else {
foreach ($this->getSortedProviders() as $provider_id => $provider) {
foreach ($this->authCollector->getSortedProviders() as $provider_id => $provider) {
if ($this->applyFilter($request, $authenticated, $provider_id)) {
$result = TRUE;
break;
@ -126,8 +86,10 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
*/
public function challengeException(Request $request, \Exception $previous) {
$provider_id = $this->getChallenger($request);
if ($provider_id) {
return $this->challengers[$provider_id]->challengeException($request, $previous);
$provider = $this->authCollector->getProvider($provider_id);
return $provider->challengeException($request, $previous);
}
}
@ -142,7 +104,7 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
* If no application detects appropriate credentials, then NULL is returned.
*/
protected function getProvider(Request $request) {
foreach ($this->getSortedProviders() as $provider_id => $provider) {
foreach ($this->authCollector->getSortedProviders() as $provider_id => $provider) {
if ($provider->applies($request)) {
return $provider_id;
}
@ -150,21 +112,19 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
}
/**
* Returns the id of the challenge provider for a request.
* Returns the ID of the challenge provider for a request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The incoming request.
*
* @return string|NULL
* The id of the first authentication provider which applies to the request.
* The ID of the first authentication provider which applies to the request.
* If no application detects appropriate credentials, then NULL is returned.
*/
protected function getChallenger(Request $request) {
if (!empty($this->challengers)) {
foreach ($this->getSortedProviders($request, FALSE) as $provider_id => $provider) {
if (isset($this->challengers[$provider_id]) && !$provider->applies($request) && $this->applyFilter($request, FALSE, $provider_id)) {
return $provider_id;
}
foreach ($this->authCollector->getSortedProviders() as $provider_id => $provider) {
if (($provider instanceof AuthenticationProviderChallengeInterface) && !$provider->applies($request) && $this->applyFilter($request, FALSE, $provider_id)) {
return $provider_id;
}
}
}
@ -186,8 +146,10 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
* TRUE if provider is allowed, FALSE otherwise.
*/
protected function applyFilter(Request $request, $authenticated, $provider_id) {
if (isset($this->filters[$provider_id])) {
$result = $this->filters[$provider_id]->appliesToRoutedRequest($request, $authenticated);
$provider = $this->authCollector->getProvider($provider_id);
if ($provider && ($provider instanceof AuthenticationProviderFilterInterface)) {
$result = $provider->appliesToRoutedRequest($request, $authenticated);
}
else {
$result = $this->defaultFilter($request, $provider_id);
@ -222,27 +184,8 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
return in_array($provider_id, $route->getOption('_auth'));
}
else {
return isset($this->globalProviders[$provider_id]);
return $this->authCollector->isGlobal($provider_id);
}
}
/**
* Returns the sorted array of authentication providers.
*
* @return \Drupal\Core\Authentication\AuthenticationProviderInterface[]
* An array of authentication provider objects.
*/
protected function getSortedProviders() {
if (!isset($this->sortedProviders)) {
// Sort the builders according to priority.
krsort($this->providerOrders);
// Merge nested providers from $this->providers into $this->sortedProviders.
$this->sortedProviders = array();
foreach ($this->providerOrders as $providers) {
$this->sortedProviders = array_merge($this->sortedProviders, $providers);
}
}
return $this->sortedProviders;
}
}

View file

@ -1,30 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Core\Authentication\AuthenticationManagerInterface.
*/
namespace Drupal\Core\Authentication;
/**
* Defines an interface for authentication managers.
*/
interface AuthenticationManagerInterface {
/**
* Adds a provider to the array of registered providers.
*
* @param \Drupal\Core\Authentication\AuthenticationProviderInterface $provider
* The provider object.
* @param string $provider_id
* Identifier of the provider.
* @param int $priority
* (optional) The provider's priority.
* @param bool $global
* (optional) TRUE if the provider is to be applied globally on all routes.
* Defaults to FALSE.
*/
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE);
}

View file

@ -10,6 +10,7 @@ namespace Drupal\Core\Block;
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContextAwarePluginBase;
use Drupal\Component\Utility\Unicode;
@ -265,18 +266,23 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
// \Drupal\system\MachineNameController::transliterate(), so it might make
// sense to provide a common service for the two.
$transliterated = $this->transliteration()->transliterate($admin_label, LanguageInterface::LANGCODE_DEFAULT, '_');
$replace_pattern = '[^a-z0-9_.]+';
$transliterated = Unicode::strtolower($transliterated);
if (isset($replace_pattern)) {
$transliterated = preg_replace('@' . $replace_pattern . '@', '', $transliterated);
}
$transliterated = preg_replace('@[^a-z0-9_.]+@', '', $transliterated);
return $transliterated;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
$max_age = parent::getCacheMaxAge();
// @todo Configurability of this will be removed in
// https://www.drupal.org/node/2458763.
return Cache::mergeMaxAges($max_age, (int) $this->configuration['cache']['max_age']);
}
/**
* Wraps the transliteration service.
*
@ -299,25 +305,4 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
$this->transliteration = $transliteration;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return (int)$this->configuration['cache']['max_age'];
}
}

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Breadcrumb;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
@ -95,7 +94,7 @@ class BreadcrumbManager implements ChainBreadcrumbBuilderInterface {
break;
}
else {
throw new \UnexpectedValueException(SafeMarkup::format('Invalid breadcrumb returned by !class::build().', array('!class' => get_class($builder))));
throw new \UnexpectedValueException('Invalid breadcrumb returned by ' . get_class($builder) . '::build().');
}
}
// Allow modules to alter the breadcrumb.

View file

@ -24,19 +24,16 @@ class Cache {
/**
* Merges arrays of cache contexts and removes duplicates.
*
* @param string[]
* Arrays of cache contexts to merge.
* @param array $a
* Cache contexts array to merge.
* @param array $b
* Cache contexts array to merge.
*
* @return string[]
* The merged array of cache contexts.
*/
public static function mergeContexts() {
$cache_context_arrays = func_get_args();
$cache_contexts = [];
foreach ($cache_context_arrays as $contexts) {
$cache_contexts = array_merge($cache_contexts, $contexts);
}
$cache_contexts = array_unique($cache_contexts);
public static function mergeContexts(array $a = [], array $b = []) {
$cache_contexts = array_unique(array_merge($a, $b));
\Drupal::service('cache_contexts_manager')->validateTokens($cache_contexts);
sort($cache_contexts);
return $cache_contexts;
@ -53,19 +50,16 @@ class Cache {
* allows items to be invalidated based on all tags attached to the content
* they're constituted from.
*
* @param string[]
* Arrays of cache tags to merge.
* @param array $a
* Cache tags array to merge.
* @param array $b
* Cache tags array to merge.
*
* @return string[]
* The merged array of cache tags.
*/
public static function mergeTags() {
$cache_tag_arrays = func_get_args();
$cache_tags = [];
foreach ($cache_tag_arrays as $tags) {
$cache_tags = array_merge($cache_tags, $tags);
}
$cache_tags = array_unique($cache_tags);
public static function mergeTags(array $a = [], array $b = []) {
$cache_tags = array_unique(array_merge($a, $b));
static::validateTags($cache_tags);
sort($cache_tags);
return $cache_tags;
@ -76,29 +70,25 @@ class Cache {
*
* Ensures infinite max-age (Cache::PERMANENT) is taken into account.
*
* @param int
* Max-age values.
* @param int $a
* Max age value to merge.
* @param int $b
* Max age value to merge.
*
* @return int
* The minimum max-age value.
*/
public static function mergeMaxAges() {
$max_ages = func_get_args();
// Filter out all max-age values set to cache permanently.
if (in_array(Cache::PERMANENT, $max_ages)) {
$max_ages = array_filter($max_ages, function ($max_age) {
return $max_age !== Cache::PERMANENT;
});
// If nothing is left, then all max-age values were set to cache
// permanently, and then that is the result.
if (empty($max_ages)) {
return Cache::PERMANENT;
}
public static function mergeMaxAges($a = Cache::PERMANENT, $b = Cache::PERMANENT) {
// If one of the values is Cache::PERMANENT, return the other value.
if ($a === Cache::PERMANENT){
return $b;
}
if ($b === Cache::PERMANENT){
return $a;
}
return min($max_ages);
// If none or the values are Cache::PERMANENT, return the minimum value.
return min($a, $b);
}
/**

View file

@ -10,6 +10,9 @@ namespace Drupal\Core\Cache;
* Defines a generic class for passing cacheability metadata.
*
* @ingroup cache
*
* @todo Use RefinableCacheableDependencyInterface and the corresponding trait in
* https://www.drupal.org/node/2526326.
*/
class CacheableMetadata implements CacheableDependencyInterface {
@ -129,6 +132,35 @@ class CacheableMetadata implements CacheableDependencyInterface {
return $this;
}
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $other_object
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*/
public function addCacheableDependency($other_object) {
if ($other_object instanceof CacheableDependencyInterface) {
$this->addCacheTags($other_object->getCacheTags());
$this->addCacheContexts($other_object->getCacheContexts());
if ($this->maxAge === Cache::PERMANENT) {
$this->maxAge = $other_object->getCacheMaxAge();
}
elseif (($max_age = $other_object->getCacheMaxAge()) && $max_age !== Cache::PERMANENT) {
$this->maxAge = Cache::mergeMaxAges($this->maxAge, $max_age);
}
}
else {
// Not a cacheable dependency, this can not be cached.
$this->maxAge = 0;
}
return $this;
}
/**
* Merges the values of another CacheableMetadata object with this one.
*
@ -139,7 +171,7 @@ class CacheableMetadata implements CacheableDependencyInterface {
* A new CacheableMetadata object, with the merged data.
*/
public function merge(CacheableMetadata $other) {
$result = new static();
$result = clone $this;
// This is called many times per request, so avoid merging unless absolutely
// necessary.

View file

@ -7,18 +7,21 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\PermissionsHashGeneratorInterface;
/**
* Defines the AccountPermissionsCacheContext service, for "per permission" caching.
*
* Cache context ID: 'user.permissions'.
*/
class AccountPermissionsCacheContext extends UserCacheContext {
class AccountPermissionsCacheContext extends UserCacheContextBase implements CacheContextInterface {
/**
* The permissions hash generator.
*
* @var \Drupal\user\PermissionsHashInterface
* @var \Drupal\Core\Session\PermissionsHashGeneratorInterface
*/
protected $permissionsHashGenerator;
@ -27,7 +30,7 @@ class AccountPermissionsCacheContext extends UserCacheContext {
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
* @param \Drupal\user\PermissionsHashInterface $permissions_hash_generator
* @param \Drupal\Core\Session\PermissionsHashGeneratorInterface $permissions_hash_generator
* The permissions hash generator.
*/
public function __construct(AccountInterface $user, PermissionsHashGeneratorInterface $permissions_hash_generator) {
@ -46,7 +49,24 @@ class AccountPermissionsCacheContext extends UserCacheContext {
* {@inheritdoc}
*/
public function getContext() {
return 'ph.' . $this->permissionsHashGenerator->generate($this->user);
return $this->permissionsHashGenerator->generate($this->user);
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
$cacheable_metadata = new CacheableMetadata();
// The permissions hash changes when:
// - a user is updated to have different roles;
$tags = ['user:' . $this->user->id()];
// - a role is updated to have different permissions.
foreach ($this->user->getRoles() as $rid) {
$tags[] = "config:user.role.$rid";
}
return $cacheable_metadata->setCacheTags($tags);
}
}

View file

@ -31,4 +31,21 @@ interface CacheContextInterface {
*/
public function getContext();
/**
* Gets the cacheability metadata for the context.
*
* There are three valid cases for the returned CacheableMetadata object:
* - An empty object means this can be optimized away safely.
* - A max-age of 0 means that this context can never be optimized away. It
* will never bubble up and cache tags will not be used.
* - Any non-zero max-age and cache tags will bubble up into the cache item
* if this is optimized away to allow for invalidation if the context
* value changes.
*
*
* @return \Drupal\Core\Cache\CacheableMetadata
* A cacheable metadata object.
*/
public function getCacheableMetadata();
}

View file

@ -7,7 +7,7 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -99,23 +99,35 @@ class CacheContextsManager {
* @param string[] $context_tokens
* An array of cache context tokens.
*
* @return string[]
* The array of corresponding cache keys.
* @return \Drupal\Core\Cache\Context\ContextCacheKeys
* The ContextCacheKeys object containing the converted cache keys and
* cacheability metadata.
*
* @throws \InvalidArgumentException
* @throws \LogicException
* Thrown if any of the context tokens or parameters are not valid.
*/
public function convertTokensToKeys(array $context_tokens) {
$context_tokens = $this->optimizeTokens($context_tokens);
sort($context_tokens);
$keys = [];
foreach (static::parseTokens($context_tokens) as $context) {
list($context_id, $parameter) = $context;
if (!in_array($context_id, $this->contexts)) {
throw new \InvalidArgumentException(SafeMarkup::format('"@context" is not a valid cache context ID.', ['@context' => $context_id]));
}
$keys[] = $this->getService($context_id)->getContext($parameter);
$this->validateTokens($context_tokens);
$cacheable_metadata = new CacheableMetadata();
$optimized_tokens = $this->optimizeTokens($context_tokens);
// Iterate over cache contexts that have been optimized away and get their
// cacheability metadata.
foreach (static::parseTokens(array_diff($context_tokens, $optimized_tokens)) as $context_token) {
list($context_id, $parameter) = $context_token;
$context = $this->getService($context_id);
$cacheable_metadata = $cacheable_metadata->merge($context->getCacheableMetadata($parameter));
}
return $keys;
sort($optimized_tokens);
$keys = [];
foreach (array_combine($optimized_tokens, static::parseTokens($optimized_tokens)) as $context_token => $context) {
list($context_id, $parameter) = $context;
$keys[] = '[' . $context_token . ']=' . $this->getService($context_id)->getContext($parameter);
}
// Create the returned object and merge in the cacheability metadata.
$context_cache_keys = new ContextCacheKeys($keys);
return $context_cache_keys->merge($cacheable_metadata);
}
/**
@ -129,6 +141,9 @@ class CacheContextsManager {
* possible of a set of cache context tokens, that still captures the entire
* universe of variations.
*
* If a cache context is being optimized away, it is able to set cacheable
* metadata for itself which will be bubbled up.
*
* E.g. when caching per user ('user'), also caching per role ('user.roles')
* is meaningless because "per role" is implied by "per user".
*
@ -150,6 +165,14 @@ class CacheContextsManager {
public function optimizeTokens(array $context_tokens) {
$optimized_content_tokens = [];
foreach ($context_tokens as $context_token) {
// Extract the parameter if available.
$parameter = NULL;
$context_id = $context_token;
if (strpos($context_token, ':') !== FALSE) {
list($context_id, $parameter) = explode(':', $context_token);
}
// Context tokens without:
// - a period means they don't have a parent
// - a colon means they're not a specific value of a cache context
@ -157,6 +180,11 @@ class CacheContextsManager {
if (strpos($context_token, '.') === FALSE && strpos($context_token, ':') === FALSE) {
$optimized_content_tokens[] = $context_token;
}
// Check cacheability. If the context defines a max-age of 0, then it
// can not be optimized away. Pass the parameter along if we have one.
elseif ($this->getService($context_id)->getCacheableMetadata($parameter)->getCacheMaxAge() === 0) {
$optimized_content_tokens[] = $context_token;
}
// The context token has a period or a colon. Iterate over all ancestor
// cache contexts. If one exists, omit the context token.
else {

View file

@ -34,7 +34,32 @@ interface CalculatedCacheContextInterface {
* @return string
* The string representation of the cache context. When $parameter is NULL,
* a value representing all possible parameters must be generated.
*
* @throws \LogicException
* Thrown if the passed in parameter is invalid.
*/
public function getContext($parameter = NULL);
/**
* Gets the cacheability metadata for the context based on the parameter value.
*
* There are three valid cases for the returned CacheableMetadata object:
* - An empty object means this can be optimized away safely.
* - A max-age of 0 means that this context can never be optimized away. It
* will never bubble up and cache tags will not be used.
* - Any non-zero max-age and cache tags will bubble up into the cache item
* if this is optimized away to allow for invalidation if the context
* value changes.
*
* @param string|null $parameter
* The parameter, or NULL to indicate all possible parameter values.
*
* @return \Drupal\Core\Cache\CacheableMetadata
* A cacheable metadata object.
*
* @throws \LogicException
* Thrown if the passed in parameter is invalid.
*/
public function getCacheableMetadata($parameter = NULL);
}

View file

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\ContextCacheKeys.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* A value object to store generated cache keys with its cacheability metadata.
*/
class ContextCacheKeys extends CacheableMetadata {
/**
* The generated cache keys.
*
* @var string[]
*/
protected $keys;
/**
* Constructs a ContextCacheKeys object.
*
* @param string[] $keys
* The cache context keys.
*/
public function __construct(array $keys) {
$this->keys = $keys;
}
/**
* Gets the generated cache keys.
*
* @return string[]
* The cache keys.
*/
public function getKeys() {
return $this->keys;
}
}

View file

@ -7,8 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the CookiesCacheContext service, for "per cookie" caching.
*
* Cache context ID: 'cookies' (to vary by all cookies).
* Calculated cache context ID: 'cookies:%name', e.g. 'cookies:device_type' (to
* vary by the 'device_type' cookie).
*/
class CookiesCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
@ -31,4 +37,11 @@ class CookiesCacheContext extends RequestStackCacheContextBase implements Calcul
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($cookie = NULL) {
return new CacheableMetadata();
}
}

View file

@ -7,8 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the HeadersCacheContext service, for "per header" caching.
*
* Cache context ID: 'headers' (to vary by all headers).
* Calculated cache context ID: 'headers:%name', e.g. 'headers:X-Something' (to
* vary by the 'X-Something' header).
*/
class HeadersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
@ -31,4 +37,11 @@ class HeadersCacheContext extends RequestStackCacheContextBase implements Calcul
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($header = NULL) {
return new CacheableMetadata();
}
}

View file

@ -7,10 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the IpCacheContext service, for "per IP address" caching.
*
* Cache context ID: 'ip'.
*/
class IpCacheContext extends RequestStackCacheContextBase {
class IpCacheContext extends RequestStackCacheContextBase implements CacheContextInterface {
/**
* {@inheritdoc}
@ -26,4 +30,11 @@ class IpCacheContext extends RequestStackCacheContextBase {
return $this->requestStack->getCurrentRequest()->getClientIp();
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -7,10 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the IsSuperUserCacheContext service, for "super user or not" caching.
*
* Cache context ID: 'user.is_super_user'.
*/
class IsSuperUserCacheContext extends UserCacheContext {
class IsSuperUserCacheContext extends UserCacheContextBase implements CacheContextInterface {
/**
* {@inheritdoc}
@ -26,4 +30,11 @@ class IsSuperUserCacheContext extends UserCacheContext {
return ((int) $this->user->id()) === 1 ? '1' : '0';
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -7,6 +7,7 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Language\LanguageManagerInterface;
/**
@ -74,4 +75,11 @@ class LanguagesCacheContext implements CalculatedCacheContextInterface {
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($type = NULL) {
return new CacheableMetadata();
}
}

View file

@ -7,12 +7,13 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\DependencyInjection\ContainerAware;
/**
* Defines the MenuActiveTrailsCacheContext service.
*
* This class is container-aware to avoid initializing the 'menu.active_trail'
* This class is container-aware to avoid initializing the 'menu.active_trails'
* service (and its dependencies) when it is not necessary.
*/
class MenuActiveTrailsCacheContext extends ContainerAware implements CalculatedCacheContextInterface {
@ -28,9 +29,24 @@ class MenuActiveTrailsCacheContext extends ContainerAware implements CalculatedC
* {@inheritdoc}
*/
public function getContext($menu_name = NULL) {
if (!$menu_name) {
throw new \LogicException('No menu name provided for menu.active_trails cache context.');
}
$active_trail = $this->container->get('menu.active_trail')
->getActiveTrailIds($menu_name);
return 'menu_trail.' . $menu_name . '|' . implode('|', $active_trail);
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($menu_name = NULL) {
if (!$menu_name) {
throw new \LogicException('No menu name provided for menu.active_trails cache context.');
}
$cacheable_metadata = new CacheableMetadata();
return $cacheable_metadata->setCacheTags(["config:system.menu.$menu_name"]);
}
}

View file

@ -7,8 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines a cache context for "per page in a pager" caching.
*
* Cache context ID: 'url.query_args.pagers' (to vary by all pagers).
* Calculated cache context ID: 'url.query_args.pagers:%pager_id', e.g.
* 'url.query_args.pagers:1' (to vary by the pager with ID 1).
*/
class PagersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
@ -28,10 +34,17 @@ class PagersCacheContext extends RequestStackCacheContextBase implements Calcula
// The value of the 'page' query argument contains the information that
// controls *all* pagers.
if ($pager_id === NULL) {
return 'pager' . $this->requestStack->getCurrentRequest()->query->get('page', '');
return $this->requestStack->getCurrentRequest()->query->get('page', '');
}
return 'pager.' . $pager_id . '.' . pager_find_page($pager_id);
return $pager_id . '.' . pager_find_page($pager_id);
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($pager_id = NULL) {
return new CacheableMetadata();
}
}

View file

@ -7,12 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the QueryArgsCacheContext service, for "per query args" caching.
*
* A "host" is defined as the combination of URI scheme, domain name and port.
*
* @see Symfony\Component\HttpFoundation::getSchemeAndHttpHost()
* Cache context ID: 'url.query_args' (to vary by all query arguments).
* Calculated cache context ID: 'url.query_args:%key', e.g.'url.query_args:foo'
* (to vary by the 'foo' query argument).
*/
class QueryArgsCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
@ -35,4 +37,11 @@ class QueryArgsCacheContext extends RequestStackCacheContextBase implements Calc
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($query_arg = NULL) {
return new CacheableMetadata();
}
}

View file

@ -7,8 +7,12 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the RequestFormatCacheContext service, for "per format" caching.
*
* Cache context ID: 'request_format'.
*/
class RequestFormatCacheContext extends RequestStackCacheContextBase {
@ -26,4 +30,11 @@ class RequestFormatCacheContext extends RequestStackCacheContextBase {
return $this->requestStack->getCurrentRequest()->getRequestFormat();
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -11,8 +11,12 @@ use Symfony\Component\HttpFoundation\RequestStack;
/**
* Defines a base class for cache contexts depending only on the request stack.
*
* Subclasses need to implement either
* \Drupal\Core\Cache\Context\CacheContextInterface or
* \Drupal\Core\Cache\Context\CalculatedCacheContextInterface.
*/
abstract class RequestStackCacheContextBase implements CacheContextInterface {
abstract class RequestStackCacheContextBase {
/**
* The request stack.

View file

@ -7,10 +7,13 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Defines the RouteCacheContext service, for "per route" caching.
*
* Cache context ID: 'route'.
*/
class RouteCacheContext implements CacheContextInterface {
@ -45,4 +48,11 @@ class RouteCacheContext implements CacheContextInterface {
return $this->routeMatch->getRouteName() . hash('sha256', serialize($this->routeMatch->getRawParameters()->all()));
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -9,6 +9,8 @@ namespace Drupal\Core\Cache\Context;
/**
* Defines the RouteCacheContext service, for "per route name" caching.
*
* Cache context ID: 'route.name'.
*/
class RouteNameCacheContext extends RouteCacheContext {

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\SessionCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the SessionCacheContext service, for "per session" caching.
*
* Cache context ID: 'session'.
*/
class SessionCacheContext extends RequestStackCacheContextBase {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Session');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->requestStack->getCurrentRequest()->getSession()->getId();
}
}

View file

@ -7,9 +7,13 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the SiteCacheContext service, for "per site" caching.
*
* Cache context ID: 'site'.
*
* A "site" is defined as the combination of URI scheme, domain name, port and
* base path. It allows for varying between the *same* site being accessed via
* different entry points. (Different sites in a multisite setup have separate
@ -18,7 +22,7 @@ namespace Drupal\Core\Cache\Context;
* @see \Symfony\Component\HttpFoundation\Request::getSchemeAndHttpHost()
* @see \Symfony\Component\HttpFoundation\Request::getBaseUrl()
*/
class SiteCacheContext extends RequestStackCacheContextBase {
class SiteCacheContext extends RequestStackCacheContextBase implements CacheContextInterface {
/**
* {@inheritdoc}
@ -35,4 +39,11 @@ class SiteCacheContext extends RequestStackCacheContextBase {
return $request->getSchemeAndHttpHost() . $request->getBaseUrl();
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -7,11 +7,13 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Theme\ThemeManagerInterface;
/**
* Defines the ThemeCacheContext service, for "per theme" caching.
*
* Cache context ID: 'theme'.
*/
class ThemeCacheContext implements CacheContextInterface {
@ -46,4 +48,11 @@ class ThemeCacheContext implements CacheContextInterface {
return $this->themeManager->getActiveTheme()->getName() ?: 'stark';
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -7,9 +7,13 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the TimeZoneCacheContext service, for "per time zone" caching.
*
* Cache context ID: 'timezone'.
*
* @see \Drupal\Core\Session\AccountProxy::setAccount()
*/
class TimeZoneCacheContext implements CacheContextInterface {
@ -30,4 +34,11 @@ class TimeZoneCacheContext implements CacheContextInterface {
return date_default_timezone_get();
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -7,10 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the UrlCacheContext service, for "per page" caching.
*
* Cache context ID: 'url'.
*/
class UrlCacheContext extends RequestStackCacheContextBase {
class UrlCacheContext extends RequestStackCacheContextBase implements CacheContextInterface {
/**
* {@inheritdoc}
@ -26,4 +30,11 @@ class UrlCacheContext extends RequestStackCacheContextBase {
return $this->requestStack->getCurrentRequest()->getUri();
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -7,22 +7,14 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the UserCacheContext service, for "per user" caching.
*
* Cache context ID: 'user'.
*/
class UserCacheContext implements CacheContextInterface {
/**
* Constructs a new UserCacheContext service.
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(AccountInterface $user) {
$this->user = $user;
}
class UserCacheContext extends UserCacheContextBase implements CacheContextInterface {
/**
* {@inheritdoc}
@ -35,7 +27,14 @@ class UserCacheContext implements CacheContextInterface {
* {@inheritdoc}
*/
public function getContext() {
return "u." . $this->user->id();
return $this->user->id();
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\UserCacheContextBase.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Session\AccountInterface;
/**
* Base class for user-based cache contexts.
*
* Subclasses need to implement either
* \Drupal\Core\Cache\Context\CacheContextInterface or
* \Drupal\Core\Cache\Context\CalculatedCacheContextInterface.
*/
abstract class UserCacheContextBase {
/**
* The account object.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* Constructs a new UserCacheContextBase class.
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(AccountInterface $user) {
$this->user = $user;
}
}

View file

@ -7,13 +7,19 @@
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the UserRolesCacheContext service, for "per role" caching.
*
* Only use this cache context when checking explicitly for certain roles. Use
* user.permissions for anything that checks permissions.
*
* Cache context ID: 'user.roles' (to vary by all roles of the current user).
* Calculated cache context ID: 'user.roles:%role', e.g. 'user.roles:anonymous'
* (to vary by the presence/absence of a specific role).
*/
class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheContextInterface{
class UserRolesCacheContext extends UserCacheContextBase implements CalculatedCacheContextInterface {
/**
* {@inheritdoc}
@ -34,11 +40,18 @@ class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheC
return 'is-super-user';
}
if ($role === NULL) {
return 'r.' . implode(',', $this->user->getRoles());
return implode(',', $this->user->getRoles());
}
else {
return 'r.' . $role . '.' . (in_array($role, $this->user->getRoles()) ? '0' : '1');
return (in_array($role, $this->user->getRoles()) ? '0' : '1');
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($role = NULL) {
return (new CacheableMetadata())->setCacheTags(['user:' . $this->user->id()]);
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\RefinableCacheableDependencyInterface.
*/
namespace Drupal\Core\Cache;
/**
* Allows to add cacheability metadata to an object for the current runtime.
*
* This must be used when changing an object in a way that affects its
* cacheability. For example, when changing the active translation of an entity
* based on the current content language then a cache context for that must be
* added.
*/
interface RefinableCacheableDependencyInterface extends CacheableDependencyInterface {
/**
* Adds cache contexts.
*
* @param string[] $cache_contexts
* The cache contexts to be added.
*
* @return $this
*/
public function addCacheContexts(array $cache_contexts);
/**
* Adds cache tags.
*
* @param string[] $cache_tags
* The cache tags to be added.
*
* @return $this
*/
public function addCacheTags(array $cache_tags);
/**
* Merges the maximum age (in seconds) with the existing maximum age.
*
* The max age will be set to the given value if it is lower than the existing
* value.
*
* @param int $max_age
* The max age to associate.
*
* @return $this
*
* @throws \InvalidArgumentException
* Thrown if a non-integer value is supplied.
*/
public function mergeCacheMaxAge($max_age);
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|object $other_object
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*
* @see \Drupal\Core\Cache\CacheableMetadata::createFromObject()
*/
public function addCacheableDependency($other_object);
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\RefinableCacheableDependencyTrait.
*/
namespace Drupal\Core\Cache;
/**
* Trait for \Drupal\Core\Cache\RefinableCacheableDependencyInterface.
*/
trait RefinableCacheableDependencyTrait {
/**
* Cache contexts.
*
* @var string[]
*/
protected $cacheContexts = [];
/**
* Cache tags.
*
* @var string[]
*/
protected $cacheTags = [];
/**
* Cache max-age.
*
* @var int
*/
protected $cacheMaxAge = Cache::PERMANENT;
/**
* {@inheritdoc}
*/
public function addCacheableDependency($other_object) {
if ($other_object instanceof CacheableDependencyInterface) {
$this->addCacheContexts($other_object->getCacheContexts());
$this->addCacheTags($other_object->getCacheTags());
$this->mergeCacheMaxAge($other_object->getCacheMaxAge());
}
else {
// Not a cacheable dependency, this can not be cached.
$this->maxAge = 0;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function addCacheContexts(array $cache_contexts) {
$this->cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts);
return $this;
}
/**
* {@inheritdoc}
*/
public function addCacheTags(array $cache_tags) {
$this->cacheTags = Cache::mergeTags($this->cacheTags, $cache_tags);
return $this;
}
/**
* {@inheritdoc}
*/
public function mergeCacheMaxAge($max_age) {
$this->cacheMaxAge = Cache::mergeMaxAges($this->cacheMaxAge, $max_age);
return $this;
}
}

View file

@ -220,6 +220,9 @@ class DbDumpCommand extends Command {
// Set primary key, unique keys, and indexes.
$this->getTableIndexes($table, $definition);
// Set table collation.
$this->getTableCollation($table, $definition);
return $definition;
}
@ -235,7 +238,6 @@ class DbDumpCommand extends Command {
// Note, this query doesn't support ordering, so that is worked around
// below by keying the array on Seq_in_index.
$query = $this->connection->query("SHOW INDEX FROM {" . $table . "}");
$indexes = [];
while (($row = $query->fetchAssoc()) !== FALSE) {
$index_name = $row['Key_name'];
$column = $row['Column_name'];
@ -259,6 +261,22 @@ class DbDumpCommand extends Command {
}
}
/**
* Set the table collation.
*
* @param string $table
* The table to find indexes for.
* @param array &$definition
* The schema definition to modify.
*/
protected function getTableCollation($table, &$definition) {
$query = $this->connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
$data = $query->fetchAssoc();
// Set `mysql_character_set`. This will be ignored by other backends.
$definition['mysql_character_set'] = str_replace('_general_ci', '', $data['Collation']);
}
/**
* Gets all data from a given table.
*

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\Core\Command\GenerateProxyClassApplication.
*/
namespace Drupal\Core\Command;
use Drupal\Component\ProxyBuilder\ProxyBuilder;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
/**
* Provides a console command to generate proxy classes.
*/
class GenerateProxyClassApplication extends Application {
/**
* The proxy builder.
*
* @var \Drupal\Component\ProxyBuilder\ProxyBuilder
*/
protected $proxyBuilder;
/**
* Constructs a new GenerateProxyClassApplication instance.
*
* @param \Drupal\Component\ProxyBuilder\ProxyBuilder $proxy_builder
* The proxy builder.
*/
public function __construct(ProxyBuilder $proxy_builder) {
$this->proxyBuilder = $proxy_builder;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function getCommandName(InputInterface $input) {
return 'generate-proxy-class';
}
/**
* {@inheritdoc}
*/
protected function getDefaultCommands() {
// Even though this is a single command, keep the HelpCommand (--help).
$default_commands = parent::getDefaultCommands();
$default_commands[] = new GenerateProxyClassCommand($this->proxyBuilder);
return $default_commands;
}
/**
* {@inheritdoc}
*
* Overridden so the application doesn't expect the command name as the first
* argument.
*/
public function getDefinition() {
$definition = parent::getDefinition();
// Clears the normal first argument (the command name).
$definition->setArguments();
return $definition;
}
}

View file

@ -0,0 +1,97 @@
<?php
/**
* @file
* Contains \Drupal\Core\Command\GenerateProxyClassCommand.
*/
namespace Drupal\Core\Command;
use Drupal\Component\ProxyBuilder\ProxyBuilder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Provides a console command to generate proxy classes.
*/
class GenerateProxyClassCommand extends Command {
/**
* The proxy builder.
*
* @var \Drupal\Component\ProxyBuilder\ProxyBuilder
*/
protected $proxyBuilder;
/**
* Constructs a new GenerateProxyClassCommand instance.
*
* @param \Drupal\Component\ProxyBuilder\ProxyBuilder $proxy_builder
* The proxy builder.
*/
public function __construct(ProxyBuilder $proxy_builder) {
parent::__construct();
$this->proxyBuilder = $proxy_builder;
}
/**
* {@inheritdoc}
*/
protected function configure() {
$this->setName('generate-proxy-class')
->setDefinition([
new InputArgument('class_name', InputArgument::REQUIRED, 'The class to be proxied'),
new InputArgument('namespace_root_path', InputArgument::REQUIRED, 'The filepath to the root of the namespace.'),
])
->setDescription('Dumps a generated proxy class into its appropriate namespace.')
->addUsage('\'Drupal\Core\Batch\BatchStorage\' "core/lib/Drupal/Core"')
->addUsage('\'Drupal\block\BlockRepository\' "core/modules/block/src"')
->addUsage('\'Drupal\mymodule\MyClass\' "modules/contrib/mymodule/src"');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$class_name = ltrim($input->getArgument('class_name'), '\\');
$namespace_root = $input->getArgument('namespace_root_path');
$match = [];
preg_match('/([a-zA-Z0-9_]+\\\\[a-zA-Z0-9_]+)\\\\(.+)/', $class_name, $match);
if ($match) {
$root_namespace = $match[1];
$rest_fqcn = $match[2];
$proxy_filename = $namespace_root . '/ProxyClass/' . str_replace('\\', '/', $rest_fqcn) . '.php';
$proxy_class_name = $root_namespace . '\\ProxyClass\\' . $rest_fqcn;
$proxy_class_string = $this->proxyBuilder->build($class_name);
$file_string = <<<EOF
<?php
/**
* @file
* Contains \{{ proxy_class_name }}.
*/
/**
* This file was generated via php core/scripts/generate-proxy-class.php '$class_name' "$namespace_root".
*/
{{ proxy_class_string }}
EOF;
$file_string = str_replace(['{{ proxy_class_name }}', '{{ proxy_class_string }}'], [$proxy_class_name, $proxy_class_string], $file_string);
mkdir(dirname($proxy_filename), 0775, TRUE);
file_put_contents($proxy_filename, $file_string);
$output->writeln(sprintf('Proxy of class %s written to %s', $class_name, $proxy_filename));
}
}
}

View file

@ -9,6 +9,7 @@ namespace Drupal\Core\Condition;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Executable\ExecutableInterface;
use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
@ -46,12 +47,12 @@ use Drupal\Core\Plugin\PluginFormInterface;
*
* @ingroup plugin_api
*/
interface ConditionInterface extends ExecutableInterface, PluginFormInterface, ConfigurablePluginInterface, PluginInspectionInterface {
interface ConditionInterface extends ExecutableInterface, PluginFormInterface, ConfigurablePluginInterface, PluginInspectionInterface, CacheableDependencyInterface {
/**
* Determines whether condition result will be negated.
*
* @return boolean
* @return bool
* Whether the condition result will be negated.
*/
public function isNegated();

View file

@ -7,6 +7,8 @@
namespace Drupal\Core\Condition;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Executable\ExecutablePluginBase;
use Drupal\Core\Form\FormStateInterface;

View file

@ -8,9 +8,9 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
@ -28,8 +28,9 @@ use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
* @see \Drupal\Core\Config\Config
* @see \Drupal\Core\Theme\ThemeSettings
*/
abstract class ConfigBase implements CacheableDependencyInterface {
abstract class ConfigBase implements RefinableCacheableDependencyInterface {
use DependencySerializationTrait;
use RefinableCacheableDependencyTrait;
/**
* The name of the configuration object.
@ -97,24 +98,17 @@ abstract class ConfigBase implements CacheableDependencyInterface {
public static function validateName($name) {
// The name must be namespaced by owner.
if (strpos($name, '.') === FALSE) {
throw new ConfigNameException(SafeMarkup::format('Missing namespace in Config object name @name.', array(
'@name' => $name,
)));
throw new ConfigNameException("Missing namespace in Config object name $name.");
}
// The name must be shorter than Config::MAX_NAME_LENGTH characters.
if (strlen($name) > self::MAX_NAME_LENGTH) {
throw new ConfigNameException(SafeMarkup::format('Config object name @name exceeds maximum allowed length of @length characters.', array(
'@name' => $name,
'@length' => self::MAX_NAME_LENGTH,
)));
throw new ConfigNameException("Config object name $name exceeds maximum allowed length of " . static::MAX_NAME_LENGTH . " characters.");
}
// The name must not contain any of the following characters:
// : ? * < > " ' / \
if (preg_match('/[:?*<>"\'\/\\\\]/', $name)) {
throw new ConfigNameException(SafeMarkup::format('Invalid character in Config object name @name.', array(
'@name' => $name,
)));
throw new ConfigNameException("Invalid character in Config object name $name.");
}
}
@ -222,7 +216,7 @@ abstract class ConfigBase implements CacheableDependencyInterface {
protected function validateKeys(array $data) {
foreach ($data as $key => $value) {
if (strpos($key, '.') !== FALSE) {
throw new ConfigValueException(SafeMarkup::format('@key key contains a dot which is not supported.', array('@key' => $key)));
throw new ConfigValueException("$key key contains a dot which is not supported.");
}
if (is_array($value)) {
$this->validateKeys($value);
@ -269,21 +263,21 @@ abstract class ConfigBase implements CacheableDependencyInterface {
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
return $this->cacheContexts;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return ['config:' . $this->name];
return Cache::mergeTags(['config:' . $this->name], $this->cacheTags);
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
return $this->cacheMaxAge;
}
}

View file

@ -21,11 +21,16 @@ final class ConfigEvents {
* object is saved. The event listener method receives a
* \Drupal\Core\Config\ConfigCrudEvent instance.
*
* See hook_update_N() documentation for safe configuration API usage and
* restrictions as this event will be fired when configuration is saved by
* hook_update_N().
*
* @Event
*
* @see \Drupal\Core\Config\ConfigCrudEvent
* @see \Drupal\Core\Config\Config::save()
* @see \Drupal\Core\Config\ConfigFactory::onConfigSave()
* @see hook_update_N()
*
* @var string
*/
@ -38,11 +43,16 @@ final class ConfigEvents {
* object is deleted. The event listener method receives a
* \Drupal\Core\Config\ConfigCrudEvent instance.
*
* See hook_update_N() documentation for safe configuration API usage and
* restrictions as this event will be fired when configuration is deleted by
* hook_update_N().
*
* @Event
*
* @see \Drupal\Core\Config\ConfigCrudEvent
* @see \Drupal\Core\Config\Config::delete()
* @see \Drupal\Core\Config\ConfigFactory::onConfigDelete()
* @see hook_update_N()
*
* @var string
*/
@ -55,10 +65,15 @@ final class ConfigEvents {
* object's name is changed. The event listener method receives a
* \Drupal\Core\Config\ConfigRenameEvent instance.
*
* See hook_update_N() documentation for safe configuration API usage and
* restrictions as this event will be fired when configuration is renamed by
* hook_update_N().
*
* @Event
*
* @see \Drupal\Core\Config\ConfigRenameEvent
* @see \Drupal\Core\Config\ConfigFactoryInterface::rename().
* @see \Drupal\Core\Config\ConfigFactoryInterface::rename()
* @see hook_update_N()
*
* @var string
*/

View file

@ -126,6 +126,9 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
$this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
}
}
$this->propagateConfigOverrideCacheability($cache_key, $name);
return $this->cache[$cache_key];
}
}
@ -183,6 +186,9 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
$this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
}
}
$this->propagateConfigOverrideCacheability($cache_key, $name);
$list[$name] = $this->cache[$cache_key];
}
}
@ -209,6 +215,20 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
return $overrides;
}
/**
* Propagates cacheability of config overrides to cached config objects.
*
* @param string $cache_key
* The key of the cached config object to update.
* @param string $name
* The name of the configuration object to construct.
*/
protected function propagateConfigOverrideCacheability($cache_key, $name) {
foreach ($this->configFactoryOverrides as $override) {
$this->cache[$cache_key]->addCacheableDependency($override->getCacheableMetadata($name));
}
}
/**
* {@inheritdoc}
*/

View file

@ -74,7 +74,7 @@ abstract class ConfigFactoryOverrideBase implements EventSubscriberInterface {
}
elseif ($changed) {
// Otherwise set the filtered override values back.
$override->setData($override_data)->save();
$override->setData($override_data)->save(TRUE);
}
}

View file

@ -58,4 +58,15 @@ interface ConfigFactoryOverrideInterface {
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION);
/**
* Gets the cacheability metadata associated with the config factory override.
*
* @param string $name
* The name of the configuration override to get metadata for.
*
* @return \Drupal\Core\Cache\CacheableMetadata
* A cacheable metadata object.
*/
public function getCacheableMetadata($name);
}

View file

@ -11,7 +11,6 @@ use Drupal\Core\Config\Importer\MissingContentEvent;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityStorageException;
@ -763,7 +762,7 @@ class ConfigImporter {
}
}
catch (\Exception $e) {
$this->logError($this->t('Unexpected error during import with operation @op for @name: @message', array('@op' => $op, '@name' => $name, '@message' => $e->getMessage())));
$this->logError($this->t('Unexpected error during import with operation @op for @name: !message', array('@op' => $op, '@name' => $name, '!message' => $e->getMessage())));
// Error for that operation was logged, mark it as processed so that
// the import can continue.
$this->setProcessedConfiguration($collection, $op, $name);
@ -972,7 +971,7 @@ class ConfigImporter {
// Call to the configuration entity's storage to handle the configuration
// change.
if (!($entity_storage instanceof ImportableEntityStorageInterface)) {
throw new EntityStorageException(SafeMarkup::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type)));
throw new EntityStorageException(sprintf('The entity storage "%s" for the "%s" entity type does not support imports', get_class($entity_storage), $entity_type));
}
$entity_storage->$method($name, $new_config, $old_config);
$this->setProcessedConfiguration($collection, $op, $name);
@ -1018,7 +1017,7 @@ class ConfigImporter {
// Call to the configuration entity's storage to handle the configuration
// change.
if (!($entity_storage instanceof ImportableEntityStorageInterface)) {
throw new EntityStorageException(SafeMarkup::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type_id)));
throw new EntityStorageException(sprintf("The entity storage '%s' for the '%s' entity type does not support imports", get_class($entity_storage), $entity_type_id));
}
$entity_storage->importRename($names['old_name'], $new_config, $old_config);
$this->setProcessedConfiguration($collection, 'rename', $rename_name);

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Config\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
@ -192,8 +191,6 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
* {@inheritdoc}
*/
public function disable() {
// An entity was disabled, invalidate its own cache tag.
Cache::invalidateTags($this->getCacheTags());
return $this->setStatus(FALSE);
}
@ -280,7 +277,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
$definition = $this->getTypedConfig()->getDefinition($config_name);
if (!isset($definition['mapping'])) {
throw new SchemaIncompleteException(SafeMarkup::format('Incomplete or missing schema for @config_name', array('@config_name' => $config_name)));
throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
}
$properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
}
@ -333,7 +330,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
->execute();
$matched_entity = reset($matching_entities);
if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
throw new ConfigDuplicateUUIDException(SafeMarkup::format('Attempt to save a configuration entity %id with UUID %uuid when this UUID is already used for %matched', array('%id' => $this->id(), '%uuid' => $this->uuid(), '%matched' => $matched_entity)));
throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this UUID is already used for '$matched_entity'");
}
// If this entity is not new, load the original entity for comparison.
@ -341,7 +338,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
$original = $storage->loadUnchanged($this->getOriginalId());
// Ensure that the UUID cannot be changed for an existing entity.
if ($original && ($original->uuid() != $this->uuid())) {
throw new ConfigDuplicateUUIDException(SafeMarkup::format('Attempt to save a configuration entity %id with UUID %uuid when this entity already exists with UUID %original_uuid', array('%id' => $this->id(), '%uuid' => $this->uuid(), '%original_uuid' => $original->uuid())));
throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this entity already exists with UUID '{$original->uuid()}'");
}
}
if (!$this->isSyncing() && !$this->trustedData) {
@ -409,7 +406,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
/**
* {@inheritdoc}
*/
public function getCacheTags() {
public function getCacheTagsToInvalidate() {
// Use cache tags that match the underlying config object's name.
// @see \Drupal\Core\Config\ConfigBase::getCacheTags()
return ['config:' . $this->getConfigDependencyName()];

View file

@ -7,14 +7,13 @@
namespace Drupal\Core\Config\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\EntityStorageBase;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Component\Uuid\UuidInterface;
@ -184,11 +183,41 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
}
// Load all of the configuration entities.
$records = array();
/** @var \Drupal\Core\Config\Config[] $configs */
$configs = [];
$records = [];
foreach ($this->configFactory->loadMultiple($names) as $config) {
$records[$config->get($this->idKey)] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
$id = $config->get($this->idKey);
$records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
$configs[$id] = $config;
}
return $this->mapFromStorageRecords($records);
$entities = $this->mapFromStorageRecords($records, $configs);
// Config entities wrap config objects, and therefore they need to inherit
// the cacheability metadata of config objects (to ensure e.g. additional
// cacheability metadata added by config overrides is not lost).
foreach ($entities as $id => $entity) {
// But rather than simply inheriting all cacheability metadata of config
// objects, we need to make sure the self-referring cache tag that is
// present on Config objects is not added to the Config entity. It must be
// removed for 3 reasons:
// 1. When renaming/duplicating a Config entity, the cache tag of the
// original config object would remain present, which would be wrong.
// 2. Some Config entities choose to not use the cache tag that the under-
// lying Config object provides by default (For performance and
// cacheability reasons it may not make sense to have a unique cache
// tag for every Config entity. The DateFormat Config entity specifies
// the 'rendered' cache tag for example, because A) date formats are
// changed extremely rarely, so invalidating all render cache items is
// fine, B) it means fewer cache tags per page.).
// 3. Fewer cache tags is better for performance.
$self_referring_cache_tag = ['config:' . $configs[$id]->getName()];
$config_cacheability = CacheableMetadata::createFromObject($configs[$id]);
$config_cacheability->setCacheTags(array_diff($config_cacheability->getCacheTags(), $self_referring_cache_tag));
$entity->addCacheableDependency($config_cacheability);
}
return $entities;
}
/**
@ -229,10 +258,7 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
// @todo Consider moving this to a protected method on the parent class, and
// abstracting it for all entity types.
if (strlen($entity->get($this->idKey)) > self::MAX_ID_LENGTH) {
throw new ConfigEntityIdLengthException(SafeMarkup::format('Configuration entity ID @id exceeds maximum allowed length of @length characters.', array(
'@id' => $entity->get($this->idKey),
'@length' => self::MAX_ID_LENGTH,
)));
throw new ConfigEntityIdLengthException("Configuration entity ID {$entity->get($this->idKey)} exceeds maximum allowed length of " . self::MAX_ID_LENGTH . " characters.");
}
return parent::save($entity);
@ -374,7 +400,7 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
$id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
$entity = $this->load($id);
if (!$entity) {
throw new ConfigImporterException(SafeMarkup::format('Attempt to update non-existing entity "@id".', array('@id' => $id)));
throw new ConfigImporterException("Attempt to update non-existing entity '$id'.");
}
$entity->setSyncing(TRUE);
$entity = $this->updateFromStorageRecord($entity, $new_config->get());

View file

@ -10,7 +10,6 @@ namespace Drupal\Core\Config\Entity;
use Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException;
use Drupal\Core\Entity\EntityType;
use Drupal\Core\Config\ConfigPrefixLengthException;
use Drupal\Component\Utility\SafeMarkup;
/**
* Provides an implementation of a configuration entity type and its metadata.
@ -93,10 +92,7 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
}
if (strlen($config_prefix) > static::PREFIX_LENGTH) {
throw new ConfigPrefixLengthException(SafeMarkup::format('The configuration file name prefix @config_prefix exceeds the maximum character limit of @max_char.', array(
'@config_prefix' => $config_prefix,
'@max_char' => static::PREFIX_LENGTH,
)));
throw new ConfigPrefixLengthException("The configuration file name prefix $config_prefix exceeds the maximum character limit of " . static::PREFIX_LENGTH);
}
return $config_prefix;
}
@ -158,7 +154,7 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
*/
protected function checkStorageClass($class) {
if (!is_a($class, 'Drupal\Core\Config\Entity\ConfigEntityStorage', TRUE)) {
throw new ConfigEntityStorageClassException(SafeMarkup::format('@class is not \Drupal\Core\Config\Entity\ConfigEntityStorage or it does not extend it', ['@class' => $class]));
throw new ConfigEntityStorageClassException("$class is not \\Drupal\\Core\\Config\\Entity\\ConfigEntityStorage or it does not extend it");
}
}

View file

@ -9,7 +9,6 @@ namespace Drupal\Core\Config;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Utility\SafeMarkup;
/**
* Defines the file storage.
@ -101,10 +100,7 @@ class FileStorage implements StorageInterface {
$data = $this->decode($data);
}
catch (InvalidDataTypeException $e) {
throw new UnsupportedDataTypeConfigException(SafeMarkup::format('Invalid data type in config @name: !message', array(
'@name' => $name,
'!message' => $e->getMessage(),
)));
throw new UnsupportedDataTypeConfigException("Invalid data type in config $name: {$e->getMessage()}");
}
return $data;
}
@ -130,10 +126,7 @@ class FileStorage implements StorageInterface {
$data = $this->encode($data);
}
catch (InvalidDataTypeException $e) {
throw new StorageException(SafeMarkup::format('Invalid data type in config @name: !message', array(
'@name' => $name,
'!message' => $e->getMessage(),
)));
throw new StorageException("Invalid data type in config $name: {$e->getMessage()}");
}
$target = $this->getFilePath($name);

View file

@ -7,8 +7,6 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\SafeMarkup;
/**
* Defines the immutable configuration object.
*
@ -31,21 +29,21 @@ class ImmutableConfig extends Config {
* {@inheritdoc}
*/
public function set($key, $value) {
throw new ImmutableConfigException(SafeMarkup::format('Can not set values on immutable configuration !name:!key. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName(), '!key' => $key]));
throw new ImmutableConfigException("Can not set values on immutable configuration {$this->getName()}:$key. Use \\Drupal\\Core\\Config\\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object");
}
/**
* {@inheritdoc}
*/
public function clear($key) {
throw new ImmutableConfigException(SafeMarkup::format('Can not clear !key key in immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName(), '!key' => $key]));
throw new ImmutableConfigException("Can not clear $key key in immutable configuration {$this->getName()}. Use \\Drupal\\Core\\Config\\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object");
}
/**
* {@inheritdoc}
*/
public function save($has_trusted_data = FALSE) {
throw new ImmutableConfigException(SafeMarkup::format('Can not save immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName()]));
throw new ImmutableConfigException("Can not save immutable configuration {$this->getName()}. Use \\Drupal\\Core\\Config\\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object");
}
/**
@ -55,7 +53,7 @@ class ImmutableConfig extends Config {
* The configuration object.
*/
public function delete() {
throw new ImmutableConfigException(SafeMarkup::format('Can not delete immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName()]));
throw new ImmutableConfigException("Can not delete immutable configuration {$this->getName()}. Use \\Drupal\\Core\\Config\\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object");
}
}

View file

@ -91,9 +91,7 @@ class InstallStorage extends FileStorage {
}
// If any code in the early installer requests a configuration object that
// does not exist anywhere as default config, then that must be mistake.
throw new StorageException(format_string('Missing configuration file: @name', array(
'@name' => $name,
)));
throw new StorageException("Missing configuration file: $name");
}
/**

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Config\Schema;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\TypedData\TypedData;
@ -94,7 +93,7 @@ abstract class ArrayElement extends TypedData implements \IteratorAggregate, Typ
return $element;
}
else {
throw new \InvalidArgumentException(SafeMarkup::format("The configuration property @key doesn't exist.", array('@key' => $name)));
throw new \InvalidArgumentException("The configuration property $name doesn't exist.");
}
}

View file

@ -24,7 +24,7 @@ interface TypedConfigInterface extends TraversableTypedDataInterface {
/**
* Determines whether the data structure is empty.
*
* @return boolean
* @return bool
* TRUE if the data structure is empty, FALSE otherwise.
*/
public function isEmpty();

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\Schema\Ignore;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\TypedData\Type\FloatInterface;
@ -163,10 +162,7 @@ abstract class StorableConfigBase extends ConfigBase {
}
}
elseif ($value !== NULL && !is_scalar($value)) {
throw new UnsupportedDataTypeConfigException(SafeMarkup::format('Invalid data type for config element @name:@key', array(
'@name' => $this->getName(),
'@key' => $key,
)));
throw new UnsupportedDataTypeConfigException("Invalid data type for config element {$this->getName()}:$key");
}
}
@ -213,10 +209,7 @@ abstract class StorableConfigBase extends ConfigBase {
else {
// Throw exception on any non-scalar or non-array value.
if (!is_array($value)) {
throw new UnsupportedDataTypeConfigException(SafeMarkup::format('Invalid data type for config element @name:@key', array(
'@name' => $this->getName(),
'@key' => $key,
)));
throw new UnsupportedDataTypeConfigException("Invalid data type for config element {$this->getName()}:$key");
}
// Recurse into any nested keys.
foreach ($value as $nested_value_key => $nested_value) {

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
@ -196,7 +195,7 @@ class StorageComparer implements StorageComparerInterface {
// ensure the array is keyed from 0.
$this->changelist[$collection][$op] = array_values(array_intersect($sort_order, $this->changelist[$collection][$op]));
if ($count != count($this->changelist[$collection][$op])) {
throw new \InvalidArgumentException(SafeMarkup::format('Sorting the @op changelist should not change its length.', array('@op' => $op)));
throw new \InvalidArgumentException("Sorting the $op changelist should not change its length.");
}
}
}

View file

@ -156,7 +156,7 @@ interface StorageInterface {
* (optional) The prefix to search for. If omitted, all configuration
* objects that exist will be deleted.
*
* @return boolean
* @return bool
* TRUE on success, FALSE otherwise.
*/
public function deleteAll($prefix = '');

View file

@ -88,14 +88,14 @@ class ConfigSchemaChecker implements EventSubscriberInterface {
$this->checked[$name . ':' . $checksum] = TRUE;
$errors = $this->checkConfigSchema($this->typedManager, $name, $data);
if ($errors === FALSE) {
throw new SchemaIncompleteException(SafeMarkup::format('No schema for @config_name', array('@config_name' => $name)));
throw new SchemaIncompleteException("No schema for $name");
}
elseif (is_array($errors)) {
$text_errors = [];
foreach ($errors as $key => $error) {
$text_errors[] = SafeMarkup::format('@key @error', array('@key' => $key, '@error' => $error));
}
throw new SchemaIncompleteException(SafeMarkup::format('Schema errors for @config_name with the following errors: @errors', array('@config_name' => $name, '@errors' => implode(', ', $text_errors))));
throw new SchemaIncompleteException("Schema errors for $name with the following errors: " . implode(', ', $text_errors));
}
}
}

View file

@ -8,7 +8,6 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\Schema\ArrayElement;
use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
@ -17,7 +16,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\TypedData\TypedDataManager;
/**
* Manages config type plugins.
* Manages config schema type plugins.
*/
class TypedConfigManager extends TypedDataManager implements TypedConfigManagerInterface {
@ -324,18 +323,18 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
parent::alterDefinitions($definitions);
$altered_schema = array_keys($definitions);
if ($discovered_schema != $altered_schema) {
$added_keys = array_diff($altered_schema, $discovered_schema);
$removed_keys = array_diff($discovered_schema, $altered_schema);
$added_keys = implode(',', array_diff($altered_schema, $discovered_schema));
$removed_keys = implode(',', array_diff($discovered_schema, $altered_schema));
if (!empty($added_keys) && !empty($removed_keys)) {
$message = 'Invoking hook_config_schema_info_alter() has added (@added) and removed (@removed) schema definitions';
$message = "Invoking hook_config_schema_info_alter() has added ($added_keys) and removed ($removed_keys) schema definitions";
}
elseif (!empty($added_keys)) {
$message = 'Invoking hook_config_schema_info_alter() has added (@added) schema definitions';
$message = "Invoking hook_config_schema_info_alter() has added ($added_keys) schema definitions";
}
else {
$message = 'Invoking hook_config_schema_info_alter() has removed (@removed) schema definitions';
$message = "Invoking hook_config_schema_info_alter() has removed ($removed_keys) schema definitions";
}
throw new ConfigSchemaAlterException(SafeMarkup::format($message, ['@added' => implode(',', $added_keys), '@removed' => implode(',', $removed_keys)]));
throw new ConfigSchemaAlterException($message);
}
}

View file

@ -12,9 +12,12 @@ use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
/**
* Defines an interface for typed configuration manager.
* Defines an interface for managing config schema type plugins.
*
* @package Drupal\Core\Config
* @see \Drupal\Core\Config\TypedConfigManager
* @see \Drupal\Core\Config\Schema\ConfigSchemaDiscovery
* @see hook_config_schema_info_alter()
* @see https://www.drupal.org/node/1905070
*/
Interface TypedConfigManagerInterface extends PluginManagerInterface, CachedDiscoveryInterface {

View file

@ -37,10 +37,6 @@ class ContentNegotiation {
return $request->query->get('_format');
}
if ($request->isXmlHttpRequest()) {
return 'ajax';
}
// Do HTML last so that it always wins.
return 'html';
}

View file

@ -10,6 +10,9 @@ namespace Drupal\Core;
use Drupal\Core\Cache\Context\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Core\DependencyInjection\Compiler\GuzzleMiddlewarePass;
use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass;
use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteEnhancers;
use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteFilters;
use Drupal\Core\DependencyInjection\Compiler\DependencySerializationTraitPass;
@ -60,6 +63,8 @@ class CoreServiceProvider implements ServiceProviderInterface {
// list-building passes are operating on the post-alter services list.
$container->addCompilerPass(new ModifyServiceDefinitionsPass());
$container->addCompilerPass(new ProxyServicesPass());
$container->addCompilerPass(new BackendCompilerPass());
$container->addCompilerPass(new StackedKernelPass());
@ -71,6 +76,7 @@ class CoreServiceProvider implements ServiceProviderInterface {
// Collect tagged handler services as method calls on consumer services.
$container->addCompilerPass(new TaggedHandlersPass());
$container->addCompilerPass(new RegisterStreamWrappersPass());
$container->addCompilerPass(new GuzzleMiddlewarePass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
@ -85,6 +91,7 @@ class CoreServiceProvider implements ServiceProviderInterface {
// Add the compiler pass that will process the tagged services.
$container->addCompilerPass(new ListCacheBinsPass());
$container->addCompilerPass(new CacheContextsPass());
$container->addCompilerPass(new ContextProvidersPass());
// Register plugin managers.
$container->addCompilerPass(new PluginManagerPass());
@ -127,10 +134,10 @@ class CoreServiceProvider implements ServiceProviderInterface {
if (!drupal_valid_test_ua()) {
return;
}
// Add the HTTP request subscriber to Guzzle.
// Add the HTTP request middleware to Guzzle.
$container
->register('test.http_client.request_subscriber', 'Drupal\Core\Test\EventSubscriber\HttpRequestSubscriber')
->addTag('http_client_subscriber');
->register('test.http_client.middleware', 'Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware')
->addTag('http_client_middleware');
}
}

View file

@ -9,6 +9,8 @@ namespace Drupal\Core;
/**
* An interface for running cron tasks.
*
* @see https://www.drupal.org/cron
*/
interface CronInterface {

View file

@ -24,7 +24,7 @@ abstract class Connection {
*
* We need this information for later auditing and logging.
*
* @var string
* @var string|null
*/
protected $target = NULL;
@ -35,14 +35,14 @@ abstract class Connection {
* connection can be a single server or a cluster of primary and replicas
* (use target to pick between primary and replica).
*
* @var string
* @var string|null
*/
protected $key = NULL;
/**
* The current database logging object for this connection.
*
* @var Log
* @var \Drupal\Core\Database\Log|null
*/
protected $logger = NULL;
@ -111,7 +111,9 @@ abstract class Connection {
/**
* The schema object for this connection.
*
* @var object
* Set to NULL when the schema is destroyed.
*
* @var \Drupal\Core\Database\Schema|null
*/
protected $schema = NULL;
@ -138,6 +140,14 @@ abstract class Connection {
/**
* Constructs a Connection object.
*
* @param \PDO $connection
* An object of the PDO class representing a database connection.
* @param array $connection_options
* An array of options for the connection. May include the following:
* - prefix
* - namespace
* - Other driver-specific options.
*/
public function __construct(\PDO $connection, array $connection_options) {
// Initialize and prepare the connection prefix.
@ -221,7 +231,7 @@ abstract class Connection {
* that behavior and simply return NULL on failure, set this option to
* FALSE.
*
* @return
* @return array
* An array of default query options.
*/
protected function defaultOptions() {
@ -241,7 +251,7 @@ abstract class Connection {
* is for requesting the connection information of this specific
* open connection object.
*
* @return
* @return array
* An array of the connection information. The exact list of
* properties is driver-dependent.
*/
@ -252,9 +262,9 @@ abstract class Connection {
/**
* Set the list of prefixes used by this database connection.
*
* @param $prefix
* The prefixes, in any of the multiple forms documented in
* default.settings.php.
* @param array|string $prefix
* Either a single prefix, or an array of prefixes, in any of the multiple
* forms documented in default.settings.php.
*/
protected function setPrefix($prefix) {
if (is_array($prefix)) {
@ -289,10 +299,10 @@ abstract class Connection {
* tables, allowing Drupal to coexist with other systems in the same database
* and/or schema if necessary.
*
* @param $sql
* @param string $sql
* A string containing a partial or entire SQL query.
*
* @return
* @return string
* The properly-prefixed string.
*/
public function prefixTables($sql) {
@ -304,6 +314,9 @@ abstract class Connection {
*
* This function is for when you want to know the prefix of a table. This
* is not used in prefixTables due to performance reasons.
*
* @param string $table
* (optional) The table to find the prefix for.
*/
public function tablePrefix($table = 'default') {
if (isset($this->prefixes[$table])) {
@ -355,9 +368,8 @@ abstract class Connection {
* signature. We therefore also ensure that this function is only ever
* called once.
*
* @param $target
* The target this connection is for. Set to NULL (default) to disable
* logging entirely.
* @param string $target
* (optional) The target this connection is for.
*/
public function setTarget($target = NULL) {
if (!isset($this->target)) {
@ -368,8 +380,8 @@ abstract class Connection {
/**
* Returns the target this connection is associated with.
*
* @return
* The target string of this connection.
* @return string|null
* The target string of this connection, or NULL if no target is set.
*/
public function getTarget() {
return $this->target;
@ -378,7 +390,7 @@ abstract class Connection {
/**
* Tells this connection object what its key is.
*
* @param $target
* @param string $key
* The key this connection is for.
*/
public function setKey($key) {
@ -390,8 +402,8 @@ abstract class Connection {
/**
* Returns the key this connection is associated with.
*
* @return
* The key of this connection.
* @return string|null
* The key of this connection, or NULL if no key is set.
*/
public function getKey() {
return $this->key;
@ -400,7 +412,7 @@ abstract class Connection {
/**
* Associates a logging object with this connection.
*
* @param $logger
* @param \Drupal\Core\Database\Log $logger
* The logging object we want to use.
*/
public function setLogger(Log $logger) {
@ -410,7 +422,7 @@ abstract class Connection {
/**
* Gets the current logging object for this connection.
*
* @return \Drupal\Core\Database\Log
* @return \Drupal\Core\Database\Log|null
* The current logging object for this connection. If there isn't one,
* NULL is returned.
*/
@ -424,12 +436,12 @@ abstract class Connection {
* This information is exposed to all database drivers, although it is only
* useful on some of them. This method is table prefix-aware.
*
* @param $table
* @param string $table
* The table name to use for the sequence.
* @param $field
* @param string $field
* The field name to use for the sequence.
*
* @return
* @return string
* A table prefix-parsed string for the sequence name.
*/
public function makeSequenceName($table, $field) {
@ -441,10 +453,10 @@ abstract class Connection {
*
* The comment string will be sanitized to avoid SQL injection attacks.
*
* @param $comments
* @param string[] $comments
* An array of query comment strings.
*
* @return
* @return string
* A sanitized comment string.
*/
public function makeComment($comments) {
@ -483,10 +495,10 @@ abstract class Connection {
* Unless the comment is sanitised first, the SQL server would drop the
* node table and ignore the rest of the SQL statement.
*
* @param $comment
* @param string $comment
* A query comment string.
*
* @return
* @return string
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
@ -500,7 +512,7 @@ abstract class Connection {
* query. All queries executed by Drupal are executed as PDO prepared
* statements.
*
* @param $query
* @param string|\Drupal\Core\Database\StatementInterface $query
* The query to execute. In most cases this will be a string containing
* an SQL query with placeholders. An already-prepared instance of
* StatementInterface may also be passed in order to allow calling
@ -509,26 +521,36 @@ abstract class Connection {
* It is extremely rare that module code will need to pass a statement
* object to this method. It is used primarily for database drivers for
* databases that require special LOB field handling.
* @param $args
* @param array $args
* An array of arguments for the prepared statement. If the prepared
* statement uses ? placeholders, this array must be an indexed array.
* If it contains named placeholders, it must be an associative array.
* @param $options
* An associative array of options to control how the query is run. See
* the documentation for DatabaseConnection::defaultOptions() for details.
* @param array $options
* An associative array of options to control how the query is run. The
* given options will be merged with self::defaultOptions(). See the
* documentation for self::defaultOptions() for details.
* Typically, $options['return'] will be set by a default or by a query
* builder, and should not be set by a user.
*
* @return \Drupal\Core\Database\StatementInterface|int|null
* This method will return one of: the executed statement, the number of
* rows affected by the query (not the number matched), or the generated
* insert ID of the last query, depending on the value of
* $options['return']. Typically that value will be set by default or a
* query builder and should not be set by a user. If there is an error,
* this method will return NULL and may throw an exception if
* $options['throw_exception'] is TRUE.
* This method will return one of the following:
* - If either $options['return'] === self::RETURN_STATEMENT, or
* $options['return'] is not set (due to self::defaultOptions()),
* returns the executed statement.
* - If $options['return'] === self::RETURN_AFFECTED,
* returns the number of rows affected by the query
* (not the number matched).
* - If $options['return'] === self::RETURN_INSERT_ID,
* returns the generated insert ID of the last query.
* - If either $options['return'] === self::RETURN_NULL, or
* an exception occurs and $options['throw_exception'] evaluates to FALSE,
* returns NULL.
*
* @throws \Drupal\Core\Database\DatabaseExceptionWrapper
* @throws \Drupal\Core\Database\IntegrityConstraintViolationException
* @throws \InvalidArgumentException
*
* @see \Drupal\Core\Database\Connection::defaultOptions()
*/
public function query($query, array $args = array(), $options = array()) {
// Use default values if not already set.
@ -628,6 +650,13 @@ abstract class Connection {
*
* @return bool
* TRUE if the query was modified, FALSE otherwise.
*
* @throws \InvalidArgumentException
* This exception is thrown when:
* - A placeholder that ends in [] is supplied, and the supplied value is
* not an array.
* - A placeholder that does not end in [] is supplied, and the supplied
* value is an array.
*/
protected function expandArguments(&$query, &$args) {
$modified = FALSE;
@ -700,12 +729,12 @@ abstract class Connection {
/**
* Prepares and returns a SELECT query object.
*
* @param $table
* @param string $table
* The base table for this query, that is, the first table in the FROM
* clause. This table will also be used as the "base" table for query_alter
* hook implementations.
* @param $alias
* The alias of the base table of this query.
* @param string $alias
* (optional) The alias of the base table of this query.
* @param $options
* An array of options on the query.
*
@ -724,8 +753,10 @@ abstract class Connection {
/**
* Prepares and returns an INSERT query object.
*
* @param $options
* An array of options on the query.
* @param string $table
* The table to use for the insert statement.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\Query\Insert
* A new Insert query object.
@ -740,8 +771,10 @@ abstract class Connection {
/**
* Prepares and returns a MERGE query object.
*
* @param $options
* An array of options on the query.
* @param string $table
* The table to use for the merge statement.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\Query\Merge
* A new Merge query object.
@ -757,8 +790,10 @@ abstract class Connection {
/**
* Prepares and returns an UPDATE query object.
*
* @param $options
* An array of options on the query.
* @param string $table
* The table to use for the update statement.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\Query\Update
* A new Update query object.
@ -773,8 +808,10 @@ abstract class Connection {
/**
* Prepares and returns a DELETE query object.
*
* @param $options
* An array of options on the query.
* @param string $table
* The table to use for the delete statement.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\Query\Delete
* A new Delete query object.
@ -789,8 +826,10 @@ abstract class Connection {
/**
* Prepares and returns a TRUNCATE query object.
*
* @param $options
* An array of options on the query.
* @param string $table
* The table to use for the truncate statement.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\Query\Truncate
* A new Truncate query object.
@ -825,8 +864,11 @@ abstract class Connection {
* For some database drivers, it may also wrap the database name in
* database-specific escape characters.
*
* @param string $database
* An unsanitized database name.
*
* @return string
* The sanitized database name string.
* The sanitized database name.
*/
public function escapeDatabase($database) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $database);
@ -839,8 +881,11 @@ abstract class Connection {
* For some database drivers, it may also wrap the table name in
* database-specific escape characters.
*
* @return
* The sanitized table name string.
* @param string $table
* An unsanitized table name.
*
* @return string
* The sanitized table name.
*/
public function escapeTable($table) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
@ -853,8 +898,11 @@ abstract class Connection {
* For some database drivers, it may also wrap the field name in
* database-specific escape characters.
*
* @return
* The sanitized field name string.
* @param string $field
* An unsanitized field name.
*
* @return string
* The sanitized field name.
*/
public function escapeField($field) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
@ -868,8 +916,11 @@ abstract class Connection {
* DatabaseConnection::escapeTable(), this doesn't allow the period (".")
* because that is not allowed in aliases.
*
* @return
* The sanitized field name string.
* @param string $field
* An unsanitized alias name.
*
* @return string
* The sanitized alias name.
*/
public function escapeAlias($field) {
return preg_replace('/[^A-Za-z0-9_]+/', '', $field);
@ -894,10 +945,10 @@ abstract class Connection {
* Backslash is defined as escape character for LIKE patterns in
* Drupal\Core\Database\Query\Condition::mapConditionOperator().
*
* @param $string
* @param string $string
* The string to escape.
*
* @return
* @return string
* The escaped string.
*/
public function escapeLike($string) {
@ -907,7 +958,7 @@ abstract class Connection {
/**
* Determines if there is an active transaction open.
*
* @return
* @return bool
* TRUE if we're currently in a transaction, FALSE otherwise.
*/
public function inTransaction() {
@ -915,7 +966,10 @@ abstract class Connection {
}
/**
* Determines current transaction depth.
* Determines the current transaction depth.
*
* @return int
* The current transaction depth.
*/
public function transactionDepth() {
return count($this->transactionLayers);
@ -924,8 +978,8 @@ abstract class Connection {
/**
* Returns a new DatabaseTransaction object on this connection.
*
* @param $name
* Optional name of the savepoint.
* @param string $name
* (optional) The name of the savepoint.
*
* @return \Drupal\Core\Database\Transaction
* A Transaction object.
@ -942,10 +996,11 @@ abstract class Connection {
*
* This method throws an exception if no transaction is active.
*
* @param $savepoint_name
* The name of the savepoint. The default, 'drupal_transaction', will roll
* the entire transaction back.
* @param string $savepoint_name
* (optional) The name of the savepoint. The default, 'drupal_transaction',
* will roll the entire transaction back.
*
* @throws \Drupal\Core\Database\TransactionOutOfOrderException
* @throws \Drupal\Core\Database\TransactionNoActiveException
*
* @see \Drupal\Core\Database\Transaction::rollback()
@ -997,6 +1052,9 @@ abstract class Connection {
*
* If no transaction is already active, we begin a new transaction.
*
* @param string $name
* The name of the transaction.
*
* @throws \Drupal\Core\Database\TransactionNameNonUniqueException
*
* @see \Drupal\Core\Database\Transaction
@ -1026,8 +1084,8 @@ abstract class Connection {
* back the transaction as necessary. If no transaction is active, we return
* because the transaction may have manually been rolled back.
*
* @param $name
* The name of the savepoint
* @param string $name
* The name of the savepoint.
*
* @throws \Drupal\Core\Database\TransactionNoActiveException
* @throws \Drupal\Core\Database\TransactionCommitFailedException
@ -1083,16 +1141,17 @@ abstract class Connection {
* separate parameters so that they can be properly escaped to avoid SQL
* injection attacks.
*
* @param $query
* @param string $query
* A string containing an SQL query.
* @param $args
* An array of values to substitute into the query at placeholder markers.
* @param $from
* @param int $from
* The first result row to return.
* @param $count
* @param int $count
* The maximum number of result rows to return.
* @param $options
* An array of options on the query.
* @param array $args
* (optional) An array of values to substitute into the query at placeholder
* markers.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\StatementInterface
* A database query result resource, or NULL if the query was not executed
@ -1103,7 +1162,7 @@ abstract class Connection {
/**
* Generates a temporary table name.
*
* @return
* @return string
* A table name.
*/
protected function generateTemporaryTableName() {
@ -1122,15 +1181,17 @@ abstract class Connection {
* Note that if you need to know how many results were returned, you should do
* a SELECT COUNT(*) on the temporary table afterwards.
*
* @param $query
* @param string $query
* A string containing a normal SELECT SQL query.
* @param $args
* An array of values to substitute into the query at placeholder markers.
* @param $options
* An associative array of options to control how the query is run. See
* the documentation for DatabaseConnection::defaultOptions() for details.
* @param array $args
* (optional) An array of values to substitute into the query at placeholder
* markers.
* @param array $options
* (optional) An associative array of options to control how the query is
* run. See the documentation for DatabaseConnection::defaultOptions() for
* details.
*
* @return
* @return string
* The name of the temporary table.
*/
abstract function queryTemporary($query, array $args = array(), array $options = array());
@ -1142,6 +1203,9 @@ abstract class Connection {
* instance, there could be two MySQL drivers, mysql and mysql_mock. This
* function would return different values for each, but both would return
* "mysql" for databaseType().
*
* @return string
* The type of database driver.
*/
abstract public function driver();
@ -1155,7 +1219,7 @@ abstract class Connection {
/**
* Determines if this driver supports transactions.
*
* @return
* @return bool
* TRUE if this connection supports transactions, FALSE otherwise.
*/
public function supportsTransactions() {
@ -1167,7 +1231,7 @@ abstract class Connection {
*
* DDL queries are those that change the schema, such as ALTER queries.
*
* @return
* @return bool
* TRUE if this connection supports transactions for DDL queries, FALSE
* otherwise.
*/
@ -1199,7 +1263,7 @@ abstract class Connection {
* overridable lookup function. Database connections should define only
* those operators they wish to be handled differently than the default.
*
* @param $operator
* @param string $operator
* The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
*
* @return
@ -1226,7 +1290,7 @@ abstract class Connection {
}
/**
* Retrieves an unique id from a given sequence.
* Retrieves an unique ID from a given sequence.
*
* Use this function if for some reason you can't use a serial field. For
* example, MySQL has no ways of reading of the current value of a sequence
@ -1234,9 +1298,9 @@ abstract class Connection {
* value. Or sometimes you just need a unique integer.
*
* @param $existing_id
* After a database import, it might be that the sequences table is behind,
* so by passing in the maximum existing id, it can be assured that we
* never issue the same id.
* (optional) After a database import, it might be that the sequences table
* is behind, so by passing in the maximum existing ID, it can be assured
* that we never issue the same ID.
*
* @return
* An integer number larger than any number returned by earlier calls and

View file

@ -14,6 +14,7 @@ use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\TransactionCommitFailedException;
use Drupal\Core\Database\DatabaseException;
use Drupal\Core\Database\Connection as DatabaseConnection;
use Drupal\Component\Utility\Unicode;
/**
* @addtogroup database
@ -34,6 +35,16 @@ class Connection extends DatabaseConnection {
*/
protected $needsCleanup = FALSE;
/**
* The minimal possible value for the max_allowed_packet setting of MySQL.
*
* @link https://mariadb.com/kb/en/mariadb/server-system-variables/#max_allowed_packet
* @link https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_allowed_packet
*
* @var int
*/
const MIN_MAX_ALLOWED_PACKET = 1024;
/**
* Constructs a Connection object.
*/
@ -49,6 +60,24 @@ class Connection extends DatabaseConnection {
$this->connectionOptions = $connection_options;
}
/**
* {@inheritdoc}
*/
public function query($query, array $args = array(), $options = array()) {
try {
return parent::query($query, $args, $options);
} catch (DatabaseException $e) {
if ($e->getPrevious()->errorInfo[1] == 1153) {
// If a max_allowed_packet error occurs the message length is truncated.
// This should prevent the error from recurring if the exception is
// logged to the database using dblog or the like.
$message = Unicode::truncateBytes($e->getMessage(), self::MIN_MAX_ALLOWED_PACKET);
$e = new DatabaseExceptionWrapper($message, $e->getCode(), $e->getPrevious());
}
throw $e;
}
}
/**
* {@inheritdoc}
*/
@ -278,6 +307,7 @@ class Connection extends DatabaseConnection {
}
}
}
}

View file

@ -31,6 +31,27 @@ class Connection extends DatabaseConnection {
*/
const DATABASE_NOT_FOUND = 7;
/**
* The list of PostgreSQL reserved key words.
*
* @see http://www.postgresql.org/docs/9.4/static/sql-keywords-appendix.html
*/
protected $postgresqlReservedKeyWords = ['all', 'analyse', 'analyze', 'and',
'any', 'array', 'as', 'asc', 'asymmetric', 'authorization', 'binary', 'both',
'case', 'cast', 'check', 'collate', 'collation', 'column', 'concurrently',
'constraint', 'create', 'cross', 'current_catalog', 'current_date',
'current_role', 'current_schema', 'current_time', 'current_timestamp',
'current_user', 'default', 'deferrable', 'desc', 'distinct', 'do', 'else',
'end', 'except', 'false', 'fetch', 'for', 'foreign', 'freeze', 'from', 'full',
'grant', 'group', 'having', 'ilike', 'in', 'initially', 'inner', 'intersect',
'into', 'is', 'isnull', 'join', 'lateral', 'leading', 'left', 'like', 'limit',
'localtime', 'localtimestamp', 'natural', 'not', 'notnull', 'null', 'offset',
'on', 'only', 'or', 'order', 'outer', 'over', 'overlaps', 'placing',
'primary', 'references', 'returning', 'right', 'select', 'session_user',
'similar', 'some', 'symmetric', 'table', 'then', 'to', 'trailing', 'true',
'union', 'unique', 'user', 'using', 'variadic', 'verbose', 'when', 'where',
'window', 'with'];
/**
* Constructs a connection object.
*/
@ -167,6 +188,10 @@ class Connection extends DatabaseConnection {
// Quote the field name for case-sensitivity.
$escaped = '"' . $escaped . '"';
}
elseif (in_array(strtolower($escaped), $this->postgresqlReservedKeyWords)) {
// Quote the field name for PostgreSQL reserved key words.
$escaped = '"' . $escaped . '"';
}
return $escaped;
}
@ -181,6 +206,10 @@ class Connection extends DatabaseConnection {
if (preg_match('/[A-Z]/', $escaped)) {
$escaped = '"' . $escaped . '"';
}
elseif (in_array(strtolower($escaped), $this->postgresqlReservedKeyWords)) {
// Quote the alias name for PostgreSQL reserved key words.
$escaped = '"' . $escaped . '"';
}
return $escaped;
}
@ -195,6 +224,10 @@ class Connection extends DatabaseConnection {
if (preg_match('/[A-Z]/', $escaped)) {
$escaped = '"' . $escaped . '"';
}
elseif (in_array(strtolower($escaped), $this->postgresqlReservedKeyWords)) {
// Quote the table name for PostgreSQL reserved key words.
$escaped = '"' . $escaped . '"';
}
return $escaped;
}

View file

@ -117,6 +117,8 @@ class Insert extends QueryInsert {
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$insert_fields = array_map(function($f) { return $this->connection->escapeField($f); }, $insert_fields);
// If we're selecting from a SelectQuery, finish building the query and
// pass it back, as any remaining options are irrelevant.
if (!empty($this->fromQuery)) {
@ -154,4 +156,5 @@ class Insert extends QueryInsert {
return $query;
}
}

View file

@ -51,7 +51,7 @@ class Tasks extends InstallTasks {
* {@inheritdoc}
*/
public function minimumVersion() {
return '8.3';
return '9.1.2';
}
/**

View file

@ -329,7 +329,7 @@ class Schema extends DatabaseSchema {
}
if (!empty($field['unsigned'])) {
// Unsigned datatypes are not supported in PostgreSQL 8.3. In MySQL,
// Unsigned datatypes are not supported in PostgreSQL 9.1. In MySQL,
// they are used to ensure a positive number is inserted and it also
// doubles the maximum integer size that can be stored in a field.
// The PostgreSQL schema in Drupal creates a check constraint
@ -569,7 +569,7 @@ class Schema extends DatabaseSchema {
}
public function indexExists($table, $name) {
// Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html
// Details http://www.postgresql.org/docs/9.1/interactive/view-pg-indexes.html
$index_name = $this->ensureIdentifiersLength($table, $name, 'idx');
// Remove leading and trailing quotes because the index name is in a WHERE
// clause and not used as an identifier.

View file

@ -22,7 +22,7 @@ use Drupal\Core\Database\StatementInterface;
class Statement extends StatementPrefetch implements StatementInterface {
/**
* SQLite specific implementation of getStatement().
* {@inheritdoc}
*
* The PDO SQLite layer doesn't replace numeric placeholders in queries
* correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
@ -87,6 +87,9 @@ class Statement extends StatementPrefetch implements StatementInterface {
return $this->pdoConnection->prepare($query);
}
/**
* {@inheritdoc}
*/
public function execute($args = array(), $options = array()) {
try {
$return = parent::execute($args, $options);

View file

@ -17,6 +17,8 @@ use Drupal\Core\Database\Connection;
*/
class Delete extends Query implements ConditionInterface {
use QueryConditionTrait;
/**
* The table from which to delete.
*
@ -24,15 +26,6 @@ class Delete extends Query implements ConditionInterface {
*/
protected $table;
/**
* The condition object for this query.
*
* Condition handling is handled via composition.
*
* @var Condition
*/
protected $condition;
/**
* Constructs a Delete object.
*
@ -51,82 +44,6 @@ class Delete extends Query implements ConditionInterface {
$this->condition = new Condition('AND');
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = '=') {
$this->condition->condition($field, $value, $operator);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNull().
*/
public function isNull($field) {
$this->condition->isNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNotNull().
*/
public function isNotNull($field) {
$this->condition->isNotNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::exists().
*/
public function exists(SelectInterface $select) {
$this->condition->exists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::notExists().
*/
public function notExists(SelectInterface $select) {
$this->condition->notExists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::conditions().
*/
public function &conditions() {
return $this->condition->conditions();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::arguments().
*/
public function arguments() {
return $this->condition->arguments();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::where().
*/
public function where($snippet, $args = array()) {
$this->condition->where($snippet, $args);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compile().
*/
public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, $queryPlaceholder);
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
}
/**
* Executes the DELETE query.
*

View file

@ -49,6 +49,9 @@ use Drupal\Core\Database\IntegrityConstraintViolationException;
* fields() and updateFields().
*/
class Merge extends Query implements ConditionInterface {
use QueryConditionTrait;
/**
* Returned by execute() if an INSERT query has been executed.
*/
@ -339,82 +342,6 @@ class Merge extends Query implements ConditionInterface {
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = '=') {
$this->condition->condition($field, $value, $operator);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNull().
*/
public function isNull($field) {
$this->condition->isNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNotNull().
*/
public function isNotNull($field) {
$this->condition->isNotNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::exists().
*/
public function exists(SelectInterface $select) {
$this->condition->exists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::notExists().
*/
public function notExists(SelectInterface $select) {
$this->condition->notExists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::conditions().
*/
public function &conditions() {
return $this->condition->conditions();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::arguments().
*/
public function arguments() {
return $this->condition->arguments();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::where().
*/
public function where($snippet, $args = array()) {
$this->condition->where($snippet, $args);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compile().
*/
public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, $queryPlaceholder);
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
}
/**
* Implements PHP magic __toString method to convert the query to a string.
*

View file

@ -180,25 +180,4 @@ abstract class Query implements PlaceholderInterface {
return $this->comments;
}
/**
* {@inheritdoc}
*/
public function conditionGroupFactory($conjunction = 'AND') {
return new Condition($conjunction);
}
/**
* {@inheritdoc}
*/
public function andConditionGroup() {
return $this->conditionGroupFactory('AND');
}
/**
* {@inheritdoc}
*/
public function orConditionGroup() {
return $this->conditionGroupFactory('OR');
}
}

View file

@ -0,0 +1,125 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Query\QueryConditionTrait.
*/
namespace Drupal\Core\Database\Query;
use Drupal\Core\Database\Connection;
/**
* Provides an implementation of ConditionInterface.
*
* @see \Drupal\Core\Database\Query\ConditionInterface
*/
trait QueryConditionTrait {
/**
* The condition object for this query.
*
* Condition handling is handled via composition.
*
* @var \Drupal\Core\Database\Query\Condition
*/
protected $condition;
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = '=') {
$this->condition->condition($field, $value, $operator);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNull().
*/
public function isNull($field) {
$this->condition->isNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNotNull().
*/
public function isNotNull($field) {
$this->condition->isNotNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::exists().
*/
public function exists(SelectInterface $select) {
$this->condition->exists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::notExists().
*/
public function notExists(SelectInterface $select) {
$this->condition->notExists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::conditions().
*/
public function &conditions() {
return $this->condition->conditions();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::arguments().
*/
public function arguments() {
return $this->condition->arguments();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::where().
*/
public function where($snippet, $args = array()) {
$this->condition->where($snippet, $args);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compile().
*/
public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
$this->condition->compile($connection, $queryPlaceholder);
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::conditionGroupFactory().
*/
public function conditionGroupFactory($conjunction = 'AND') {
return new Condition($conjunction);
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::andConditionGroup().
*/
public function andConditionGroup() {
return $this->conditionGroupFactory('AND');
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::orConditionGroup().
*/
public function orConditionGroup() {
return $this->conditionGroupFactory('OR');
}
}

View file

@ -18,6 +18,8 @@ use Drupal\Core\Database\Connection;
*/
class Select extends Query implements SelectInterface {
use QueryConditionTrait;
/**
* The fields to SELECT.
*
@ -71,13 +73,6 @@ class Select extends Query implements SelectInterface {
*/
protected $group = array();
/**
* The conditional object for the WHERE clause.
*
* @var \Drupal\Core\Database\Query\Condition
*/
protected $where;
/**
* The conditional object for the HAVING clause.
*
@ -139,7 +134,7 @@ class Select extends Query implements SelectInterface {
$options['return'] = Database::RETURN_STATEMENT;
parent::__construct($connection, $options);
$conjunction = isset($options['conjunction']) ? $options['conjunction'] : 'AND';
$this->where = new Condition($conjunction);
$this->condition = new Condition($conjunction);
$this->having = new Condition($conjunction);
$this->addJoin(NULL, $table, $alias);
}
@ -188,21 +183,6 @@ class Select extends Query implements SelectInterface {
return isset($this->alterMetaData[$key]) ? $this->alterMetaData[$key] : NULL;
}
/**
* {@inheritdoc}
*/
public function condition($field, $value = NULL, $operator = '=') {
$this->where->condition($field, $value, $operator);
return $this;
}
/**
* {@inheritdoc}
*/
public function &conditions() {
return $this->where->conditions();
}
/**
* {@inheritdoc}
*/
@ -211,7 +191,7 @@ class Select extends Query implements SelectInterface {
return NULL;
}
$args = $this->where->arguments() + $this->having->arguments();
$args = $this->condition->arguments() + $this->having->arguments();
foreach ($this->tables as $table) {
if ($table['arguments']) {
@ -238,51 +218,11 @@ class Select extends Query implements SelectInterface {
return $args;
}
/**
* {@inheritdoc}
*/
public function where($snippet, $args = array()) {
$this->where->where($snippet, $args);
return $this;
}
/**
* {@inheritdoc}
*/
public function isNull($field) {
$this->where->isNull($field);
return $this;
}
/**
* {@inheritdoc}
*/
public function isNotNull($field) {
$this->where->isNotNull($field);
return $this;
}
/**
* {@inheritdoc}
*/
public function exists(SelectInterface $select) {
$this->where->exists($select);
return $this;
}
/**
* {@inheritdoc}
*/
public function notExists(SelectInterface $select) {
$this->where->notExists($select);
return $this;
}
/**
* {@inheritdoc}
*/
public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
$this->where->compile($connection, $queryPlaceholder);
$this->condition->compile($connection, $queryPlaceholder);
$this->having->compile($connection, $queryPlaceholder);
foreach ($this->tables as $table) {
@ -302,7 +242,7 @@ class Select extends Query implements SelectInterface {
* {@inheritdoc}
*/
public function compiled() {
if (!$this->where->compiled() || !$this->having->compiled()) {
if (!$this->condition->compiled() || !$this->having->compiled()) {
return FALSE;
}
@ -463,6 +403,13 @@ class Select extends Query implements SelectInterface {
return $this->connection->escapeLike($string);
}
/**
* {@inheritdoc}
*/
public function escapeField($string) {
return $this->connection->escapeField($string);
}
/**
* {@inheritdoc}
*/
@ -885,9 +832,9 @@ class Select extends Query implements SelectInterface {
}
// WHERE
if (count($this->where)) {
if (count($this->condition)) {
// There is an implicit string cast on $this->condition.
$query .= "\nWHERE " . $this->where;
$query .= "\nWHERE " . $this->condition;
}
// GROUP BY
@ -943,7 +890,7 @@ class Select extends Query implements SelectInterface {
// want to clone the database connection object as that would duplicate the
// connection itself.
$this->where = clone($this->where);
$this->condition = clone($this->condition);
$this->having = clone($this->having);
foreach ($this->union as $key => $aggregate) {
$this->union[$key]['query'] = clone($aggregate['query']);

View file

@ -210,6 +210,14 @@ class SelectExtender implements SelectInterface {
return $this->query->escapeLike($string);
}
/**
* {@inheritdoc}
*/
public function escapeField($string) {
$this->query->escapeField($string);
return $this;
}
public function getArguments(PlaceholderInterface $queryPlaceholder = NULL) {
return $this->query->getArguments($queryPlaceholder);
}

View file

@ -140,6 +140,18 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
*/
public function escapeLike($string);
/**
* Escapes a field name string.
*
* Force all field names to be strictly alphanumeric-plus-underscore.
* For some database drivers, it may also wrap the field name in
* database-specific escape characters.
*
* @return
* The sanitized field name string.
*/
public function escapeField($string);
/**
* Compiles and returns an associative array of the arguments for this prepared statement.
*
@ -632,4 +644,5 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
* The called object.
*/
public function forUpdate($set = TRUE);
}

View file

@ -17,6 +17,8 @@ use Drupal\Core\Database\Connection;
*/
class Update extends Query implements ConditionInterface {
use QueryConditionTrait;
/**
* The table to update.
*
@ -38,15 +40,6 @@ class Update extends Query implements ConditionInterface {
*/
protected $arguments = array();
/**
* The condition object for this query.
*
* Condition handling is handled via composition.
*
* @var \Drupal\Core\Database\Query\Condition
*/
protected $condition;
/**
* Array of fields to update to an expression in case of a duplicate record.
*
@ -80,82 +73,6 @@ class Update extends Query implements ConditionInterface {
$this->condition = new Condition('AND');
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = '=') {
$this->condition->condition($field, $value, $operator);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNull().
*/
public function isNull($field) {
$this->condition->isNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::isNotNull().
*/
public function isNotNull($field) {
$this->condition->isNotNull($field);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::exists().
*/
public function exists(SelectInterface $select) {
$this->condition->exists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::notExists().
*/
public function notExists(SelectInterface $select) {
$this->condition->notExists($select);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::conditions().
*/
public function &conditions() {
return $this->condition->conditions();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::arguments().
*/
public function arguments() {
return $this->condition->arguments();
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::where().
*/
public function where($snippet, $args = array()) {
$this->condition->where($snippet, $args);
return $this;
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compile().
*/
public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, $queryPlaceholder);
}
/**
* Implements Drupal\Core\Database\Query\ConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
}
/**
* Adds a set of field->value pairs to be updated.
*
@ -256,13 +173,13 @@ class Update extends Query implements ConditionInterface {
$data['expression']->compile($this->connection, $this);
$data['expression'] = ' (' . $data['expression'] . ')';
}
$update_fields[] = $field . '=' . $data['expression'];
$update_fields[] = $this->connection->escapeField($field) . '=' . $data['expression'];
unset($fields[$field]);
}
$max_placeholder = 0;
foreach ($fields as $field => $value) {
$update_fields[] = $field . '=:db_update_placeholder_' . ($max_placeholder++);
$update_fields[] = $this->connection->escapeField($field) . '=:db_update_placeholder_' . ($max_placeholder++);
}
$query = $comments . 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields);

View file

@ -41,6 +41,9 @@ class Statement extends \PDOStatement implements StatementInterface {
$this->setFetchMode(\PDO::FETCH_OBJ);
}
/**
* {@inheritdoc}
*/
public function execute($args = array(), $options = array()) {
if (isset($options['fetch'])) {
if (is_string($options['fetch'])) {
@ -68,14 +71,23 @@ class Statement extends \PDOStatement implements StatementInterface {
return $return;
}
/**
* {@inheritdoc}
*/
public function getQueryString() {
return $this->queryString;
}
/**
* {@inheritdoc}
*/
public function fetchCol($index = 0) {
return $this->fetchAll(\PDO::FETCH_COLUMN, $index);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssoc($key, $fetch = NULL) {
$return = array();
if (isset($fetch)) {
@ -95,6 +107,9 @@ class Statement extends \PDOStatement implements StatementInterface {
return $return;
}
/**
* {@inheritdoc}
*/
public function fetchAllKeyed($key_index = 0, $value_index = 1) {
$return = array();
$this->setFetchMode(\PDO::FETCH_NUM);
@ -104,11 +119,17 @@ class Statement extends \PDOStatement implements StatementInterface {
return $return;
}
/**
* {@inheritdoc}
*/
public function fetchField($index = 0) {
// Call \PDOStatement::fetchColumn to fetch the field.
return $this->fetchColumn($index);
}
/**
* {@inheritdoc}
*/
public function fetchAssoc() {
// Call \PDOStatement::fetch to fetch the row.
return $this->fetch(\PDO::FETCH_ASSOC);
@ -127,4 +148,42 @@ class Statement extends \PDOStatement implements StatementInterface {
}
}
/**
* {@inheritdoc}
*/
public function setFetchMode($mode, $a1 = NULL, $a2 = array()) {
// Call \PDOStatement::setFetchMode to set fetch mode.
// \PDOStatement is picky about the number of arguments in some cases so we
// need to be pass the exact number of arguments we where given.
switch (func_num_args()) {
case 1:
return parent::setFetchMode($mode);
case 2:
return parent::setFetchMode($mode, $a1);
case 3:
default:
return parent::setFetchMode($mode, $a1, $a2);
}
}
/**
* {@inheritdoc}
*/
public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
// Call \PDOStatement::fetchAll to fetch all rows.
// \PDOStatement is picky about the number of arguments in some cases so we
// need to be pass the exact number of arguments we where given.
switch (func_num_args()) {
case 0:
return parent::fetchAll();
case 1:
return parent::fetchAll($mode);
case 2:
return parent::fetchAll($mode, $column_index);
case 3:
default:
return parent::fetchAll($mode, $column_index, $constructor_arguments);
}
}
}

View file

@ -28,14 +28,23 @@ class StatementEmpty implements \Iterator, StatementInterface {
*/
public $allowRowCount = FALSE;
/**
* {@inheritdoc}
*/
public function execute($args = array(), $options = array()) {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getQueryString() {
return '';
}
/**
* {@inheritdoc}
*/
public function rowCount() {
if ($this->allowRowCount) {
return 0;
@ -43,61 +52,102 @@ class StatementEmpty implements \Iterator, StatementInterface {
throw new RowCountException();
}
/**
* {@inheritdoc}
*/
public function setFetchMode($mode, $a1 = NULL, $a2 = array()) {
return;
}
/**
* {@inheritdoc}
*/
public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function fetchField($index = 0) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function fetchObject() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function fetchAssoc() {
return NULL;
}
function fetchAll($mode = NULL, $column_index = NULL, array $constructor_arguments = array()) {
/**
* {@inheritdoc}
*/
public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
return array();
}
/**
* {@inheritdoc}
*/
public function fetchCol($index = 0) {
return array();
}
/**
* {@inheritdoc}
*/
public function fetchAllKeyed($key_index = 0, $value_index = 1) {
return array();
}
/**
* {@inheritdoc}
*/
public function fetchAllAssoc($key, $fetch = NULL) {
return array();
}
/* Implementations of Iterator. */
/**
* {@inheritdoc}
*/
public function current() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function key() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function rewind() {
// Nothing to do: our DatabaseStatement can't be rewound.
}
/**
* {@inheritdoc}
*/
public function next() {
// Do nothing, since this is an always-empty implementation.
}
/**
* {@inheritdoc}
*/
public function valid() {
return FALSE;
}
}

View file

@ -10,11 +10,6 @@ namespace Drupal\Core\Database;
/**
* Represents a prepared statement.
*
* Some methods in that class are purposefully commented out. Due to a change in
* how PHP defines PDOStatement, we can't define a signature for those methods
* that will work the same way between versions older than 5.2.6 and later
* versions. See http://bugs.php.net/bug.php?id=42452 for more details.
*
* Child implementations should either extend PDOStatement:
* @code
* class Drupal\Core\Database\Driver\oracle\Statement extends PDOStatement implements Drupal\Core\Database\StatementInterface {}
@ -100,7 +95,7 @@ interface StatementInterface extends \Traversable {
* If $mode is PDO::FETCH_CLASS, the optional arguments to pass to the
* constructor.
*/
// public function setFetchMode($mode, $a1 = NULL, $a2 = array());
public function setFetchMode($mode, $a1 = NULL, $a2 = array());
/**
* Fetches the next row from a result set.
@ -119,7 +114,7 @@ interface StatementInterface extends \Traversable {
* @return
* A result, formatted according to $mode.
*/
// public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL);
public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL);
/**
* Returns a single field from the next record of a result set.
@ -138,7 +133,7 @@ interface StatementInterface extends \Traversable {
* The object will be of the class specified by StatementInterface::setFetchMode()
* or stdClass if not specified.
*/
// public function fetchObject();
public function fetchObject();
/**
* Fetches the next row and returns it as an associative array.
@ -165,7 +160,7 @@ interface StatementInterface extends \Traversable {
* @return
* An array of results.
*/
// function fetchAll($mode = NULL, $column_index = NULL, array $constructor_arguments);
function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL);
/**
* Returns an entire single column of a result set as an indexed array.

View file

@ -139,14 +139,7 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
/**
* Executes a prepared statement.
*
* @param $args
* An array of values with as many elements as there are bound parameters in the SQL statement being executed.
* @param $options
* An array of options for this query.
* @return
* TRUE on success, or FALSE on failure.
* {@inheritdoc}
*/
public function execute($args = array(), $options = array()) {
if (isset($options['fetch'])) {
@ -236,29 +229,29 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
/**
* Return the object's SQL query string.
* {@inheritdoc}
*/
public function getQueryString() {
return $this->queryString;
}
/**
* @see \PDOStatement::setFetchMode()
* {@inheritdoc}
*/
public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) {
$this->defaultFetchStyle = $fetchStyle;
switch ($fetchStyle) {
public function setFetchMode($mode, $a1 = NULL, $a2 = array()) {
$this->defaultFetchStyle = $mode;
switch ($mode) {
case \PDO::FETCH_CLASS:
$this->defaultFetchOptions['class'] = $a2;
if ($a3) {
$this->defaultFetchOptions['constructor_args'] = $a3;
$this->defaultFetchOptions['class'] = $a1;
if ($a2) {
$this->defaultFetchOptions['constructor_args'] = $a2;
}
break;
case \PDO::FETCH_COLUMN:
$this->defaultFetchOptions['column'] = $a2;
$this->defaultFetchOptions['column'] = $a1;
break;
case \PDO::FETCH_INTO:
$this->defaultFetchOptions['object'] = $a2;
$this->defaultFetchOptions['object'] = $a1;
break;
}
@ -274,8 +267,8 @@ class StatementPrefetch implements \Iterator, StatementInterface {
* array position in $this->data and format it according to $this->fetchStyle
* and $this->fetchMode.
*
* @return
* The current row formatted as requested.
* @return mixed
* The current row formatted as requested.
*/
public function current() {
if (isset($this->currentRow)) {
@ -327,16 +320,23 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
}
/* Implementations of Iterator. */
/**
* {@inheritdoc}
*/
public function key() {
return $this->currentKey;
}
/**
* {@inheritdoc}
*/
public function rewind() {
// Nothing to do: our DatabaseStatement can't be rewound.
}
/**
* {@inheritdoc}
*/
public function next() {
if (!empty($this->data)) {
$this->currentRow = reset($this->data);
@ -348,6 +348,9 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
}
/**
* {@inheritdoc}
*/
public function valid() {
return isset($this->currentRow);
}
@ -365,6 +368,9 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
}
/**
* {@inheritdoc}
*/
public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
if (isset($this->currentRow)) {
// Set the fetch parameter.
@ -398,10 +404,16 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
}
/**
* {@inheritdoc}
*/
public function fetchField($index = 0) {
return $this->fetchColumn($index);
}
/**
* {@inheritdoc}
*/
public function fetchObject($class_name = NULL, $constructor_args = array()) {
if (isset($this->currentRow)) {
if (!isset($class_name)) {
@ -427,6 +439,9 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
}
/**
* {@inheritdoc}
*/
public function fetchAssoc() {
if (isset($this->currentRow)) {
$result = $this->currentRow;
@ -438,14 +453,17 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
}
public function fetchAll($fetch_style = NULL, $fetch_column = NULL, $constructor_args = NULL) {
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
/**
* {@inheritdoc}
*/
public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
$this->fetchStyle = isset($mode) ? $mode : $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
if (isset($fetch_column)) {
$this->fetchOptions['column'] = $fetch_column;
if (isset($column_index)) {
$this->fetchOptions['column'] = $column_index;
}
if (isset($constructor_args)) {
$this->fetchOptions['constructor_args'] = $constructor_args;
if (isset($constructor_arguments)) {
$this->fetchOptions['constructor_args'] = $constructor_arguments;
}
$result = array();
@ -462,6 +480,9 @@ class StatementPrefetch implements \Iterator, StatementInterface {
return $result;
}
/**
* {@inheritdoc}
*/
public function fetchCol($index = 0) {
if (isset($this->columnNames[$index])) {
$result = array();
@ -477,6 +498,9 @@ class StatementPrefetch implements \Iterator, StatementInterface {
}
}
/**
* {@inheritdoc}
*/
public function fetchAllKeyed($key_index = 0, $value_index = 1) {
if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index]))
return array();
@ -493,6 +517,9 @@ class StatementPrefetch implements \Iterator, StatementInterface {
return $result;
}
/**
* {@inheritdoc}
*/
public function fetchAllAssoc($key, $fetch_style = NULL) {
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;

View file

@ -307,7 +307,8 @@
* specification). Each specification is an array containing the name of
* the referenced table ('table'), and an array of column mappings
* ('columns'). Column mappings are defined by key pairs ('source_column' =>
* 'referenced_column').
* 'referenced_column'). This key is for documentation purposes only; foreign
* keys are not created in the database, nor are they enforced by Drupal.
* - 'indexes': An associative array of indexes ('indexname' =>
* specification). Each specification is an array of one or more
* key column specifiers (see below) that form an index on the
@ -357,6 +358,8 @@
* 'unique keys' => array(
* 'vid' => array('vid'),
* ),
* // For documentation purposes only; foreign keys are not created in the
* // database.
* 'foreign keys' => array(
* 'node_revision' => array(
* 'table' => 'node_field_revision',
@ -467,7 +470,9 @@ function hook_query_TAG_alter(Drupal\Core\Database\Query\AlterableInterface $que
* creation and alteration of the supported database engines.
*
* See the Schema API Handbook at https://www.drupal.org/node/146843 for details
* on schema definition structures.
* on schema definition structures. Note that foreign key definitions are for
* documentation purposes only; foreign keys are not created in the database,
* nor are they enforced by Drupal.
*
* @return array
* A schema definition structure array. For each element of the
@ -517,6 +522,8 @@ function hook_schema() {
'nid_vid' => array('nid', 'vid'),
'vid' => array('vid'),
),
// For documentation purposes only; foreign keys are not created in the
// database.
'foreign keys' => array(
'node_revision' => array(
'table' => 'node_field_revision',

View file

@ -204,6 +204,11 @@ class DateFormatter {
$interval %= $value;
$granularity--;
}
elseif ($output) {
// Break if there was previous output but not any output at this level,
// to avoid skipping levels and getting output like "1 year 1 second".
break;
}
if ($granularity == 0) {
break;
@ -373,15 +378,21 @@ class DateFormatter {
// ourselves.
$interval_output = '';
$days = $interval->d;
if ($days >= 7) {
$weeks = floor($days / 7);
$weeks = floor($days / 7);
if ($weeks) {
$interval_output .= $this->formatPlural($weeks, '1 week', '@count weeks', array(), array('langcode' => $options['langcode']));
$days -= $weeks * 7;
$granularity--;
}
if ($granularity > 0 && $days > 0) {
if ((!$output || $weeks > 0) && $granularity > 0 && $days > 0) {
$interval_output .= ($interval_output ? ' ' : '') . $this->formatPlural($days, '1 day', '@count days', array(), array('langcode' => $options['langcode']));
}
else {
// If we did not output days, set the granularity to 0 so that we
// will not output hours and get things like "1 week 1 hour".
$granularity = 0;
}
break;
case 'h':
@ -397,9 +408,14 @@ class DateFormatter {
break;
}
$output .= ($output ? ' ' : '') . $interval_output;
$output .= ($output && $interval_output ? ' ' : '') . $interval_output;
$granularity--;
}
elseif ($output) {
// Break if there was previous output but not any output at this level,
// to avoid skipping levels and getting output like "1 year 1 second".
break;
}
if ($granularity <= 0) {
break;

View file

@ -335,10 +335,10 @@ class Datelist extends DateElementBase {
protected static function incrementRound(&$date, $increment) {
// Round minutes and seconds, if necessary.
if ($date instanceOf DrupalDateTime && $increment > 1) {
$day = intval(date_format($date, 'j'));
$hour = intval(date_format($date, 'H'));
$second = intval(round(intval(date_format($date, 's')) / $increment) * $increment);
$minute = intval(date_format($date, 'i'));
$day = intval($date->format('j'));
$hour = intval($date->format('H'));
$second = intval(round(intval($date->format('s')) / $increment) * $increment);
$minute = intval($date->format('i'));
if ($second == 60) {
$minute += 1;
$second = 0;
@ -348,12 +348,12 @@ class Datelist extends DateElementBase {
$hour += 1;
$minute = 0;
}
date_time_set($date, $hour, $minute, $second);
$date->setTime($hour, $minute, $second);
if ($hour == 24) {
$day += 1;
$year = date_format($date, 'Y');
$month = date_format($date, 'n');
date_date_set($date, $year, $month, $day);
$year = $date->format('Y');
$month = $date->format('n');
$date->setDate($year, $month, $day);
}
}
return $date;

View file

@ -268,6 +268,7 @@ class Datetime extends DateElementBase {
'#required' => $element['#required'],
'#size' => max(12, strlen($element['#value']['date'])),
'#error_no_message' => TRUE,
'#date_date_format' => $element['#date_date_format'],
);
// Allows custom callbacks to alter the element.

View file

@ -41,14 +41,14 @@ class DateFormat extends ConfigEntityBase implements DateFormatInterface {
*
* @var string
*/
public $id;
protected $id;
/**
* The human-readable name of the date format entity.
*
* @var string
*/
public $label;
protected $label;
/**
* The date format pattern.
@ -101,7 +101,7 @@ class DateFormat extends ConfigEntityBase implements DateFormatInterface {
/**
* {@inheritdoc}
*/
public function getCacheTags() {
public function getCacheTagsToInvalidate() {
return ['rendered'];
}

View file

@ -42,11 +42,25 @@ class BackendCompilerPass implements CompilerPassInterface {
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$default_backend = $container->hasParameter('default_backend') ? $container->getParameter('default_backend') : NULL;
// No default backend was configured, so continue as normal.
if (!isset($default_backend)) {
return;
if ($container->hasParameter('default_backend')) {
$default_backend = $container->getParameter('default_backend');
// Opt out from the default backend.
if (!$default_backend) {
return;
}
}
else {
try {
$default_backend = $container->get('database')->driver();
$container->set('database', NULL);
}
catch (\Exception $e) {
// If Drupal is not installed or a test doesn't define database there
// is nothing to override.
return;
}
}
foreach ($container->findTaggedServiceIds('backend_overridable') as $id => $attributes) {
// If the service is already an alias it is not the original backend, so

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds the context provider service IDs to the context manager.
*/
class ContextProvidersPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*
* Passes the service IDs of all context providers to the context repository.
*/
public function process(ContainerBuilder $container) {
$context_providers = [];
foreach (array_keys($container->findTaggedServiceIds('context_provider')) as $id) {
$context_providers[] = $id;
}
$definition = $container->getDefinition('context.repository');
$definition->addArgument($context_providers);
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\GuzzleMiddlewarePass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class GuzzleMiddlewarePass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$middleware_ids = array_keys($container->findTaggedServiceIds('http_client_middleware'));
$container->getDefinition('http_handler_stack_configurator')
->addArgument($middleware_ids);
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Drupal\Component\ProxyBuilder\ProxyBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Replaces all services with a lazy flag.
*/
class ProxyServicesPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
foreach ($container->getDefinitions() as $service_id => $definition) {
if ($definition->isLazy()) {
$proxy_class = ProxyBuilder::buildProxyClassName($definition->getClass());
if (class_exists($proxy_class)) {
// Copy the existing definition to a new entry.
$definition->setLazy(FALSE);
// Ensure that the service is accessible.
$definition->setPublic(TRUE);
$new_service_id = 'drupal.proxy_original_service.' . $service_id;
$container->setDefinition($new_service_id, $definition);
$container->register($service_id, $proxy_class)
->setArguments([new Reference('service_container'), $new_service_id]);
}
else {
$class_name = $definition->getClass();
// Find the root namespace.
$match = [];
preg_match('/([a-zA-Z0-9_]+\\\\[a-zA-Z0-9_]+)\\\\(.+)/', $class_name, $match);
$root_namespace = $match[1];
// Find the root namespace path.
$root_namespace_dir = '[namespace_root_path]';
$namespaces = $container->getParameter('container.namespaces');
// Hardcode Drupal Core, because it is not registered.
$namespaces['Drupal\Core'] = 'core/lib/Drupal/Core';
if (isset($namespaces[$root_namespace])) {
$root_namespace_dir = $namespaces[$root_namespace];
}
$message =<<<EOF
Missing proxy class '$proxy_class' for lazy service '$service_id'.
Use the following command to generate the proxy class:
php core/scripts/generate-proxy-class.php '$class_name' "$root_namespace_dir"
EOF;
trigger_error($message, E_USER_WARNING);
}
}
}
}
}

View file

@ -8,7 +8,6 @@
namespace Drupal\Core;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\ProxyBuilder\ProxyDumper;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
@ -24,7 +23,6 @@ use Drupal\Core\Http\TrustedHostsRequestFactory;
use Drupal\Core\Language\Language;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\ProxyBuilder\ProxyBuilder;
use Drupal\Core\Site\Settings;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -54,8 +52,6 @@ use Symfony\Component\Routing\Route;
*/
class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
const CONTAINER_BASE_CLASS = '\Drupal\Core\DependencyInjection\Container';
/**
* Holds the container instance.
*
@ -128,6 +124,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
*/
protected $allowDumping;
/**
* Whether the container needs to be rebuilt the next time it is initialized.
*
* @var bool
*/
protected $containerNeedsRebuild = FALSE;
/**
* Whether the container needs to be dumped once booting is complete.
*
@ -463,8 +466,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
$this->container->get('request_stack')->push($request);
// Set the allowed protocols once we have the config available.
$allowed_protocols = $this->container->get('config.factory')->get('system.filter')->get('protocols');
if (!isset($allowed_protocols)) {
$allowed_protocols = $this->container->getParameter('filter_protocols');
if (!$allowed_protocols) {
// \Drupal\Component\Utility\UrlHelper::filterBadProtocol() is called by
// the installer and update.php, in which case the configuration may not
// exist (yet). Provide a minimal default set of allowed protocols for
@ -697,11 +700,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
// If we haven't yet booted, we don't need to do anything: the new module
// list will take effect when boot() is called. If we have already booted,
// then rebuild the container in order to refresh the serviceProvider list
// and container.
// list will take effect when boot() is called. However we set a
// flag that the container needs a rebuild, so that a potentially cached
// container is not used. If we have already booted, then rebuild the
// container in order to refresh the serviceProvider list and container.
$this->containerNeedsRebuild = TRUE;
if ($this->booted) {
$this->initializeContainer(TRUE);
$this->initializeContainer();
}
}
@ -740,11 +745,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
/**
* Initializes the service container.
*
* @param bool $rebuild
* Force a container rebuild.
* @return \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected function initializeContainer($rebuild = FALSE) {
protected function initializeContainer() {
$this->containerNeedsDumping = FALSE;
$session_started = FALSE;
if (isset($this->container)) {
@ -766,7 +769,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
// If the module list hasn't already been set in updateModules and we are
// not forcing a rebuild, then try and load the container from the disk.
if (empty($this->moduleList) && !$rebuild) {
if (empty($this->moduleList) && !$this->containerNeedsRebuild) {
$fully_qualified_class_name = '\\' . $this->getClassNamespace() . '\\' . $this->getClassName();
// First, try to load from storage.
@ -783,6 +786,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
$container = $this->compileContainer();
}
// The container was rebuilt successfully.
$this->containerNeedsRebuild = FALSE;
$this->attachSynthetic($container);
$this->container = $container;
@ -807,7 +813,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
\Drupal::setContainer($this->container);
// If needs dumping flag was set, dump the container.
if ($this->containerNeedsDumping && !$this->dumpDrupalContainer($this->container, static::CONTAINER_BASE_CLASS)) {
$base_class = Settings::get('container_base_class', '\Drupal\Core\DependencyInjection\Container');
if ($this->containerNeedsDumping && !$this->dumpDrupalContainer($this->container, $base_class)) {
$this->container->get('logger.factory')->get('DrupalKernel')->notice('Container cannot be written to disk');
}
@ -1000,15 +1007,33 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
/**
* Force a container rebuild.
*
* @return \Symfony\Component\DependencyInjection\ContainerInterface
* {@inheritdoc}
*/
public function rebuildContainer() {
// Empty module properties and for them to be reloaded from scratch.
$this->moduleList = NULL;
$this->moduleData = array();
return $this->initializeContainer(TRUE);
$this->containerNeedsRebuild = TRUE;
return $this->initializeContainer();
}
/**
* {@inheritdoc}
*/
public function invalidateContainer() {
// An invalidated container needs a rebuild.
$this->containerNeedsRebuild = TRUE;
// If we have not yet booted, settings or bootstrap services might not yet
// be available. In that case the container will not be loaded from cache
// due to the above setting when the Kernel is booted.
if (!$this->booted) {
return;
}
// Also wipe the PHP Storage caches, so that the container is rebuilt
// for the next request.
$this->storage()->deleteAll();
}
/**
@ -1180,7 +1205,6 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
// Cache the container.
$dumper = new PhpDumper($container);
$dumper->setProxyDumper(new ProxyDumper(new ProxyBuilder()));
$class = $this->getClassName();
$namespace = $this->getClassNamespace();
$content = $dumper->dump([
@ -1336,8 +1360,6 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
*
* @return bool
* TRUE if the hostmame is valid, or FALSE otherwise.
*
* @todo Adjust per resolution to https://github.com/symfony/symfony/issues/12349
*/
public static function validateHostname(Request $request) {
// $request->getHost() can throw an UnexpectedValueException if it
@ -1380,7 +1402,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* @param array $host_patterns
* The array of trusted host patterns.
*
* @return boolean
* @return bool
* TRUE if the Host header is trusted, FALSE otherwise.
*
* @see https://www.drupal.org/node/1992030

View file

@ -96,6 +96,18 @@ interface DrupalKernelInterface extends HttpKernelInterface {
*/
public function updateModules(array $module_list, array $module_filenames = array());
/**
* Force a container rebuild.
*
* @return \Symfony\Component\DependencyInjection\ContainerInterface
*/
public function rebuildContainer();
/**
* Invalidate the service container for the next request.
*/
public function invalidateContainer();
/**
* Prepare the kernel for handling a request without handling the request.
*

View file

@ -138,12 +138,33 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
protected $isDefaultRevision = TRUE;
/**
* Holds entity keys like the ID, bundle and revision ID.
* Holds translatable entity keys such as the ID, bundle and revision ID.
*
* @var array
*/
protected $entityKeys = array();
/**
* Holds translatable entity keys such as the label.
*
* @var array
*/
protected $translatableEntityKeys = array();
/**
* Whether entity validation was performed.
*
* @var bool
*/
protected $validated = FALSE;
/**
* Whether entity validation is required before saving the entity.
*
* @var bool
*/
protected $validationRequired = FALSE;
/**
* Overrides Entity::__construct().
*/
@ -165,14 +186,36 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$this->values = $values;
foreach ($this->getEntityType()->getKeys() as $key => $field_name) {
if (isset($this->values[$field_name])) {
if (is_array($this->values[$field_name]) && isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
if (is_array($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'])) {
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'];
if (is_array($this->values[$field_name])) {
// We store untranslatable fields into an entity key without using a
// langcode key.
if (!$this->getFieldDefinition($field_name)->isTranslatable()) {
if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
if (is_array($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'])) {
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'];
}
}
else {
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT];
}
}
}
else {
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT];
// We save translatable fields such as the publishing status of a node
// into an entity key array keyed by langcode as a performance
// optimization, so we don't have to go through TypedData when we
// need these values.
foreach ($this->values[$field_name] as $langcode => $field_value) {
if (is_array($this->values[$field_name][$langcode])) {
if (isset($this->values[$field_name][$langcode][0]['value'])) {
$this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode][0]['value'];
}
}
else {
$this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode];
}
}
}
}
}
@ -220,7 +263,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
*/
public function setNewRevision($value = TRUE) {
if (!$this->getEntityType()->hasKey('revision')) {
throw new \LogicException(SafeMarkup::format('Entity type @entity_type does not support revisions.', ['@entity_type' => $this->getEntityTypeId()]));
throw new \LogicException("Entity type {$this->getEntityTypeId()} does not support revisions.");
}
if ($value && !$this->newRevision) {
@ -302,6 +345,23 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
return !empty($bundles[$this->bundle()]['translatable']) && !$this->getUntranslated()->language()->isLocked() && $this->languageManager()->isMultilingual();
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
// An entity requiring validation should not be saved if it has not been
// actually validated.
if ($this->validationRequired && !$this->validated) {
// @todo Make this an assertion in https://www.drupal.org/node/2408013.
throw new \LogicException('Entity validation was skipped.');
}
else {
$this->validated = FALSE;
}
parent::preSave($storage);
}
/**
* {@inheritdoc}
*/
@ -312,10 +372,26 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
* {@inheritdoc}
*/
public function validate() {
$this->validated = TRUE;
$violations = $this->getTypedData()->validate();
return new EntityConstraintViolationList($this, iterator_to_array($violations));
}
/**
* {@inheritdoc}
*/
public function isValidationRequired() {
return (bool) $this->validationRequired;
}
/**
* {@inheritdoc}
*/
public function setValidationRequired($required) {
$this->validationRequired = $required;
return $this;
}
/**
* Clear entity translation object cache to remove stale references.
*/
@ -388,15 +464,14 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
*/
protected function getTranslatedField($name, $langcode) {
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
$message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $this->activeLangcode)));
throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated.");
}
// Populate $this->fields to speed-up further look-ups and to keep track of
// fields objects, possibly holding changes to field values.
if (!isset($this->fields[$name][$langcode])) {
$definition = $this->getFieldDefinition($name);
if (!$definition) {
throw new \InvalidArgumentException('Field ' . SafeMarkup::checkPlain($name) . ' is unknown.');
throw new \InvalidArgumentException("Field $name is unknown.");
}
// Non-translatable fields are always stored with
// LanguageInterface::LANGCODE_DEFAULT as key.
@ -413,7 +488,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
if (isset($this->values[$name][$langcode])) {
$value = $this->values[$name][$langcode];
}
$field = \Drupal::service('plugin.manager.field.field_type')->createFieldItemList($this, $name, $value);
$field = \Drupal::service('plugin.manager.field.field_type')->createFieldItemList($this->getTranslation($langcode), $name, $value);
if ($default) {
// $this->defaultLangcode might not be set if we are initializing the
// default language code cache, in which case there is no valid
@ -454,6 +529,19 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
return $fields;
}
/**
* {@inheritdoc}
*/
public function getTranslatableFields($include_computed = TRUE) {
$fields = [];
foreach ($this->getFieldDefinitions() as $name => $definition) {
if (($include_computed || !$definition->isComputed()) && $definition->isTranslatable()) {
$fields[$name] = $this->get($name);
}
}
return $fields;
}
/**
* {@inheritdoc}
*/
@ -537,12 +625,12 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// Get the language code if the property exists.
// Try to read the value directly from the list of entity keys which got
// initialized in __construct(). This avoids creating a field item object.
if (isset($this->entityKeys['langcode'])) {
$this->defaultLangcode = $this->entityKeys['langcode'];
if (isset($this->translatableEntityKeys['langcode'][$this->activeLangcode])) {
$this->defaultLangcode = $this->translatableEntityKeys['langcode'][$this->activeLangcode];
}
elseif ($this->hasField($this->langcodeKey) && ($item = $this->get($this->langcodeKey)) && isset($item->language)) {
$this->defaultLangcode = $item->language->getId();
$this->entityKeys['langcode'] = $this->defaultLangcode;
$this->translatableEntityKeys['langcode'][$this->activeLangcode] = $this->defaultLangcode;
}
if (empty($this->defaultLangcode)) {
@ -583,8 +671,13 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// that check, as it ready only and must not change, unsetting it could
// lead to recursions.
if ($key = array_search($name, $this->getEntityType()->getKeys())) {
if (isset($this->entityKeys[$key]) && $key != 'bundle') {
unset($this->entityKeys[$key]);
if ($key != 'bundle') {
if (isset($this->entityKeys[$key])) {
unset($this->entityKeys[$key]);
}
elseif (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) {
unset($this->translatableEntityKeys[$key][$this->activeLangcode]);
}
}
}
@ -663,8 +756,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
}
if (empty($translation)) {
$message = 'Invalid translation language (@langcode) specified.';
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $langcode)));
throw new \InvalidArgumentException("Invalid translation language ($langcode) specified.");
}
return $translation;
@ -710,8 +802,6 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$translation->enforceIsNew = &$this->enforceIsNew;
$translation->newRevision = &$this->newRevision;
$translation->translationInitialize = FALSE;
// Reset language-dependent properties.
unset($translation->entityKeys['label']);
$translation->typedData = NULL;
return $translation;
@ -733,8 +823,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
public function addTranslation($langcode, array $values = array()) {
$this->getLanguages();
if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) {
$message = 'Invalid translation language (@langcode) specified.';
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $langcode)));
throw new \InvalidArgumentException("Invalid translation language ($langcode) specified.");
}
// Instantiate a new empty entity so default values will be populated in the
@ -784,8 +873,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED;
}
else {
$message = 'The specified translation (@langcode) cannot be removed.';
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $langcode)));
throw new \InvalidArgumentException("The specified translation ($langcode) cannot be removed.");
}
}
@ -918,8 +1006,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
*/
public function createDuplicate() {
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
$message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $this->activeLangcode)));
throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated.");
}
$duplicate = clone $this;
@ -962,7 +1049,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
}
foreach ($values as $langcode => $items) {
$this->fields[$name][$langcode] = clone $items;
$this->fields[$name][$langcode]->setContext($name, $this->getTypedData());
$this->fields[$name][$langcode]->setContext($name, $this->getTranslation($langcode)->getTypedData());
}
}
@ -1020,18 +1107,34 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
* The value of the entity key, NULL if not defined.
*/
protected function getEntityKey($key) {
if (!isset($this->entityKeys[$key]) || !array_key_exists($key, $this->entityKeys)) {
if ($this->getEntityType()->hasKey($key)) {
$field_name = $this->getEntityType()->getKey($key);
$property = $this->getFieldDefinition($field_name)->getFieldStorageDefinition()->getMainPropertyName();
$this->entityKeys[$key] = $this->get($field_name)->$property;
// If the value is known already, return it.
if (isset($this->entityKeys[$key])) {
return $this->entityKeys[$key];
}
if (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) {
return $this->translatableEntityKeys[$key][$this->activeLangcode];
}
// Otherwise fetch the value by creating a field object.
$value = NULL;
if ($this->getEntityType()->hasKey($key)) {
$field_name = $this->getEntityType()->getKey($key);
$definition = $this->getFieldDefinition($field_name);
$property = $definition->getFieldStorageDefinition()->getMainPropertyName();
$value = $this->get($field_name)->$property;
// Put it in the right array, depending on whether it is translatable.
if ($definition->isTranslatable()) {
$this->translatableEntityKeys[$key][$this->activeLangcode] = $value;
}
else {
$this->entityKeys[$key] = NULL;
$this->entityKeys[$key] = $value;
}
}
return $this->entityKeys[$key];
else {
$this->entityKeys[$key] = $value;
}
return $value;
}
/**

View file

@ -86,9 +86,6 @@ abstract class ContentEntityConfirmFormBase extends ContentEntityForm implements
'submit' => array(
'#type' => 'submit',
'#value' => $this->getConfirmText(),
'#validate' => array(
array($this, 'validate'),
),
'#submit' => array(
array($this, 'submitForm'),
),
@ -121,9 +118,10 @@ abstract class ContentEntityConfirmFormBase extends ContentEntityForm implements
/**
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
public function validateForm(array &$form, FormStateInterface $form_state) {
// Override the default validation implementation as it is not necessary
// nor possible to validate an entity in a confirmation form.
return $this->entity;
}
}

View file

@ -64,17 +64,28 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
/**
* {@inheritdoc}
*
* Note that extending classes should not override this method to add entity
* validation logic, but define further validation constraints using the
* entity validation API and/or provide a new validation constraint if
* necessary. This is the only way to ensure that the validation logic
* is correctly applied independently of form submissions; e.g., for REST
* requests.
* For more information about entity validation, see
* https://www.drupal.org/node/2015613.
*/
public function validate(array $form, FormStateInterface $form_state) {
public function buildEntity(array $form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = parent::buildEntity($form, $form_state);
// Mark the entity as requiring validation.
$entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated'));
return $entity;
}
/**
* {@inheritdoc}
*
* Button-level validation handlers are highly discouraged for entity forms,
* as they will prevent entity validation from running. If the entity is going
* to be saved during the form submission, this method should be manually
* invoked from the button-level validation handler, otherwise an exception
* will be thrown.
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->buildEntity($form, $form_state);
@ -87,10 +98,10 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
$this->flagViolations($violations, $form, $form_state);
// @todo Remove this.
// Execute legacy global validation handlers.
$form_state->setValidateHandlers([]);
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
// The entity was validated.
$entity->setValidationRequired(FALSE);
$form_state->setTemporaryValue('entity_validated', TRUE);
return $entity;
}

Some files were not shown because too many files have changed in this diff Show more