Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/classes/Elgg/Cache/MetadataCache.php (2 issues)

1
<?php
2
namespace Elgg\Cache;
3
4
use Elgg\Database\Clauses\OrderByClause;
5
use Elgg\Values;
6
use ElggCache;
7
use ElggMetadata;
8
9
/**
10
 * In memory cache of known metadata values stored by entity.
11
 *
12
 * @access private
13
 */
14
class MetadataCache {
15
16
	/**
17
	 * @var ElggCache
18
	 */
19
	protected $cache;
20
21
	/**
22
	 * Constructor
23
	 *
24
	 * @param ElggCache $cache Cache
25
	 */
26 4417
	public function __construct(ElggCache $cache) {
27 4417
		$this->cache = $cache;
28 4417
	}
29
30
	/**
31
	 * Set the visible metadata for an entity in the cache
32
	 *
33
	 * Note this does NOT invalidate any other part of the cache.
34
	 *
35
	 * @param int   $entity_guid The GUID of the entity
36
	 * @param array $values      The metadata values to cache
37
	 *
38
	 * @return void
39
	 *
40
	 * @interal For testing only
41
	 */
42 4
	public function inject($entity_guid, array $values = []) {
43 4
		$metadata = [];
44 4
		foreach ($values as $key => $value) {
45 4
			if ($value instanceof ElggMetadata) {
46
				$md = $value;
47
			} else {
48 4
				$md = new ElggMetadata();
49 4
				$md->name = $key;
50 4
				$md->value = $value;
51 4
				$md->entity_guid = $entity_guid;
52
			}
53
54 4
			$metadata[] = $md->toObject();
55
		}
56
57 4
		$this->cache->save($entity_guid, $metadata);
58 4
	}
59
60
	/**
61
	 * Get the metadata for a particular name. Note, this can return an array of values.
62
	 *
63
	 * Warning: You should always call isLoaded() beforehand to verify that this
64
	 * function's return value can be trusted.
65
	 *
66
	 * @see isLoaded
67
	 *
68
	 * @param int    $entity_guid The GUID of the entity
69
	 * @param string $name        The metadata name
70
	 *
71
	 * @return array|string|int|null null = value does not exist
72
	 */
73 2303
	public function getSingle($entity_guid, $name) {
74 2303
		$metadata = $this->cache->load($entity_guid);
75 2303
		if (!$metadata) {
76 1
			return null;
77
		}
78
79 2303
		$values = [];
80
81 2303
		foreach ($metadata as $md) {
82 2303
			if ($md->name !== $name) {
83 2231
				continue;
84
			}
85
86 2303
			$values[] = $md->value;
87
		}
88
89 2303
		if (empty($values)) {
90 2027
			return null;
91
		}
92
93 2303
		return count($values) > 1 ? $values : $values[0];
94
95
	}
96
97
	/**
98
	 * Get the metadata id for a particular name
99
	 *
100
	 * Warning: You should always call isLoaded() beforehand to verify that this
101
	 * function's return value can be trusted.
102
	 *
103
	 * @see isLoaded
104
	 *
105
	 * @param int    $entity_guid The GUID of the entity
106
	 * @param string $name        The metadata name
107
	 *
108
	 * @return int[]|int|null
109
	 */
110 666
	public function getSingleId($entity_guid, $name) {
111 666
		$metadata = $this->cache->load($entity_guid);
112 666
		if (!$metadata) {
113
			return null;
114
		}
115
116 666
		$ids = [];
117
118 666
		foreach ($metadata as $md) {
119 666
			if ($md->name !== $name) {
120 666
				continue;
121
			}
122
123 199
			$ids[] = $md->id;
124
		}
125
126 666
		if (empty($ids)) {
127 666
			return null;
128
		}
129
130 199
		return count($ids) > 1 ? $ids : $ids[0];
131
	}
132
133
	/**
134
	 * Forget about all metadata for an entity.
135
	 *
136
	 * @param int $entity_guid The GUID of the entity
137
	 *
138
	 * @return void
139
	 */
140 1028
	public function clear($entity_guid) {
141 1028
		$this->invalidateByOptions([
142 1028
			'guid' => $entity_guid,
143
		]);
144 1028
	}
145
146
	/**
147
	 * If true, getSingle() will return an accurate values from the DB
148
	 *
149
	 * @param int $entity_guid The GUID of the entity
150
	 *
151
	 * @return bool
152
	 */
153 5356
	public function isLoaded($entity_guid) {
154 5356
		return $this->cache->load($entity_guid) !== null;
155
	}
156
157
	/**
158
	 * Clear entire cache
159
	 *
160
	 * @return void
161
	 */
162 7
	public function clearAll() {
163 7
		$this->invalidateByOptions([]);
164 7
	}
165
166
	/**
167
	 * Returns loaded entity metadata
168
	 *
169
	 * @param int $entity_guid Entity guid
170
	 *
171
	 * @return \stdClass[]|null
172
	 */
173 13
	public function getEntityMetadata($entity_guid) {
174 13
		if (!$this->isLoaded($entity_guid)) {
175
			$this->populateFromEntities($entity_guid);
0 ignored issues
show
$entity_guid of type integer is incompatible with the type integer[] expected by parameter $guids of Elgg\Cache\MetadataCache::populateFromEntities(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

175
			$this->populateFromEntities(/** @scrutinizer ignore-type */ $entity_guid);
Loading history...
176
		}
177
178 13
		if ($this->isLoaded($entity_guid)) {
179 13
			return $this->cache->load($entity_guid);
180
		}
181
182
		return null;
183
	}
184
185
	/**
186
	 * Invalidate based on options passed to the global *_metadata functions
187
	 *
188
	 * @param array $options Options passed to elgg_(delete|disable|enable)_metadata
189
	 *                       "guid" if given, invalidation will be limited to this entity
190
	 * @return void
191
	 */
192 1057
	public function invalidateByOptions(array $options) {
193 1057
		if (empty($options['guid'])) {
194 7
			_elgg_services()->sessionCache->clear();
195 7
			_elgg_services()->dataCache->clear();
196
		} else {
197 1055
			_elgg_services()->entityTable->invalidateCache($options['guid']);
198
		}
199 1057
	}
200
201
	/**
202
	 * Populate the cache from a set of entities
203
	 *
204
	 * @param int[] ...$guids Array of or single GUIDs
205
	 * @return void
206
	 */
207 4197
	public function populateFromEntities(...$guids) {
208
		try {
209 4197
			$guids = Values::normalizeGuids($guids);
210
		} catch (\DataFormatException $e) {
211
			return null;
212
		}
213
214 4197
		if (empty($guids)) {
215 25
			return;
216
		}
217
218 4197
		$version = (int) _elgg_config()->version;
219 4197
		if (!empty($version) && ($version < 2016110900)) {
220
			// can't use this during upgrade from 2.x to 3.0
221
			return;
222
		}
223
224 4197
		foreach ($guids as $i => $guid) {
225 4197
			$value = $this->cache->load($guid);
226 4197
			if ($value !== null) {
227 4197
				unset($guids[$i]);
228
			}
229
		}
230
231 4197
		if (empty($guids)) {
232 508
			return;
233
		}
234
235
		// could be useful at some point in future
236
		//$guids = $this->filterMetadataHeavyEntities($guids);
237
238
		$options = [
239 4197
			'guids' => $guids,
240 4197
			'limit' => 0,
241
			'callback' => false,
242
			'distinct' => false,
243
			'order_by' => [
244 4197
				new OrderByClause('n_table.entity_guid', 'asc'),
245 4197
				new OrderByClause('n_table.time_created', 'asc'),
246 4197
				new OrderByClause('n_table.id', 'asc')
247
			],
248
		];
249
250
		// We already have a loaded entity, so we can ignore entity access clauses
251 4197
		$ia = elgg_set_ignore_access(true);
252 4197
		$data = _elgg_services()->metadataTable->getAll($options);
253 4197
		elgg_set_ignore_access($ia);
254
255 4197
		$values = [];
256
257 4197
		foreach ($data as $i => $row) {
258 1143
			$row->value = ($row->value_type === 'text') ? $row->value : (int) $row->value;
259 1143
			$values[$row->entity_guid][] = $row;
260
		}
261
262 4197
		foreach ($values as $guid => $row) {
263 1143
			$this->cache->save($guid, $row);
264
		}
265 4197
	}
266
267
	/**
268
	 * Filter out entities whose concatenated metadata values (INTs casted as string)
269
	 * exceed a threshold in characters. This could be used to avoid overpopulating the
270
	 * cache if RAM usage becomes an issue.
271
	 *
272
	 * @param array $guids GUIDs of entities to examine
273
	 * @param int   $limit Limit in characters of all metadata (with ints casted to strings)
274
	 *
275
	 * @return array
276
	 */
277 1
	public function filterMetadataHeavyEntities(array $guids, $limit = 1024000) {
278
279 1
		$guids = _elgg_services()->metadataTable->getAll([
280 1
			'guids' => $guids,
281 1
			'limit' => 0,
282 1
			'callback' => function($e) {
283 1
				return (int) $e->entity_guid;
284 1
			},
285
			'selects' => ['SUM(LENGTH(n_table.value)) AS bytes'],
286
			'order_by' => 'n_table.entity_guid, n_table.time_created ASC',
287
			'group_by' => 'n_table.entity_guid',
288
			'having' => [
289
				"bytes < $limit",
290
			]
291
		]);
292
293
		return $guids ? : [];
1 ignored issue
show
Bug Best Practice introduced by
The expression return $guids ?: array() also could return the type integer which is incompatible with the documented return type array.
Loading history...
294
	}
295
}
296