1<?php
2
3/*
4 * This file is part of the Predis package.
5 *
6 * (c) Daniele Alessandri <suppakilla@gmail.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
12namespace Predis\Connection;
13
14use Predis\Command\RawCommand;
15
16/**
17 * Standard connection factory for creating connections to Redis nodes.
18 *
19 * @author Daniele Alessandri <suppakilla@gmail.com>
20 */
21class Factory implements FactoryInterface
22{
23    private $defaults = array();
24
25    protected $schemes = array(
26        'tcp' => 'Predis\Connection\StreamConnection',
27        'unix' => 'Predis\Connection\StreamConnection',
28        'tls' => 'Predis\Connection\StreamConnection',
29        'redis' => 'Predis\Connection\StreamConnection',
30        'rediss' => 'Predis\Connection\StreamConnection',
31        'http' => 'Predis\Connection\WebdisConnection',
32    );
33
34    /**
35     * Checks if the provided argument represents a valid connection class
36     * implementing Predis\Connection\NodeConnectionInterface. Optionally,
37     * callable objects are used for lazy initialization of connection objects.
38     *
39     * @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
40     *
41     * @throws \InvalidArgumentException
42     *
43     * @return mixed
44     */
45    protected function checkInitializer($initializer)
46    {
47        if (is_callable($initializer)) {
48            return $initializer;
49        }
50
51        $class = new \ReflectionClass($initializer);
52
53        if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) {
54            throw new \InvalidArgumentException(
55                'A connection initializer must be a valid connection class or a callable object.'
56            );
57        }
58
59        return $initializer;
60    }
61
62    /**
63     * {@inheritdoc}
64     */
65    public function define($scheme, $initializer)
66    {
67        $this->schemes[$scheme] = $this->checkInitializer($initializer);
68    }
69
70    /**
71     * {@inheritdoc}
72     */
73    public function undefine($scheme)
74    {
75        unset($this->schemes[$scheme]);
76    }
77
78    /**
79     * {@inheritdoc}
80     */
81    public function create($parameters)
82    {
83        if (!$parameters instanceof ParametersInterface) {
84            $parameters = $this->createParameters($parameters);
85        }
86
87        $scheme = $parameters->scheme;
88
89        if (!isset($this->schemes[$scheme])) {
90            throw new \InvalidArgumentException("Unknown connection scheme: '$scheme'.");
91        }
92
93        $initializer = $this->schemes[$scheme];
94
95        if (is_callable($initializer)) {
96            $connection = call_user_func($initializer, $parameters, $this);
97        } else {
98            $connection = new $initializer($parameters);
99            $this->prepareConnection($connection);
100        }
101
102        if (!$connection instanceof NodeConnectionInterface) {
103            throw new \UnexpectedValueException(
104                'Objects returned by connection initializers must implement '.
105                "'Predis\Connection\NodeConnectionInterface'."
106            );
107        }
108
109        return $connection;
110    }
111
112    /**
113     * {@inheritdoc}
114     */
115    public function aggregate(AggregateConnectionInterface $connection, array $parameters)
116    {
117        foreach ($parameters as $node) {
118            $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node));
119        }
120    }
121
122    /**
123     * Assigns a default set of parameters applied to new connections.
124     *
125     * The set of parameters passed to create a new connection have precedence
126     * over the default values set for the connection factory.
127     *
128     * @param array $parameters Set of connection parameters.
129     */
130    public function setDefaultParameters(array $parameters)
131    {
132        $this->defaults = $parameters;
133    }
134
135    /**
136     * Returns the default set of parameters applied to new connections.
137     *
138     * @return array
139     */
140    public function getDefaultParameters()
141    {
142        return $this->defaults;
143    }
144
145    /**
146     * Creates a connection parameters instance from the supplied argument.
147     *
148     * @param mixed $parameters Original connection parameters.
149     *
150     * @return ParametersInterface
151     */
152    protected function createParameters($parameters)
153    {
154        if (is_string($parameters)) {
155            $parameters = Parameters::parse($parameters);
156        } else {
157            $parameters = $parameters ?: array();
158        }
159
160        if ($this->defaults) {
161            $parameters += $this->defaults;
162        }
163
164        return new Parameters($parameters);
165    }
166
167    /**
168     * Prepares a connection instance after its initialization.
169     *
170     * @param NodeConnectionInterface $connection Connection instance.
171     */
172    protected function prepareConnection(NodeConnectionInterface $connection)
173    {
174        $parameters = $connection->getParameters();
175
176        if (isset($parameters->password) && strlen($parameters->password)) {
177            $cmdAuthArgs = isset($parameters->username) && strlen($parameters->username)
178                ? array('AUTH', $parameters->username, $parameters->password)
179                : array('AUTH', $parameters->password);
180
181            $connection->addConnectCommand(
182                new RawCommand($cmdAuthArgs)
183            );
184        }
185
186        if (isset($parameters->database) && strlen($parameters->database)) {
187            $connection->addConnectCommand(
188                new RawCommand(array('SELECT', $parameters->database))
189            );
190        }
191    }
192}
193