0) { throw new Exception\ExtensionNotLoadedException("Missing ext/memcache version >= 2.0.0"); } parent::__construct($options); // reset initialized flag on update option(s) $initialized = &$this->initialized; $this->getEventManager()->attach('option', function () use (&$initialized) { $initialized = false; }); } /** * Initialize the internal memcache resource * * @return MemcacheResource */ protected function getMemcacheResource() { if ($this->initialized) { return $this->resourceManager->getResource($this->resourceId); } $options = $this->getOptions(); // get resource manager and resource id $this->resourceManager = $options->getResourceManager(); $this->resourceId = $options->getResourceId(); // init namespace prefix $this->namespacePrefix = ''; $namespace = $options->getNamespace(); if ($namespace !== '') { $this->namespacePrefix = $namespace . $options->getNamespaceSeparator(); } // update initialized flag $this->initialized = true; return $this->resourceManager->getResource($this->resourceId); } /* options */ /** * Set options. * * @see getOptions() * * @param array|Traversable|MemcacheOptions $options * @return Memcache */ public function setOptions($options) { if (! $options instanceof MemcacheOptions) { $options = new MemcacheOptions($options); } return parent::setOptions($options); } /** * Get options. * * @see setOptions() * * @return MemcacheOptions */ public function getOptions() { if (! $this->options) { $this->setOptions(new MemcacheOptions()); } return $this->options; } /** * @param mixed $value * @return int */ protected function getWriteFlag(&$value) { if (! $this->getOptions()->getCompression()) { return 0; } // Don't compress numeric or boolean types return is_bool($value) || is_int($value) || is_float($value) ? 0 : MEMCACHE_COMPRESSED; } /* FlushableInterface */ /** * Flush the whole storage * * @return bool */ public function flush() { $memc = $this->getMemcacheResource(); if (! $memc->flush()) { return new Exception\RuntimeException("Memcache flush failed"); } return true; } /* TotalSpaceCapableInterface */ /** * Get total space in bytes * * @return int|float */ public function getTotalSpace() { $memc = $this->getMemcacheResource(); $stats = $memc->getExtendedStats(); if ($stats === false) { return new Exception\RuntimeException("Memcache getStats failed"); } $mem = array_pop($stats); return $mem['limit_maxbytes']; } /* AvailableSpaceCapableInterface */ /** * Get available space in bytes * * @return int|float */ public function getAvailableSpace() { $memc = $this->getMemcacheResource(); $stats = $memc->getExtendedStats(); if ($stats === false) { throw new Exception\RuntimeException('Memcache getStats failed'); } $mem = array_pop($stats); return $mem['limit_maxbytes'] - $mem['bytes']; } /* reading */ /** * Internal method to get an item. * * @param string $normalizedKey * @param bool $success * @param mixed $casToken * @return mixed Data on success, null on failure * @throws Exception\ExceptionInterface */ protected function internalGetItem(&$normalizedKey, &$success = null, &$casToken = null) { $memc = $this->getMemcacheResource(); $internalKey = $this->namespacePrefix . $normalizedKey; $result = $memc->get($internalKey); $success = $result !== false; if ($result === false) { return; } $casToken = $result; return $result; } /** * Internal method to get multiple items. * * @param array $normalizedKeys * @return array Associative array of keys and values * @throws Exception\ExceptionInterface */ protected function internalGetItems(array &$normalizedKeys) { $memc = $this->getMemcacheResource(); foreach ($normalizedKeys as & $normalizedKey) { $normalizedKey = $this->namespacePrefix . $normalizedKey; } $result = $memc->get($normalizedKeys); if ($result === false) { return []; } // remove namespace prefix from result if ($this->namespacePrefix !== '') { $tmp = []; $nsPrefixLength = strlen($this->namespacePrefix); foreach ($result as $internalKey => &$value) { $tmp[substr($internalKey, $nsPrefixLength)] = &$value; } $result = $tmp; } return $result; } /** * Internal method to test if an item exists. * * @param string $normalizedKey * @return bool * @throws Exception\ExceptionInterface */ protected function internalHasItem(&$normalizedKey) { $memc = $this->getMemcacheResource(); $value = $memc->get($this->namespacePrefix . $normalizedKey); return $value !== false; } /** * Internal method to test multiple items. * * @param array $normalizedKeys * @return array Array of found keys * @throws Exception\ExceptionInterface */ protected function internalHasItems(array &$normalizedKeys) { $memc = $this->getMemcacheResource(); foreach ($normalizedKeys as & $normalizedKey) { $normalizedKey = $this->namespacePrefix . $normalizedKey; } $result = $memc->get($normalizedKeys); if ($result === false) { return []; } // Convert to a single list $result = array_keys($result); // remove namespace prefix if ($result && $this->namespacePrefix !== '') { $nsPrefixLength = strlen($this->namespacePrefix); foreach ($result as & $internalKey) { $internalKey = substr($internalKey, $nsPrefixLength); } } return $result; } /** * Get metadata of multiple items * * @param array $normalizedKeys * @return array Associative array of keys and metadata * @throws Exception\ExceptionInterface */ protected function internalGetMetadatas(array &$normalizedKeys) { $memc = $this->getMemcacheResource(); foreach ($normalizedKeys as & $normalizedKey) { $normalizedKey = $this->namespacePrefix . $normalizedKey; } $result = $memc->get($normalizedKeys); if ($result === false) { return []; } // remove namespace prefix and use an empty array as metadata if ($this->namespacePrefix === '') { foreach ($result as & $value) { $value = []; } return $result; } $final = []; $nsPrefixLength = strlen($this->namespacePrefix); foreach (array_keys($result) as $internalKey) { $final[substr($internalKey, $nsPrefixLength)] = []; } return $final; } /* writing */ /** * Internal method to store an item. * * @param string $normalizedKey * @param mixed $value * @return bool * @throws Exception\ExceptionInterface */ protected function internalSetItem(&$normalizedKey, &$value) { $memc = $this->getMemcacheResource(); $expiration = $this->expirationTime(); $flag = $this->getWriteFlag($value); if (! $memc->set($this->namespacePrefix . $normalizedKey, $value, $flag, $expiration)) { throw new Exception\RuntimeException('Memcache set value failed'); } return true; } /** * Add an item. * * @param string $normalizedKey * @param mixed $value * @return bool * @throws Exception\ExceptionInterface */ protected function internalAddItem(&$normalizedKey, &$value) { $memc = $this->getMemcacheResource(); $expiration = $this->expirationTime(); $flag = $this->getWriteFlag($value); return $memc->add($this->namespacePrefix . $normalizedKey, $value, $flag, $expiration); } /** * Internal method to replace an existing item. * * @param string $normalizedKey * @param mixed $value * @return bool * @throws Exception\ExceptionInterface */ protected function internalReplaceItem(&$normalizedKey, &$value) { $memc = $this->getMemcacheResource(); $expiration = $this->expirationTime(); $flag = $this->getWriteFlag($value); return $memc->replace($this->namespacePrefix . $normalizedKey, $value, $flag, $expiration); } /** * Internal method to remove an item. * * @param string $normalizedKey * @return bool * @throws Exception\ExceptionInterface */ protected function internalRemoveItem(&$normalizedKey) { $memc = $this->getMemcacheResource(); // Delete's second parameter (timeout) is deprecated and not supported. // Values other than 0 may cause delete to fail. // http://www.php.net/manual/memcache.delete.php return $memc->delete($this->namespacePrefix . $normalizedKey, 0); } /** * Internal method to increment an item. * * @param string $normalizedKey * @param int $value * @return int|bool The new value on success, false on failure * @throws Exception\ExceptionInterface */ protected function internalIncrementItem(&$normalizedKey, &$value) { $memc = $this->getMemcacheResource(); $internalKey = $this->namespacePrefix . $normalizedKey; $value = (int) $value; $newValue = $memc->increment($internalKey, $value); if ($newValue !== false) { return $newValue; } // Set initial value. Don't use compression! // http://www.php.net/manual/memcache.increment.php $newValue = $value; if (! $memc->add($internalKey, $newValue, 0, $this->expirationTime())) { throw new Exception\RuntimeException('Memcache unable to add increment value'); } return $newValue; } /** * Internal method to decrement an item. * * @param string $normalizedKey * @param int $value * @return int|bool The new value on success, false on failure * @throws Exception\ExceptionInterface */ protected function internalDecrementItem(&$normalizedKey, &$value) { $memc = $this->getMemcacheResource(); $internalKey = $this->namespacePrefix . $normalizedKey; $value = (int) $value; $newValue = $memc->decrement($internalKey, $value); if ($newValue !== false) { return $newValue; } // Set initial value. Don't use compression! // http://www.php.net/manual/memcache.decrement.php $newValue = -$value; if (! $memc->add($internalKey, $newValue, 0, $this->expirationTime())) { throw new Exception\RuntimeException('Memcache unable to add decrement value'); } return $newValue; } /* status */ /** * Internal method to get capabilities of this adapter * * @return Capabilities */ protected function internalGetCapabilities() { if ($this->capabilities !== null) { return $this->capabilities; } if (version_compare('3.0.3', phpversion('memcache')) <= 0) { // In ext/memcache v3.0.3: // Scalar data types (int, bool, double) are preserved by get/set. // http://pecl.php.net/package/memcache/3.0.3 // // This effectively removes support for `boolean` types since // "not found" return values are === false. $supportedDatatypes = [ 'NULL' => true, 'boolean' => false, 'integer' => true, 'double' => true, 'string' => true, 'array' => true, 'object' => 'object', 'resource' => false, ]; } else { // In stable 2.x ext/memcache versions, scalar data types are // converted to strings and must be manually cast back to original // types by the user. // // ie. It is impossible to know if the saved value: (string)"1" // was previously: (bool)true, (int)1, or (string)"1". // Similarly, the saved value: (string)"" // might have previously been: (bool)false or (string)"" $supportedDatatypes = [ 'NULL' => true, 'boolean' => 'boolean', 'integer' => 'integer', 'double' => 'double', 'string' => true, 'array' => true, 'object' => 'object', 'resource' => false, ]; } $this->capabilityMarker = new stdClass(); $this->capabilities = new Capabilities( $this, $this->capabilityMarker, [ 'supportedDatatypes' => $supportedDatatypes, 'supportedMetadata' => [], 'minTtl' => 1, 'maxTtl' => 0, 'staticTtl' => true, 'ttlPrecision' => 1, 'useRequestTime' => false, 'maxKeyLength' => 255, 'namespaceIsPrefix' => true, ] ); return $this->capabilities; } /* internal */ /** * Get expiration time by ttl * * Some storage commands involve sending an expiration value (relative to * an item or to an operation requested by the client) to the server. In * all such cases, the actual value sent may either be Unix time (number of * seconds since January 1, 1970, as an integer), or a number of seconds * starting from current time. In the latter case, this number of seconds * may not exceed 60*60*24*30 (number of seconds in 30 days); if the * expiration value is larger than that, the server will consider it to be * real Unix time value rather than an offset from current time. * * @return int */ protected function expirationTime() { $ttl = $this->getOptions()->getTtl(); if ($ttl > 2592000) { return time() + $ttl; } return $ttl; } }