From 1776b0190cc0fac62c74fc2dae67bf426942f710 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Wed, 20 May 2015 11:11:43 +0100
Subject: [PATCH] Issue #2337753 by alexpott: ContentEntityNullStorage does not
 implement a query service

---
 core/core.services.yml                        |  2 +
 .../Core/Entity/ContentEntityNullStorage.php  |  4 +-
 .../Core/Entity/Query/Null/Condition.php      | 37 +++++++++
 .../Drupal/Core/Entity/Query/Null/Query.php   | 50 +++++++++++
 .../Core/Entity/Query/Null/QueryFactory.php   | 47 +++++++++++
 .../Entity/ContentEntityNullStorageTest.php   | 83 +++++++++++++++++++
 6 files changed, 221 insertions(+), 2 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Entity/Query/Null/Condition.php
 create mode 100644 core/lib/Drupal/Core/Entity/Query/Null/Query.php
 create mode 100644 core/lib/Drupal/Core/Entity/Query/Null/QueryFactory.php
 create mode 100644 core/modules/system/src/Tests/Entity/ContentEntityNullStorageTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index ddab635e1674..528ddd046db8 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -724,6 +724,8 @@ services:
     arguments: ['@database']
     tags:
       - { name: backend_overridable }
+  entity.query.null:
+    class: Drupal\Core\Entity\Query\Null\QueryFactory
   entity.query.keyvalue:
     class: Drupal\Core\Entity\KeyValueStore\Query\QueryFactory
     arguments: ['@keyvalue']
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
index b9843c8d8af3..83c18342c583 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Entity\FieldableNullStorage.
+ * Contains \Drupal\Core\Entity\ContentEntityNullStorage.
  */
 
 namespace Drupal\Core\Entity;
@@ -79,7 +79,7 @@ public function save(EntityInterface $entity) {
    * {@inheritdoc}
    */
   protected function getQueryServiceName() {
-    throw new QueryException('Null implementation can not be queried.');
+    return 'entity.query.null';
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Query/Null/Condition.php b/core/lib/Drupal/Core/Entity/Query/Null/Condition.php
new file mode 100644
index 000000000000..482d4b8a0814
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/Null/Condition.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\Null\Condition.
+ */
+
+namespace Drupal\Core\Entity\Query\Null;
+
+use Drupal\Core\Entity\Query\ConditionBase;
+
+/**
+ * Defines the condition class for the null entity query.
+ */
+class Condition extends ConditionBase {
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::compile().
+   */
+  public function compile($query) {
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::exists().
+   */
+  public function exists($field, $langcode = NULL) {
+    return $this->condition($field, NULL, 'IS NOT NULL', $langcode);
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\ConditionInterface::notExists().
+   */
+  public function notExists($field, $langcode = NULL) {
+    return $this->condition($field, NULL, 'IS NULL', $langcode);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Query/Null/Query.php b/core/lib/Drupal/Core/Entity/Query/Null/Query.php
new file mode 100644
index 000000000000..d787940aafae
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/Null/Query.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\Null\Query.
+ */
+
+namespace Drupal\Core\Entity\Query\Null;
+
+use Drupal\Core\Entity\Query\QueryAggregateInterface;
+use Drupal\Core\Entity\Query\QueryBase;
+use Drupal\Core\Entity\Query\QueryInterface;
+use Drupal\Core\Entity\Query\Sql\ConditionAggregate;
+
+/**
+ * Defines the entity query for configuration entities.
+ */
+class Query extends QueryBase implements QueryInterface, QueryAggregateInterface {
+
+  /**
+   * Implements \Drupal\Core\Entity\Query\QueryInterface::execute().
+   */
+  public function execute() {
+    if ($this->count) {
+      return 0;
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function existsAggregate($field, $function, $langcode = NULL) {
+    return $this->conditionAggregate->exists($field, $function, $langcode);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function notExistsAggregate($field, $function, $langcode = NULL) {
+    return $this->conditionAggregate->notExists($field, $function, $langcode);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function conditionAggregateGroupFactory($conjunction = 'AND') {
+    return new ConditionAggregate($conjunction, $this);
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/Query/Null/QueryFactory.php b/core/lib/Drupal/Core/Entity/Query/Null/QueryFactory.php
new file mode 100644
index 000000000000..4bcef0caffeb
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/Null/QueryFactory.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\Null\QueryFactory.
+ */
+
+namespace Drupal\Core\Entity\Query\Null;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryBase;
+use Drupal\Core\Entity\Query\QueryFactoryInterface;
+
+/**
+ * Provides a factory for creating entity query objects for the null backend.
+ */
+class QueryFactory implements QueryFactoryInterface {
+
+  /**
+   * The namespace of this class, the parent class etc.
+   *
+   * @var array
+   */
+  protected $namespaces;
+
+  /**
+   * Constructs a QueryFactory object.
+   */
+  public function __construct() {
+    $this->namespaces = QueryBase::getNamespaces($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get(EntityTypeInterface $entity_type, $conjunction) {
+    return new Query($entity_type, $conjunction, $this->namespaces);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
+    return new Query($entity_type, $conjunction, $this->namespaces);
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Entity/ContentEntityNullStorageTest.php b/core/modules/system/src/Tests/Entity/ContentEntityNullStorageTest.php
new file mode 100644
index 000000000000..69ad3b9f1ca0
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/ContentEntityNullStorageTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\ContentEntityNullStorageTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\contact\Entity\ContactForm;
+use Drupal\Core\Config\ConfigImporter;
+use Drupal\Core\Config\StorageComparer;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests ContentEntityNullStorage entity query support.
+ *
+ * @see \Drupal\Core\Entity\ContentEntityNullStorage
+ * @see \Drupal\Core\Entity\Query\Null\Query
+ *
+ * @group Entity
+ */
+class ContentEntityNullStorageTest extends KernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('system',  'contact', 'user');
+
+  /**
+   * Tests using entity query with ContentEntityNullStorage.
+   *
+   * @see \Drupal\Core\Entity\Query\Null\Query
+   */
+  public function testEntityQuery() {
+    $this->assertIdentical(0, \Drupal::entityQuery('contact_message')->count()->execute(), 'Counting a null storage returns 0.');
+    $this->assertIdentical([], \Drupal::entityQuery('contact_message')->execute(), 'Querying a null storage returns an empty array.');
+    $this->assertIdentical([], \Drupal::entityQuery('contact_message')->condition('contact_form', 'test')->execute(), 'Querying a null storage returns an empty array and conditions are ignored.');
+    $this->assertIdentical([], \Drupal::entityQueryAggregate('contact_message')->aggregate('name', 'AVG')->execute(), 'Aggregate querying a null storage returns an empty array');
+
+  }
+
+  /**
+   * Tests deleting a contact form entity via a configuration import.
+   *
+   * @see \Drupal\Core\Entity\Event\BundleConfigImportValidate
+   */
+  public function testDeleteThroughImport() {
+    $contact_form = ContactForm::create(['id' => 'test']);
+    $contact_form->save();
+
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
+
+    // Set up the ConfigImporter object for testing.
+    $storage_comparer = new StorageComparer(
+      $this->container->get('config.storage.staging'),
+      $this->container->get('config.storage'),
+      $this->container->get('config.manager')
+    );
+    $config_importer = new ConfigImporter(
+      $storage_comparer->createChangelist(),
+      $this->container->get('event_dispatcher'),
+      $this->container->get('config.manager'),
+      $this->container->get('lock'),
+      $this->container->get('config.typed'),
+      $this->container->get('module_handler'),
+      $this->container->get('module_installer'),
+      $this->container->get('theme_handler'),
+      $this->container->get('string_translation')
+    );
+
+    // Delete the contact message in staging.
+    $staging = $this->container->get('config.storage.staging');
+    $staging->delete($contact_form->getConfigDependencyName());
+
+    // Import.
+    $config_importer->reset()->import();
+    $this->assertNull(ContactForm::load($contact_form->id()), 'The contact form has been deleted.');
+  }
+
+}
-- 
GitLab