142 if (!extension_loaded(
'redis')) {
143 throw new Exception(
'The PHP extension "redis" must be installed and loaded in order to use the redis backend.', 1279462933);
145 parent::__construct(
$context, $options);
155 $this->redis = new \Redis();
157 if ($this->persistentConnection) {
158 $this->connected = $this->redis->pconnect($this->hostname, $this->port, $this->connectionTimeout, (
string)$this->database);
160 $this->connected = $this->redis->connect($this->hostname, $this->port, $this->connectionTimeout);
162 }
catch (\Exception $e) {
163 $this->logger->alert(
'Could not connect to redis server.', [
'exception' => $e]);
165 if ($this->connected) {
166 if ($this->password !==
'') {
167 $success = $this->redis->auth($this->password);
169 throw new Exception(
'The given password was not accepted by the redis server.', 1279765134);
172 if ($this->database >= 0) {
173 $success = $this->redis->select($this->database);
175 throw new Exception(
'The given database "' . $this->database .
'" could not be selected.', 1279765144);
220 throw new \InvalidArgumentException(
'The specified database number is of type "' . gettype(
$database) .
'" but an integer is expected.', 1279763057);
223 throw new \InvalidArgumentException(
'The specified database "' .
$database .
'" must be greater or equal than zero.', 1279763534);
247 throw new \InvalidArgumentException(
'The specified compression of type "' . gettype(
$compression) .
'" but a boolean is expected.', 1289679153);
263 throw new \InvalidArgumentException(
'The specified compression of type "' . gettype(
$compressionLevel) .
'" but an integer is expected.', 1289679154);
268 throw new \InvalidArgumentException(
'The specified compression level must be an integer between -1 and 9.', 1289679155);
283 throw new \InvalidArgumentException(
'The specified connection timeout is of type "' . gettype(
$connectionTimeout) .
'" but an integer is expected.', 1487849315);
287 throw new \InvalidArgumentException(
'The specified connection timeout "' .
$connectionTimeout .
'" must be greater or equal than zero.', 1487849326);
306 public function set($entryIdentifier, $data, array $tags = [], $lifetime =
null)
309 throw new \InvalidArgumentException(
'The specified identifier is of type "' . gettype($entryIdentifier) .
'" which can\'t be converted to string.', 1377006651);
311 if (!is_string($data)) {
312 throw new InvalidDataException(
'The specified data is of type "' . gettype($data) .
'" but a string is expected.', 1279469941);
315 if (!is_int($lifetime)) {
316 throw new \InvalidArgumentException(
'The specified lifetime is of type "' . gettype($lifetime) .
'" but an integer or NULL is expected.', 1279488008);
319 throw new \InvalidArgumentException(
'The specified lifetime "' . $lifetime .
'" must be greater or equal than zero.', 1279487573);
321 if ($this->connected) {
323 if ($this->compression) {
324 $data = gzcompress($data, $this->compressionLevel);
326 $this->redis->setex(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier, $expiration, $data);
329 $existingTags = $this->redis->sMembers(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier);
330 if (!empty($existingTags)) {
331 $addTags = array_diff($tags, $existingTags);
332 $removeTags = array_diff($existingTags, $tags);
334 if (!empty($removeTags) || !empty($addTags)) {
335 $queue = $this->redis->multi(\Redis::PIPELINE);
336 foreach ($removeTags as $tag) {
337 $queue->sRem(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier, $tag);
338 $queue->sRem(self::TAG_IDENTIFIERS_PREFIX . $tag, $entryIdentifier);
340 foreach ($addTags as $tag) {
341 $queue->sAdd(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier, $tag);
342 $queue->sAdd(self::TAG_IDENTIFIERS_PREFIX . $tag, $entryIdentifier);
358 public function get($entryIdentifier)
361 throw new \InvalidArgumentException(
'The specified identifier is of type "' . gettype($entryIdentifier) .
'" which can\'t be converted to string.', 1377006652);
363 $storedEntry =
false;
364 if ($this->connected) {
365 $storedEntry = $this->redis->get(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier);
367 if ($this->compression && (
string)$storedEntry !==
'') {
368 $storedEntry = gzuncompress($storedEntry);
382 public function has($entryIdentifier)
385 throw new \InvalidArgumentException(
'The specified identifier is of type "' . gettype($entryIdentifier) .
'" which can\'t be converted to string.', 1377006653);
387 return $this->connected && $this->redis->exists(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier);
400 public function remove($entryIdentifier)
403 throw new \InvalidArgumentException(
'The specified identifier is of type "' . gettype($entryIdentifier) .
'" which can\'t be converted to string.', 1377006654);
405 $elementsDeleted =
false;
406 if ($this->connected) {
407 if ($this->redis->exists(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier)) {
408 $assignedTags = $this->redis->sMembers(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier);
409 $queue = $this->redis->multi(\Redis::PIPELINE);
410 foreach ($assignedTags as $tag) {
411 $queue->sRem(self::TAG_IDENTIFIERS_PREFIX . $tag, $entryIdentifier);
413 $queue->del(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier, self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier);
415 $elementsDeleted =
true;
418 return $elementsDeleted;
435 throw new \InvalidArgumentException(
'The specified tag is of type "' . gettype($tag) .
'" which can\'t be converted to string.', 1377006655);
437 $foundIdentifiers = [];
438 if ($this->connected) {
439 $foundIdentifiers = $this->redis->sMembers(self::TAG_IDENTIFIERS_PREFIX . $tag);
441 return $foundIdentifiers;
451 if ($this->connected) {
452 $this->redis->flushDB();
468 throw new \InvalidArgumentException(
'The specified tag is of type "' . gettype($tag) .
'" which can\'t be converted to string.', 1377006656);
470 if ($this->connected) {
471 $identifiers = $this->redis->sMembers(self::TAG_IDENTIFIERS_PREFIX . $tag);
472 if (!empty($identifiers)) {
488 $identifierToTagsKeys = $this->redis->keys(self::IDENTIFIER_TAGS_PREFIX .
'*');
489 foreach ($identifierToTagsKeys as $identifierToTagsKey) {
490 list(, $identifier) = explode(
':', $identifierToTagsKey);
492 if (!$this->redis->exists(self::IDENTIFIER_DATA_PREFIX . $identifier)) {
493 $tagsToRemoveIdentifierFrom = $this->redis->sMembers($identifierToTagsKey);
494 $queue = $this->redis->multi(\Redis::PIPELINE);
495 $queue->del($identifierToTagsKey);
496 foreach ($tagsToRemoveIdentifierFrom as $tag) {
497 $queue->sRem(self::TAG_IDENTIFIERS_PREFIX . $tag, $identifier);
519 $prefixedKeysToDelete = [$uniqueTempKey];
520 $prefixedIdentifierToTagsKeysToDelete = [];
521 foreach ($identifiers as $identifier) {
522 $prefixedKeysToDelete[] = self::IDENTIFIER_DATA_PREFIX . $identifier;
523 $prefixedIdentifierToTagsKeysToDelete[] = self::IDENTIFIER_TAGS_PREFIX . $identifier;
525 foreach ($tags as $tag) {
526 $prefixedKeysToDelete[] = self::TAG_IDENTIFIERS_PREFIX . $tag;
528 $tagToIdentifiersSetsToRemoveIdentifiersFrom = $this->redis->sUnion($prefixedIdentifierToTagsKeysToDelete);
530 $tagToIdentifiersSetsToRemoveIdentifiersFrom = array_diff($tagToIdentifiersSetsToRemoveIdentifiersFrom, $tags);
533 $queue = $this->redis->multi(\Redis::PIPELINE);
534 foreach ($identifiers as $identifier) {
535 $queue->sAdd($uniqueTempKey, $identifier);
537 foreach ($tagToIdentifiersSetsToRemoveIdentifiersFrom as $tagToIdentifiersSet) {
538 $queue->sDiffStore(self::TAG_IDENTIFIERS_PREFIX . $tagToIdentifiersSet, self::TAG_IDENTIFIERS_PREFIX . $tagToIdentifiersSet, $uniqueTempKey);
540 $queue->del(array_merge($prefixedKeysToDelete, $prefixedIdentifierToTagsKeysToDelete));
552 return is_scalar($variable) || (is_object($variable) && method_exists($variable,
'__toString'));