Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 10ea36a

Browse filesBrowse files
committed
Add the DSN component
1 parent 55a7691 commit 10ea36a
Copy full SHA for 10ea36a

13 files changed

+719
-0
lines changed

‎src/Symfony/Component/Dsn/.gitignore

Copy file name to clipboard
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
composer.lock
2+
phpunit.xml
3+
vendor/
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
3.4.0
5+
-----
6+
7+
* added the component
+67Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dsn;
13+
14+
use Symfony\Component\Dsn\Exception\InvalidArgumentException;
15+
use Symfony\Component\Dsn\Factory\MemcachedConnectionFactory;
16+
use Symfony\Component\Dsn\Factory\RedisConnectionFactory;
17+
18+
/**
19+
* Factory for undetermined Dsn.
20+
*
21+
* @author Jérémy Derussé <jeremy@derusse.com>
22+
*/
23+
class ConnectionFactory
24+
{
25+
const TYPE_REDIS = 1;
26+
const TYPE_MEMCACHED = 2;
27+
28+
/**
29+
* @param string $dsn
30+
*
31+
* @return int
32+
*/
33+
public static function getType($dsn)
34+
{
35+
if (!is_string($dsn)) {
36+
throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, gettype($dsn)));
37+
}
38+
if (0 === strpos($dsn, 'redis://')) {
39+
return static::TYPE_REDIS;
40+
}
41+
if (0 === strpos($dsn, 'memcached://')) {
42+
return static::TYPE_MEMCACHED;
43+
}
44+
45+
throw new InvalidArgumentException(sprintf('Unsupported Dsn: %s.', $dsn));
46+
}
47+
48+
/**
49+
* Create a connection for a redis Dsn.
50+
*
51+
* @param string $dsn
52+
* @param array $options
53+
*
54+
* @return mixed
55+
*/
56+
public static function createConnection($dsn, array $options = array())
57+
{
58+
switch (static::getType($dsn)) {
59+
case static::TYPE_REDIS:
60+
return RedisConnectionFactory::createConnection($dsn, $options);
61+
case static::TYPE_MEMCACHED:
62+
return MemcachedConnectionFactory::createConnection($dsn, $options);
63+
default:
64+
throw new InvalidArgumentException(sprintf('Unsupported Dsn: %s.', $dsn));
65+
}
66+
}
67+
}
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dsn\Exception;
13+
14+
/**
15+
* Interface for exceptions.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
interface ExceptionInterface
20+
{
21+
}
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dsn\Exception;
13+
14+
/**
15+
* Base InvalidArgumentException for the Dsn component.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
20+
{
21+
}
+156Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dsn\Factory;
13+
14+
use Symfony\Component\Dsn\Exception\InvalidArgumentException;
15+
16+
/**
17+
* Factory for Memcached connections.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
class MemcachedConnectionFactory
22+
{
23+
private static $defaultClientOptions = array(
24+
'persistent_id' => null,
25+
'username' => null,
26+
'password' => null,
27+
'serializer' => 'php',
28+
);
29+
30+
/**
31+
* Creates a Memcached instance.
32+
*
33+
* By default, the binary protocol, no block, and libketama compatible options are enabled.
34+
*
35+
* Examples for servers:
36+
* - memcached://localhost
37+
* - memcached://example.com:1234
38+
* - memcached://user:pass@example.com
39+
* - memcached://localhost?weight=25
40+
* - memcached:///var/run/memcached.sock?weight=25
41+
* - memcached://user:password@/var/run/memcached.socket?weight=25
42+
* - array('memcached://server1', 'memcached://server2')
43+
*
44+
* @param string|string[] A DSN, or an array of DSNs
45+
* @param array An array of options
46+
*
47+
* @return \Memcached
48+
*
49+
* @throws \ErrorException When invalid options or servers are provided
50+
*/
51+
public static function createConnection($dsns, array $options = array())
52+
{
53+
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
54+
try {
55+
$options += static::$defaultClientOptions;
56+
$client = new \Memcached($options['persistent_id']);
57+
$username = $options['username'];
58+
$password = $options['password'];
59+
60+
// parse any DSN in $dsns
61+
$servers=[];
62+
foreach ((array) $dsns as $dsn) {
63+
if (0 !== strpos($dsn, 'memcached://')) {
64+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached://"', $dsn));
65+
}
66+
$params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
67+
if (!empty($m[1])) {
68+
list($username, $password) = explode(':', $m[1], 2) + array(1 => null);
69+
}
70+
71+
return 'file://';
72+
}, $dsn);
73+
if (false === $params = parse_url($params)) {
74+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
75+
}
76+
if (!isset($params['host']) && !isset($params['path'])) {
77+
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
78+
}
79+
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
80+
$params['weight'] = $m[1];
81+
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
82+
}
83+
$params += array(
84+
'host' => isset($params['host']) ? $params['host'] : $params['path'],
85+
'port' => isset($params['host']) ? 11211 : null,
86+
'weight' => 0,
87+
);
88+
if (isset($params['query'])) {
89+
parse_str($params['query'], $query);
90+
$params += $query;
91+
$options = $query + $options;
92+
}
93+
94+
$servers[] = array($params['host'], $params['port'], $params['weight']);
95+
}
96+
97+
// set client's options
98+
unset($options['persistent_id'], $options['username'], $options['password'], $options['weight']);
99+
$options = array_change_key_case($options, CASE_UPPER);
100+
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
101+
$client->setOption(\Memcached::OPT_NO_BLOCK, true);
102+
if (!array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
103+
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
104+
}
105+
foreach ($options as $name => $value) {
106+
if (is_int($name)) {
107+
continue;
108+
}
109+
if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
110+
$value = constant('Memcached::'.$name.'_'.strtoupper($value));
111+
}
112+
$opt = constant('Memcached::OPT_'.$name);
113+
114+
unset($options[$name]);
115+
$options[$opt] = $value;
116+
}
117+
$client->setOptions($options);
118+
119+
// set client's servers, taking care of persistent connections
120+
if (!$client->isPristine()) {
121+
$oldServers = array();
122+
foreach ($client->getServerList() as $server) {
123+
$oldServers[] = array($server['host'], $server['port']);
124+
}
125+
126+
$newServers = array();
127+
foreach ($servers as $server) {
128+
if (1 < count($server)) {
129+
$server = array_values($server);
130+
unset($server[2]);
131+
$server[1] = (int) $server[1];
132+
}
133+
$newServers[] = $server;
134+
}
135+
136+
if ($oldServers !== $newServers) {
137+
// before resetting, ensure $servers is valid
138+
$client->addServers($servers);
139+
$client->resetServerList();
140+
}
141+
}
142+
$client->addServers($servers);
143+
144+
if (null !== $username || null !== $password) {
145+
if (!method_exists($client, 'setSaslAuthData')) {
146+
trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
147+
}
148+
$client->setSaslAuthData($username, $password);
149+
}
150+
151+
return $client;
152+
} finally {
153+
restore_error_handler();
154+
}
155+
}
156+
}
+118Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dsn\Factory;
13+
14+
use Symfony\Component\Dsn\Exception\InvalidArgumentException;
15+
16+
/**
17+
* Factory for Redis Dsn.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
class RedisConnectionFactory
22+
{
23+
private static $defaultConnectionOptions = array(
24+
'class' => null,
25+
'persistent' => 0,
26+
'persistent_id' => null,
27+
'timeout' => 30,
28+
'read_timeout' => 0,
29+
'retry_interval' => 0,
30+
);
31+
32+
/**
33+
* Creates a Redis connection using a DSN configuration.
34+
*
35+
* Example DSN:
36+
* - redis://localhost
37+
* - redis://example.com:1234
38+
* - redis://secret@example.com/13
39+
* - redis:///var/run/redis.sock
40+
* - redis://secret@/var/run/redis.sock/13
41+
*
42+
* @param string $dsn
43+
* @param array $options See self::$defaultConnectionOptions
44+
*
45+
* @throws InvalidArgumentException when the DSN is invalid
46+
*
47+
* @return \Redis|\Predis\Client According to the "class" option
48+
*/
49+
public static function createConnection($dsn, array $options = array())
50+
{
51+
if (0 !== strpos($dsn, 'redis://')) {
52+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis://"', $dsn));
53+
}
54+
$params = preg_replace_callback('#^redis://(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
55+
if (isset($m[1])) {
56+
$auth = $m[1];
57+
}
58+
59+
return 'file://';
60+
}, $dsn);
61+
if (false === $params = parse_url($params)) {
62+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
63+
}
64+
if (!isset($params['host']) && !isset($params['path'])) {
65+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
66+
}
67+
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
68+
$params['dbindex'] = $m[1];
69+
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
70+
}
71+
if (isset($params['host'])) {
72+
$scheme = 'tcp';
73+
} else {
74+
$scheme = 'unix';
75+
}
76+
$params += array(
77+
'host' => isset($params['host']) ? $params['host'] : $params['path'],
78+
'port' => isset($params['host']) ? 6379 : null,
79+
'dbindex' => 0,
80+
);
81+
if (isset($params['query'])) {
82+
parse_str($params['query'], $query);
83+
$params += $query;
84+
}
85+
$params += $options + self::$defaultConnectionOptions;
86+
$class = null === $params['class'] ? (extension_loaded('redis') ? \Redis::class : \Predis\Client::class) : $params['class'];
87+
88+
if (is_a($class, \Redis::class, true)) {
89+
$connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
90+
$redis = new $class();
91+
@$redis->{$connect}($params['host'], $params['port'], $params['timeout'], $params['persistent_id'], $params['retry_interval']);
92+
93+
if (@!$redis->isConnected()) {
94+
$e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : '';
95+
throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn));
96+
}
97+
98+
if ((null !== $auth && !$redis->auth($auth))
99+
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
100+
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
101+
) {
102+
$e = preg_replace('/^ERR /', '', $redis->getLastError());
103+
throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
104+
}
105+
} elseif (is_a($class, \Predis\Client::class, true)) {
106+
$params['scheme'] = $scheme;
107+
$params['database'] = $params['dbindex'] ?: null;
108+
$params['password'] = $auth;
109+
$redis = new $class((new Factory())->create($params));
110+
} elseif (class_exists($class, false)) {
111+
throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis" or "Predis\Client"', $class));
112+
} else {
113+
throw new InvalidArgumentException(sprintf('Class "%s" does not exist', $class));
114+
}
115+
116+
return $redis;
117+
}
118+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.