/[drupal]/drupal/includes/entity.inc
ViewVC logotype

Contents of /drupal/includes/entity.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.3 - (show annotations) (download) (as text)
Sat Oct 31 16:06:35 2009 UTC (3 weeks, 4 days ago) by dries
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10, HEAD
Changes since 1.2: +2 -2 lines
File MIME type: text/x-php
- Patch #606994 by yched: move entity handling out of Field API.
1 <?php
2 // $Id: entity.inc,v 1.2 2009/10/16 03:47:13 webchick Exp $
3
4 /**
5 * Interface for entity controller classes.
6 *
7 * All entity controller classes specified via the 'controller class' key
8 * returned by hook_entity_info() or hook_entity_info_alter() have to implement
9 * this interface.
10 *
11 * Most simple, SQL-based entity controllers will do better by extending
12 * DrupalDefaultEntityController instead of implementing this interface
13 * directly.
14 */
15 interface DrupalEntityControllerInterface {
16 /**
17 * Constructor.
18 *
19 * @param $entityType
20 * The entity type for which the instance is created.
21 */
22 public function __construct($entityType);
23
24 /**
25 * Reset the internal, static entity cache.
26 */
27 public function resetCache();
28
29 /**
30 * Load one or more entities.
31 *
32 * @param $ids
33 * An array of entity IDs, or FALSE to load all entities.
34 * @param $conditions
35 * An array of conditions in the form 'field' => $value.
36 *
37 * @return
38 * An array of entity objects indexed by their ids.
39 */
40 public function load($ids = array(), $conditions = array());
41 }
42
43 /**
44 * Default implementation of DrupalEntityControllerInterface.
45 *
46 * This class can be used as-is by most simple entity types. Entity types
47 * requiring special handling can extend the class.
48 */
49 class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
50
51 protected $entityCache;
52 protected $entityType;
53 protected $entityInfo;
54 protected $hookLoadArguments;
55 protected $idKey;
56 protected $revisionKey;
57 protected $revisionTable;
58 protected $query;
59
60 /**
61 * Constructor. Set basic variables.
62 */
63 public function __construct($entityType) {
64 $this->entityType = $entityType;
65 $this->entityInfo = entity_get_info($entityType);
66 $this->entityCache = array();
67 $this->hookLoadArguments = array();
68 $this->idKey = $this->entityInfo['object keys']['id'];
69
70 // Check if the entity type supports revisions.
71 if (!empty($this->entityInfo['object keys']['revision'])) {
72 $this->revisionKey = $this->entityInfo['object keys']['revision'];
73 $this->revisionTable = $this->entityInfo['revision table'];
74 }
75 else {
76 $this->revisionKey = FALSE;
77 }
78
79 // Check if the entity type supports static caching of loaded entities.
80 $this->cache = !empty($this->entityInfo['static cache']);
81 }
82
83 public function resetCache() {
84 $this->entityCache = array();
85 }
86
87 public function load($ids = array(), $conditions = array()) {
88 $this->ids = $ids;
89 $this->conditions = $conditions;
90
91 $entities = array();
92
93 // Revisions are not statically cached, and require a different query to
94 // other conditions, so separate the revision id into its own variable.
95 if ($this->revisionKey && isset($this->conditions[$this->revisionKey])) {
96 $this->revisionId = $this->conditions[$this->revisionKey];
97 unset($this->conditions[$this->revisionKey]);
98 }
99 else {
100 $this->revisionId = FALSE;
101 }
102
103
104 // Create a new variable which is either a prepared version of the $ids
105 // array for later comparison with the entity cache, or FALSE if no $ids
106 // were passed. The $ids array is reduced as items are loaded from cache,
107 // and we need to know if it's empty for this reason to avoid querying the
108 // database when all requested entities are loaded from cache.
109 $passed_ids = !empty($this->ids) ? array_flip($this->ids) : FALSE;
110 // Try to load entities from the static cache, if the entity type supports
111 // static caching.
112 if ($this->cache) {
113 $entities += $this->cacheGet($this->ids, $this->conditions);
114 // If any entities were loaded, remove them from the ids still to load.
115 if ($passed_ids) {
116 $this->ids = array_keys(array_diff_key($passed_ids, $entities));
117 }
118 }
119
120 // Load any remaining entities from the database. This is the case if $ids
121 // is set to FALSE (so we load all entities), if there are any ids left to
122 // load, if loading a revision, or if $conditions was passed without $ids.
123 if ($this->ids === FALSE || $this->ids || $this->revisionId || ($this->conditions && !$passed_ids)) {
124 // Build the query.
125 $this->buildQuery();
126 $queried_entities = $this->query
127 ->execute()
128 ->fetchAllAssoc($this->idKey);
129 }
130
131 // Pass all entities loaded from the database through $this->attachLoad(),
132 // which attaches fields (if supported by the entity type) and calls the
133 // entity type specific load callback, for example hook_node_load().
134 if (!empty($queried_entities)) {
135 $this->attachLoad($queried_entities);
136 $entities += $queried_entities;
137 }
138
139 if ($this->cache) {
140 // Add entities to the cache if we are not loading a revision.
141 if (!empty($queried_entities) && !$this->revisionId) {
142 $this->cacheSet($queried_entities);
143 }
144 // Ensure that the returned array is ordered the same as the original
145 // $ids array if this was passed in and remove any invalid ids.
146 if ($passed_ids) {
147 // Remove any invalid ids from the array.
148 $passed_ids = array_intersect_key($passed_ids, $entities);
149 foreach ($entities as $entity) {
150 $passed_ids[$entity->{$this->idKey}] = $entity;
151 }
152 $entities = $passed_ids;
153 }
154 }
155
156 return $entities;
157 }
158
159 /**
160 * Build the query to load the entity.
161 *
162 * This has full revision support. For entities requiring special queries,
163 * the class can be extended, and the default query can be constructed by
164 * calling parent::buildQuery(). This is usually necessary when the object
165 * being loaded needs to be augmented with additional data from another
166 * table, such as loading node type into comments or vocabulary machine name
167 * into terms, however it can also support $conditions on different tables.
168 * See NodeController::buildQuery() or TaxonomyTermController::buildQuery()
169 * for examples.
170 */
171 protected function buildQuery() {
172 $this->query = db_select($this->entityInfo['base table'], 'base');
173
174 $this->query->addTag($this->entityType . '_load_multiple');
175
176 if ($this->revisionId) {
177 $this->query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $this->revisionId));
178 }
179 elseif ($this->revisionKey) {
180 $this->query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
181 }
182
183 // Add fields from the {entity} table.
184 $entity_fields = drupal_schema_fields_sql($this->entityInfo['base table']);
185
186 if ($this->revisionKey) {
187 // Add all fields from the {entity_revision} table.
188 $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->revisionTable));
189 // The id field is provided by entity, so remove it.
190 unset($entity_revision_fields[$this->idKey]);
191
192 // Change timestamp to revision_timestamp, and revision uid to
193 // revision_uid before adding them to the query.
194 // TODO: This is node specific and has to be moved into NodeController.
195 unset($entity_revision_fields['timestamp']);
196 $this->query->addField('revision', 'timestamp', 'revision_timestamp');
197 unset($entity_revision_fields['uid']);
198 $this->query->addField('revision', 'uid', 'revision_uid');
199
200 // Remove all fields from the base table that are also fields by the same
201 // name in the revision table.
202 $entity_field_keys = array_flip($entity_fields);
203 foreach ($entity_revision_fields as $key => $name) {
204 if (isset($entity_field_keys[$name])) {
205 unset($entity_fields[$entity_field_keys[$name]]);
206 }
207 }
208 $this->query->fields('revision', $entity_revision_fields);
209 }
210
211 $this->query->fields('base', $entity_fields);
212
213 if ($this->ids) {
214 $this->query->condition("base.{$this->idKey}", $this->ids, 'IN');
215 }
216 if ($this->conditions) {
217 foreach ($this->conditions as $field => $value) {
218 $this->query->condition('base.' . $field, $value);
219 }
220 }
221 }
222
223 /**
224 * Attach data to entities upon loading.
225 *
226 * This will attach fields, if the entity is fieldable. It calls
227 * hook_entity_load() for modules which need to add data to all entities.
228 * It also calls hook_TYPE_load() on the loaded entities. For example
229 * hook_node_load() or hook_user_load(). If your hook_TYPE_load()
230 * expects special parameters apart from the queried entities, you can set
231 * $this->hookLoadArguments prior to calling the method.
232 * See NodeController::attachLoad() for an example.
233 */
234 protected function attachLoad(&$queried_entities) {
235 // Attach fields.
236 if ($this->entityInfo['fieldable']) {
237 if ($this->revisionId) {
238 field_attach_load_revision($this->entityType, $queried_entities);
239 }
240 else {
241 field_attach_load($this->entityType, $queried_entities);
242 }
243 }
244
245 // Call hook_entity_load().
246 foreach (module_implements('entity_load') as $module) {
247 $function = $module . '_entity_load';
248 $function($queried_entities, $this->entityType);
249 }
250 // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
251 // always the queried entities, followed by additional arguments set in
252 // $this->hookLoadArguments.
253 $args = array_merge(array($queried_entities), $this->hookLoadArguments);
254 foreach (module_implements($this->entityInfo['load hook']) as $module) {
255 call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
256 }
257 }
258
259 /**
260 * Get entities from the static cache.
261 *
262 * @param $ids
263 * If not empty, return entities that match these IDs.
264 * @param $conditions
265 * If set, return entities that match all of these conditions.
266 */
267 protected function cacheGet($ids, $conditions = array()) {
268 $entities = array();
269 // Load any available entities from the internal cache.
270 if (!empty($this->entityCache) && !$this->revisionId) {
271 if ($ids) {
272 $entities += array_intersect_key($this->entityCache, array_flip($ids));
273 }
274 // If loading entities only by conditions, fetch all available entities
275 // from the cache. Entities which don't match are removed later.
276 elseif ($conditions) {
277 $entities = $this->entityCache;
278 }
279 }
280
281 // Exclude any entities loaded from cache if they don't match $conditions.
282 // This ensures the same behavior whether loading from memory or database.
283 if ($conditions) {
284 foreach ($entities as $entity) {
285 $entity_values = (array) $entity;
286 if (array_diff_assoc($conditions, $entity_values)) {
287 unset($entities[$entity->{$this->idKey}]);
288 }
289 }
290 }
291 return $entities;
292 }
293
294 /**
295 * Store entities in the static entity cache.
296 */
297 protected function cacheSet($entities) {
298 $this->entityCache += $entities;
299 }
300 }

  ViewVC Help
Powered by ViewVC 1.1.2