Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[Cache] Add DSN, createClient & better error reporting to MemcachedAdapter#21108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -115,6 +115,21 @@ public static function createSystemCache($namespace, $defaultLifetime, $version, | ||
| return new ChainAdapter(array($apcu, $fs)); | ||
| } | ||
| public static function createConnection($dsn, array $options = array()) | ||
| { | ||
| if (!is_string($dsn)) { | ||
| throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, gettype($dsn))); | ||
| } | ||
| if (0 === strpos($dsn, 'redis://')) { | ||
| return RedisAdapter::createConnection($dsn, $options); | ||
| } | ||
| if (0 === strpos($dsn, 'memcached://')) { | ||
| return MemcachedAdapter::createConnection($dsn, $options); | ||
| } | ||
| throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn)); | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Should the MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This is a stateless static method, no need for yet another class. This has to be somewhere :) | ||
| } | ||
| /** | ||
| * Fetches several cache items. | ||
| * | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -11,68 +11,234 @@ | ||
| namespace Symfony\Component\Cache\Adapter; | ||
| use Symfony\Component\Cache\Exception\CacheException; | ||
| use Symfony\Component\Cache\Exception\InvalidArgumentException; | ||
| /** | ||
| * @author Rob Frawley 2nd <rmf@src.run> | ||
| * @author Nicolas Grekas <p@tchwork.com> | ||
| */ | ||
| class MemcachedAdapter extends AbstractAdapter | ||
| { | ||
| private static $defaultClientOptions = array( | ||
| 'persistent_id' => null, | ||
| 'username' => null, | ||
| 'password' => null, | ||
| ); | ||
| protected $maxIdLength = 250; | ||
| private $client; | ||
| public static function isSupported() | ||
| { | ||
| return extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>='); | ||
| } | ||
| public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0) | ||
| { | ||
| if (!static::isSupported()) { | ||
| throw new CacheException('Memcached >= 2.2.0 is required'); | ||
| } | ||
| $opt = $client->getOption(\Memcached::OPT_SERIALIZER); | ||
| if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { | ||
| throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); | ||
| } | ||
| if (!$client->getOption(\Memcached::OPT_BINARY_PROTOCOL)) { | ||
| throw new CacheException('MemcachedAdapter: "binary_protocol" option must be enabled.'); | ||
| } | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I understand the serializer requirement, but why are weforcing binary protocol and no_block=false? MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Binary because we must support all chars in keys especially spaces, per psr-6. Contributor
| ||
| $this->maxIdLength -= strlen($client->getOption(\Memcached::OPT_PREFIX_KEY)); | ||
| parent::__construct($namespace, $defaultLifetime); | ||
| $this->client = $client; | ||
| } | ||
| /** | ||
| * Creates a Memcached instance. | ||
| * | ||
| * By default, the binary protocol, no block, and libketama compatible options are enabled. | ||
| * | ||
| * Examples for servers: | ||
| * - 'memcached://user:pass@localhost?weight=33' | ||
| * - array(array('localhost', 11211, 33)) | ||
| * | ||
| * @param array[]|string|string[] An array of servers, a DSN, or an array of DSNs | ||
| * @param array An array of options | ||
| * | ||
| * @return \Memcached | ||
| * | ||
| * @throws \ErrorEception When invalid options or servers are provided. | ||
| */ | ||
| public static function createConnection($servers, array $options = array()) | ||
| { | ||
| if (is_string($servers)) { | ||
| $servers = array($servers); | ||
| } elseif (!is_array($servers)) { | ||
| throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, %s given.', gettype($servers))); | ||
| } | ||
| set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); | ||
| try { | ||
| if (!static::isSupported()) { | ||
| throw new trigger_error('Memcached >= 2.2.0 is required'); | ||
| } | ||
| $options += static::$defaultClientOptions; | ||
| $client = new \Memcached($options['persistent_id']); | ||
| $username = $options['username']; | ||
| $password = $options['password']; | ||
| unset($options['persistent_id'], $options['username'], $options['password']); | ||
| $options = array_change_key_case($options, CASE_UPPER); | ||
| // set client's options | ||
| $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); | ||
| $client->setOption(\Memcached::OPT_NO_BLOCK, true); | ||
| if (!array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) { | ||
| $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true); | ||
| } | ||
| foreach ($options as $name => $value) { | ||
Contributor
| ||
| if (is_int($name)) { | ||
| continue; | ||
| } | ||
| if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) { | ||
| $value = constant('Memcached::'.$name.'_'.strtoupper($value)); | ||
| } | ||
| $opt = constant('Memcached::OPT_'.$name); | ||
| unset($options[$name]); | ||
| $options[$opt] = $value; | ||
| } | ||
| $client->setOptions($options); | ||
| // parse any DSN in $servers | ||
| foreach ($servers as $i => $dsn) { | ||
| if (is_array($dsn)) { | ||
| continue; | ||
| } | ||
| if (0 !== strpos($dsn, 'memcached://')) { | ||
| throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached://"', $dsn)); | ||
| } | ||
| $params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { | ||
| if (!empty($m[1])) { | ||
| list($username, $password) = explode(':', $m[1], 2) + array(1 => null); | ||
| } | ||
| return 'file://'; | ||
| }, $dsn); | ||
| if (false === $params = parse_url($params)) { | ||
| throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn)); | ||
| } | ||
| if (!isset($params['host']) && !isset($params['path'])) { | ||
| throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn)); | ||
| } | ||
| if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) { | ||
| $params['weight'] = $m[1]; | ||
| $params['path'] = substr($params['path'], 0, -strlen($m[0])); | ||
| } | ||
| $params += array( | ||
| 'host' => isset($params['host']) ? $params['host'] : $params['path'], | ||
| 'port' => isset($params['host']) ? 11211 : null, | ||
| 'weight' => 0, | ||
| ); | ||
| if (isset($params['query'])) { | ||
| parse_str($params['query'], $query); | ||
| $params += $query; | ||
| } | ||
| $servers[$i] = array($params['host'], $params['port'], $params['weight']); | ||
| } | ||
| // set client's servers, taking care of persistent connections | ||
| if (!$client->isPristine()) { | ||
Contributor
| ||
| $oldServers = array(); | ||
| foreach ($client->getServerList() as $server) { | ||
| $oldServers[] = array($server['host'], $server['port']); | ||
| } | ||
| $newServers = array(); | ||
| foreach ($servers as $server) { | ||
| if (1 < count($server)) { | ||
| $server = array_values($server); | ||
| unset($server[2]); | ||
| $server[1] = (int) $server[1]; | ||
| } | ||
| $newServers[] = $server; | ||
| } | ||
| if ($oldServers !== $newServers) { | ||
| // before resetting, ensure $servers is valid | ||
| $client->addServers($servers); | ||
| $client->resetServerList(); | ||
| } | ||
| } | ||
| $client->addServers($servers); | ||
| if (null !== $username || null !== $password) { | ||
| if (!method_exists($client, 'setSaslAuthData')) { | ||
| trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.'); | ||
| } | ||
| $client->setSaslAuthData($username, $password); | ||
| } | ||
| return $client; | ||
| } finally { | ||
| restore_error_handler(); | ||
| } | ||
| } | ||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function doSave(array $values, $lifetime) | ||
| { | ||
| return $this->checkResultCode($this->client->setMulti($values, $lifetime)); | ||
| } | ||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function doFetch(array $ids) | ||
| { | ||
| return $this->checkResultCode($this->client->getMulti($ids)); | ||
| } | ||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function doHave($id) | ||
| { | ||
| returnfalse !==$this->client->get($id) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); | ||
| } | ||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function doDelete(array $ids) | ||
| { | ||
| $ok =true; | ||
| foreach ($this->checkResultCode($this->client->deleteMulti($ids)) as $result) { | ||
| if (\Memcached::RES_SUCCESS!== $result&& \Memcached::RES_NOTFOUND!== $result) { | ||
| $ok = false; | ||
| } | ||
| } | ||
| return$ok; | ||
| } | ||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function doClear($namespace) | ||
| { | ||
| return $this->checkResultCode($this->client->flush()); | ||
| } | ||
| private function checkResultCode($result) | ||
| { | ||
| $code = $this->client->getResultCode(); | ||
| if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) { | ||
| return $result; | ||
| } | ||
| throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage()))); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading.Please reload this page.