Overview

Packages

  • ClipIt
    • clipit
      • api
    • urjc
      • backend
  • Elgg
    • Core
      • Access
      • Authentication
      • Cache
      • Caches
      • Core
      • DataMode
        • Site
      • DataModel
        • Annotations
        • Entities
        • Extender
        • File
        • Importable
        • Loggable
        • Notable
        • Object
        • User
      • DataStorage
      • Exception
      • Exceptions
        • Stub
      • FileStore
        • Disk
      • Groups
      • Helpers
      • HMAC
      • Memcache
      • Metadata
      • Navigation
      • ODD
      • Output
      • Plugins
        • Settings
      • Sessions
      • SocialModel
        • Friendable
        • Locatable
      • WebServicesAPI
      • Widgets
      • XML
      • XMLRPC
    • Exceptions
      • Stub
  • None
  • PHP

Classes

  • ElggCache
  • ElggLRUCache
  • ElggSharedMemoryCache
  • ElggStaticVariableCache
  • ElggVolatileMetadataCache
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * ElggVolatileMetadataCache
  4:  * In memory cache of known metadata values stored by entity.
  5:  *
  6:  * @package    Elgg.Core
  7:  * @subpackage Cache
  8:  *
  9:  * @access private
 10:  */
 11: class ElggVolatileMetadataCache {
 12: 
 13:     /**
 14:      * The cached values (or null for known to be empty). If the portion of the cache
 15:      * is synchronized, missing values are assumed to indicate that values do not
 16:      * exist in storage, otherwise, we don't know what's there.
 17:      *
 18:      * @var array
 19:      */
 20:     protected $values = array();
 21: 
 22:     /**
 23:      * Does the cache know that it contains all names fetch-able from storage?
 24:      * The keys are entity GUIDs and either the value exists (true) or it's not set.
 25:      *
 26:      * @var array
 27:      */
 28:     protected $isSynchronized = array();
 29: 
 30:     /**
 31:      * @var null|bool
 32:      */
 33:     protected $ignoreAccess = null;
 34: 
 35:     /**
 36:      * Cache metadata for an entity
 37:      * 
 38:      * @param int   $entity_guid The GUID of the entity
 39:      * @param array $values      The metadata values to cache
 40:      * @return void
 41:      */
 42:     public function saveAll($entity_guid, array $values) {
 43:         if (!$this->getIgnoreAccess()) {
 44:             $this->values[$entity_guid] = $values;
 45:             $this->isSynchronized[$entity_guid] = true;
 46:         }
 47:     }
 48: 
 49:     /**
 50:      * Get the metadata for an entity
 51:      * 
 52:      * @param int $entity_guid The GUID of the entity
 53:      * @return array
 54:      */
 55:     public function loadAll($entity_guid) {
 56:         if (isset($this->values[$entity_guid])) {
 57:             return $this->values[$entity_guid];
 58:         } else {
 59:             return array();
 60:         }
 61:     }
 62: 
 63:     /**
 64:      * Declare that there may be fetch-able metadata names in storage that this
 65:      * cache doesn't know about
 66:      *
 67:      * @param int $entity_guid The GUID of the entity
 68:      * @return void
 69:      */
 70:     public function markOutOfSync($entity_guid) {
 71:         unset($this->isSynchronized[$entity_guid]);
 72:     }
 73: 
 74:     /**
 75:      * Have all the metadata for this entity been cached?
 76:      * 
 77:      * @param int $entity_guid The GUID of the entity
 78:      * @return bool
 79:      */
 80:     public function isSynchronized($entity_guid) {
 81:         return isset($this->isSynchronized[$entity_guid]);
 82:     }
 83: 
 84:     /**
 85:      * Cache a piece of metadata
 86:      * 
 87:      * @param int                   $entity_guid    The GUID of the entity
 88:      * @param string                $name           The metadata name
 89:      * @param array|int|string|null $value          The metadata value. null means it is 
 90:      *                                              known that there is no fetch-able 
 91:      *                                              metadata under this name
 92:      * @param bool                  $allow_multiple Can the metadata be an array
 93:      * @return void
 94:      */
 95:     public function save($entity_guid, $name, $value, $allow_multiple = false) {
 96:         if ($this->getIgnoreAccess()) {
 97:             // we don't know if what gets saves here will be available to user once
 98:             // access control returns, hence it's best to forget :/
 99:             $this->markUnknown($entity_guid, $name);
100:         } else {
101:             if ($allow_multiple) {
102:                 if ($this->isKnown($entity_guid, $name)) {
103:                     $existing = $this->load($entity_guid, $name);
104:                     if ($existing !== null) {
105:                         $existing = (array) $existing;
106:                         $existing[] = $value;
107:                         $value = $existing;
108:                     }
109:                 } else {
110:                     // we don't know whether there are unknown values, so it's
111:                     // safest to leave that assumption
112:                     $this->markUnknown($entity_guid, $name);
113:                     return;
114:                 }
115:             }
116:             $this->values[$entity_guid][$name] = $value;
117:         }
118:     }
119: 
120:     /**
121:      * Warning: You should always call isKnown() beforehand to verify that this
122:      * function's return value should be trusted (otherwise a null return value
123:      * is ambiguous).
124:      *
125:      * @param int    $entity_guid The GUID of the entity
126:      * @param string $name        The metadata name
127:      * @return array|string|int|null null = value does not exist
128:      */
129:     public function load($entity_guid, $name) {
130:         if (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid])) {
131:             return $this->values[$entity_guid][$name];
132:         } else {
133:             return null;
134:         }
135:     }
136: 
137:     /**
138:      * Forget about this metadata entry. We don't want to try to guess what the
139:      * next fetch from storage will return
140:      *
141:      * @param int    $entity_guid The GUID of the entity
142:      * @param string $name        The metadata name
143:      * @return void
144:      */
145:     public function markUnknown($entity_guid, $name) {
146:         unset($this->values[$entity_guid][$name]);
147:         $this->markOutOfSync($entity_guid);
148:     }
149: 
150:     /**
151:      * If true, load() will return an accurate value for this name
152:      *
153:      * @param int    $entity_guid The GUID of the entity
154:      * @param string $name        The metadata name
155:      * @return bool
156:      */
157:     public function isKnown($entity_guid, $name) {
158:         if (isset($this->isSynchronized[$entity_guid])) {
159:             return true;
160:         } else {
161:             return (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid]));
162:         }
163: 
164:     }
165: 
166:     /**
167:      * Declare that metadata under this name is known to be not fetch-able from storage
168:      *
169:      * @param int    $entity_guid The GUID of the entity
170:      * @param string $name        The metadata name
171:      * @return array
172:      */
173:     public function markEmpty($entity_guid, $name) {
174:         $this->values[$entity_guid][$name] = null;
175:     }
176: 
177:     /**
178:      * Forget about all metadata for an entity
179:      *
180:      * @param int $entity_guid The GUID of the entity
181:      * @return void
182:      */
183:     public function clear($entity_guid) {
184:         $this->values[$entity_guid] = array();
185:         $this->markOutOfSync($entity_guid);
186:     }
187: 
188:     /**
189:      * Clear entire cache and mark all entities as out of sync
190:      * 
191:      * @return void
192:      */
193:     public function flush() {
194:         $this->values = array();
195:         $this->isSynchronized = array();
196:     }
197: 
198:     /**
199:      * Use this value instead of calling elgg_get_ignore_access(). By default that
200:      * function will be called.
201:      *
202:      * This setting makes this component a little more loosely-coupled.
203:      *
204:      * @param bool $ignore Whether to ignore access or not
205:      * @return void
206:      */
207:     public function setIgnoreAccess($ignore) {
208:         $this->ignoreAccess = (bool) $ignore;
209:     }
210: 
211:     /**
212:      * Tell the cache to call elgg_get_ignore_access() to determing access status.
213:      * 
214:      * @return void
215:      */
216:     public function unsetIgnoreAccess() {
217:         $this->ignoreAccess = null;
218:     }
219: 
220:     /**
221:      * Get the ignore access value
222:      * 
223:      * @return bool
224:      */
225:     protected function getIgnoreAccess() {
226:         if (null === $this->ignoreAccess) {
227:             return elgg_get_ignore_access();
228:         } else {
229:             return $this->ignoreAccess;
230:         }
231:     }
232: 
233:     /**
234:      * Invalidate based on options passed to the global *_metadata functions
235:      *
236:      * @param string $action  Action performed on metadata. "delete", "disable", or "enable"
237:      * @param array  $options Options passed to elgg_(delete|disable|enable)_metadata
238:      *                         "guid" if given, invalidation will be limited to this entity
239:      *                         "metadata_name" if given, invalidation will be limited to metadata with this name
240:      * @return void
241:      */
242:     public function invalidateByOptions($action, array $options) {
243:         // remove as little as possible, optimizing for common cases
244:         if (empty($options['guid'])) {
245:             // safest to clear everything unless we want to make this even more complex :(
246:             $this->flush();
247:         } else {
248:             if (empty($options['metadata_name'])) {
249:                 // safest to clear the whole entity
250:                 $this->clear($options['guid']);
251:             } else {
252:                 switch ($action) {
253:                     case 'delete':
254:                         $this->markEmpty($options['guid'], $options['metadata_name']);
255:                         break;
256:                     default:
257:                         $this->markUnknown($options['guid'], $options['metadata_name']);
258:                 }
259:             }
260:         }
261:     }
262: 
263:     /**
264:      * Populate the cache from a set of entities
265:      * 
266:      * @param int|array $guids Array of or single GUIDs
267:      * @return void
268:      */
269:     public function populateFromEntities($guids) {
270:         if (empty($guids)) {
271:             return;
272:         }
273:         if (!is_array($guids)) {
274:             $guids = array($guids);
275:         }
276:         $guids = array_unique($guids);
277: 
278:         // could be useful at some point in future
279:         //$guids = $this->filterMetadataHeavyEntities($guids);
280: 
281:         $db_prefix = elgg_get_config('dbprefix');
282:         $options = array(
283:             'guids' => $guids,
284:             'limit' => 0,
285:             'callback' => false,
286:             'joins' => array(
287:                 "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id",
288:                 "JOIN {$db_prefix}metastrings n ON n_table.name_id = n.id",
289:             ),
290:             'selects' => array('n.string AS name', 'v.string AS value'),
291:             'order_by' => 'n_table.entity_guid, n_table.time_created ASC',
292: 
293:             // @todo don't know why this is necessary
294:             'wheres' => array(get_access_sql_suffix('n_table')),
295:         );
296:         $data = elgg_get_metadata($options);
297: 
298:         // build up metadata for each entity, save when GUID changes (or data ends)
299:         $last_guid = null;
300:         $metadata = array();
301:         $last_row_idx = count($data) - 1;
302:         foreach ($data as $i => $row) {
303:             $name = $row->name;
304:             $value = ($row->value_type === 'text') ? $row->value : (int) $row->value;
305:             $guid = $row->entity_guid;
306:             if ($guid !== $last_guid) {
307:                 if ($last_guid) {
308:                     $this->saveAll($last_guid, $metadata);
309:                 }
310:                 $metadata = array();
311:             }
312:             if (isset($metadata[$name])) {
313:                 $metadata[$name] = (array) $metadata[$name];
314:                 $metadata[$name][] = $value;
315:             } else {
316:                 $metadata[$name] = $value;
317:             }
318:             if (($i == $last_row_idx)) {
319:                 $this->saveAll($guid, $metadata);
320:             }
321:             $last_guid = $guid;
322:         }
323:     }
324: 
325:     /**
326:      * Filter out entities whose concatenated metadata values (INTs casted as string)
327:      * exceed a threshold in characters. This could be used to avoid overpopulating the
328:      * cache if RAM usage becomes an issue.
329:      *
330:      * @param array $guids GUIDs of entities to examine
331:      * @param int   $limit Limit in characters of all metadata (with ints casted to strings)
332:      * @return array
333:      */
334:     public function filterMetadataHeavyEntities(array $guids, $limit = 1024000) {
335:         $db_prefix = elgg_get_config('dbprefix');
336: 
337:         $options = array(
338:             'guids' => $guids,
339:             'limit' => 0,
340:             'callback' => false,
341:             'joins' => "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id",
342:             'selects' => array('SUM(LENGTH(v.string)) AS bytes'),
343:             'order_by' => 'n_table.entity_guid, n_table.time_created ASC',
344:             'group_by' => 'n_table.entity_guid',
345:         );
346:         $data = elgg_get_metadata($options);
347:         // don't cache if metadata for entity is over 10MB (or rolled INT)
348:         foreach ($data as $row) {
349:             if ($row->bytes > $limit || $row->bytes < 0) {
350:                 array_splice($guids, array_search($row->entity_guid, $guids), 1);
351:             }
352:         }
353:         return $guids;
354:     }
355: }
356: 
API documentation generated by ApiGen 2.8.0