Skip to content

Commit 7895d96

Browse files
committed
Finalize entity query support
1 parent f828090 commit 7895d96

File tree

1 file changed

+112
-23
lines changed

1 file changed

+112
-23
lines changed

src/Entity/Query/Query.php

Lines changed: 112 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
use Drupal\Core\Entity\EntityTypeManagerInterface;
2626
use Drupal\Core\Entity\Query\QueryBase;
2727
use Drupal\Core\Entity\Query\QueryInterface;
28+
use Drupal\Core\Extension\ModuleHandlerInterface;
29+
use Drupal\Core\Render\RendererInterface;
30+
use Drupal\Core\Session\AccountInterface;
31+
use Symfony\Component\HttpFoundation\RequestStack;
2832

2933
/**
3034
* Defines the entity query for Apigee Edge entities.
@@ -53,24 +57,85 @@ class Query extends QueryBase implements QueryInterface {
5357
*
5458
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
5559
*/
56-
protected $entityTypeManager;
60+
protected EntityTypeManagerInterface $entityTypeManager;
61+
62+
/**
63+
* The current user.
64+
*
65+
* @var \Drupal\Core\Session\AccountInterface
66+
*/
67+
protected AccountInterface $currentUser;
68+
69+
/**
70+
* The request stack.
71+
*
72+
* @var \Symfony\Component\HttpFoundation\RequestStack
73+
*/
74+
protected RequestStack $requestStack;
75+
76+
/**
77+
* The renderer.
78+
*
79+
* @var \Drupal\Core\Render\RendererInterface
80+
*/
81+
protected RendererInterface $renderer;
82+
83+
/**
84+
* The module handler.
85+
*
86+
* @var \Drupal\Core\Extension\ModuleHandlerInterface
87+
*/
88+
protected ModuleHandlerInterface $moduleHandler;
5789

5890
/**
5991
* Constructs a Query object.
6092
*
6193
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
6294
* The entity type definition.
6395
* @param string $conjunction
64-
* - AND: all of the conditions on the query need to match.
96+
* - AND: all the conditions on the query need to match.
6597
* - OR: at least one of the conditions on the query need to match.
6698
* @param array $namespaces
6799
* List of potential namespaces of the classes belonging to this query.
68100
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
69101
* The entity type manager.
102+
* @param \Drupal\Core\Session\AccountInterface|null $current_user
103+
* The current user.
104+
* @param \Symfony\Component\HttpFoundation\RequestStack|null $request_stack
105+
* The request stack.
106+
* @param \Drupal\Core\Render\RendererInterface|null $renderer
107+
* The renderer.
108+
* @param \Drupal\Core\Extension\ModuleHandlerInterface|null $module_handler
109+
* The module handler.
70110
*/
71-
public function __construct(EntityTypeInterface $entity_type, string $conjunction, array $namespaces, EntityTypeManagerInterface $entity_type_manager) {
111+
public function __construct(EntityTypeInterface $entity_type, string $conjunction, array $namespaces, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user = NULL, RequestStack $request_stack = NULL, RendererInterface $renderer = NULL, ModuleHandlerInterface $module_handler = NULL) {
72112
parent::__construct($entity_type, $conjunction, $namespaces);
113+
114+
if ($current_user === NULL) {
115+
$current_user = \Drupal::currentUser();
116+
@trigger_error('Calling ' . __METHOD__ . ' without the $current_user is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498', E_USER_DEPRECATED);
117+
}
118+
119+
if ($request_stack === NULL) {
120+
$request_stack = \Drupal::requestStack();
121+
@trigger_error('Calling ' . __METHOD__ . ' without the $request_stack is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498', E_USER_DEPRECATED);
122+
}
123+
124+
if ($renderer === NULL) {
125+
$renderer = \Drupal::service('renderer');
126+
@trigger_error('Calling ' . __METHOD__ . ' without the $renderer is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498', E_USER_DEPRECATED);
127+
}
128+
129+
if ($module_handler === NULL) {
130+
$module_handler = \Drupal::moduleHandler();
131+
@trigger_error('Calling ' . __METHOD__ . ' without the $module_handler is deprecated in apigee_edge:2.0.9 and is required before apigee_edge:3.0.0. See https://www.drupal.org/node/3338498', E_USER_DEPRECATED);
132+
}
133+
73134
$this->entityTypeManager = $entity_type_manager;
135+
$this->currentUser = $current_user;
136+
$this->requestStack = $request_stack;
137+
$this->renderer = $renderer;
138+
$this->moduleHandler = $module_handler;
74139
}
75140

76141
/**
@@ -86,36 +151,60 @@ public function execute() {
86151
// result because this function gets called.
87152
$all_records = $this->getFromStorage();
88153

89-
// @todo Proper entity query support that is aligned with the implementation
90-
// in \Drupal\Core\Entity\Query\Sql\Query::prepare() can be only added
91-
// if the following Entity API module issue is solved.
92-
// https://www.drupal.org/project/entity/issues/3332956
93-
// (Having a fix for a similar Group module issue is a nice to have,
94-
// https://www.drupal.org/project/group/issues/3332963.)
154+
// Be consistent with \Drupal\Core\Entity\Query\Sql\Query::prepare().
155+
// Add and fire special entity query tags.
156+
$this->addTag('entity_query');
157+
$this->addTag('entity_query_' . $this->entityTypeId);
158+
95159
if ($this->accessCheck) {
160+
// We do not just add a tag but ensure that only those Apigee entities
161+
// are returned that the entity access API grants view access.
162+
// (Storage level filtering is not available or way too limited.)
163+
$this->addTag($this->entityTypeId . '_access');
164+
96165
// Read meta-data from query, if provided.
97-
if (!$account = $this->getMetaData('account')) {
98-
// @todo DI dependency.
99-
$account = \Drupal::currentUser();
166+
$account = $this->getMetaData('account');
167+
if ($account === NULL) {
168+
$account = $this->currentUser;
100169
}
170+
101171
$cacheability = CacheableMetadata::createFromRenderArray([]);
102-
$all_records = array_filter($all_records, static function (EntityInterface $entity) use ($cacheability, $account) {
172+
$viewable_entity_ids = array_reduce($all_records, static function (array $carry, EntityInterface $entity) use ($cacheability, $account) {
103173
// Bubble up cacheability information even from a revoked access result.
104174
$result = $entity->access('view', $account, TRUE);
105175
$cacheability->addCacheableDependency($result);
106-
return $result->isAllowed();
107-
});
108-
// @todo DI dependencies.
176+
if ($result->isAllowed()) {
177+
$carry[] = $entity->id();
178+
}
179+
return $carry;
180+
}, []);
181+
182+
// We deliberately add conditions to the original entity query instead
183+
// of pre-filtering all records because query conditions are visible
184+
// in hook_query_TAG_alter() implementations for downstream developers.
185+
if (empty($viewable_entity_ids)) {
186+
// Add an always false condition. A persisted entity's primary id
187+
// cannot be null.
188+
$this->condition->notExists($this->entityType->getKey('id'));
189+
}
190+
else {
191+
$this->condition->condition($this->entityType->getKey('id'), $viewable_entity_ids, 'IN');
192+
}
109193
/** @var \Symfony\Component\HttpFoundation\Request $request */
110-
$request = \Drupal::requestStack()->getCurrentRequest();
111-
$renderer = \Drupal::service('renderer');
112-
if ($request->isMethodCacheable() && $renderer->hasRenderContext()) {
194+
$request = $this->requestStack->getCurrentRequest();
195+
if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) {
113196
$build = [];
114197
$cacheability->applyTo($build);
115-
$renderer->render($build);
198+
$this->renderer->render($build);
116199
}
117200
}
118201

202+
$hooks = ['query'];
203+
foreach ($this->alterTags as $tag => $value) {
204+
$hooks[] = 'query_' . $tag;
205+
}
206+
$this->moduleHandler->alter($hooks, $this);
207+
119208
$filter = $this->condition->compile($this);
120209
$result = array_filter($all_records, $filter);
121210

@@ -124,7 +213,7 @@ public function execute() {
124213
}
125214

126215
if ($this->sort) {
127-
uasort($result, function (EntityInterface $entity0, EntityInterface $entity1) : int {
216+
uasort($result, function (EntityInterface $entity0, EntityInterface $entity1): int {
128217
foreach ($this->sort as $sort) {
129218
$value0 = Condition::getProperty($entity0, $sort['field']);
130219
$value1 = Condition::getProperty($entity1, $sort['field']);
@@ -150,7 +239,7 @@ public function execute() {
150239
$result = array_slice($result, $this->range['start'], $this->range['length']);
151240
}
152241

153-
return array_map(function (EntityInterface $entity) : string {
242+
return array_map(static function (EntityInterface $entity): string {
154243
return (string) $entity->id();
155244
}, $result);
156245
}
@@ -219,7 +308,7 @@ protected function getFromStorage(): array {
219308
else {
220309
$ids = [$id];
221310
unset($filtered_conditions[$key]);
222-
// If we found an id field in the query do not look for an another
311+
// If we found an id field in the query do not look for another
223312
// because that would not make any sense to query one entity by
224313
// both id fields. (Where in theory both id field could refer to a
225314
// different entity.)

0 commit comments

Comments
 (0)