Client.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <?php
  2. /*
  3. * This file is part of the Predis package.
  4. *
  5. * (c) Daniele Alessandri <suppakilla@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Predis;
  11. use Predis\Commands\ICommand;
  12. use Predis\Network\IConnection;
  13. use Predis\Network\IConnectionSingle;
  14. use Predis\Profiles\IServerProfile;
  15. use Predis\Profiles\ServerProfile;
  16. use Predis\Pipeline\PipelineContext;
  17. use Predis\Transaction\MultiExecContext;
  18. /**
  19. * Main class that exposes the most high-level interface to interact with Redis.
  20. *
  21. * @author Daniele Alessandri <suppakilla@gmail.com>
  22. */
  23. class Client
  24. {
  25. const VERSION = '0.7.0-dev';
  26. private $options;
  27. private $profile;
  28. private $connection;
  29. private $connectionFactory;
  30. /**
  31. * Initializes a new client with optional connection parameters and client options.
  32. *
  33. * @param mixed $parameters Connection parameters for one or multiple servers.
  34. * @param mixed $options Options that specify certain behaviours for the client.
  35. */
  36. public function __construct($parameters = null, $options = null)
  37. {
  38. $options = $this->filterOptions($options);
  39. $profile = $options->profile;
  40. if (isset($options->prefix)) {
  41. $profile->setProcessor($options->prefix);
  42. }
  43. $this->options = $options;
  44. $this->profile = $profile;
  45. $this->connectionFactory = $options->connections;
  46. $this->connection = $this->initializeConnection($parameters);
  47. }
  48. /**
  49. * Creates an instance of Predis\Options\ClientOptions from various types of
  50. * arguments (string, array, Predis\Profiles\ServerProfile) or returns the
  51. * passed object if it is an instance of Predis\Options\ClientOptions.
  52. *
  53. * @param mixed $options Client options.
  54. * @return ClientOptions
  55. */
  56. private function filterOptions($options)
  57. {
  58. if ($options === null) {
  59. return new ClientOptions();
  60. }
  61. if (is_array($options)) {
  62. return new ClientOptions($options);
  63. }
  64. if ($options instanceof ClientOptions) {
  65. return $options;
  66. }
  67. if ($options instanceof IServerProfile) {
  68. return new ClientOptions(array('profile' => $options));
  69. }
  70. if (is_string($options)) {
  71. return new ClientOptions(array('profile' => ServerProfile::get($options)));
  72. }
  73. throw new \InvalidArgumentException("Invalid type for client options");
  74. }
  75. /**
  76. * Initializes one or multiple connection (cluster) objects from various
  77. * types of arguments (string, array) or returns the passed object if it
  78. * implements the Predis\Network\IConnection interface.
  79. *
  80. * @param mixed $parameters Connection parameters or instance.
  81. * @return IConnection
  82. */
  83. private function initializeConnection($parameters)
  84. {
  85. if ($parameters === null) {
  86. return $this->createConnection(new ConnectionParameters());
  87. }
  88. if (is_array($parameters)) {
  89. if (isset($parameters[0])) {
  90. $cluster = $this->options->cluster;
  91. foreach ($parameters as $node) {
  92. $connection = $node instanceof IConnectionSingle ? $node : $this->createConnection($node);
  93. $cluster->add($connection);
  94. }
  95. return $cluster;
  96. }
  97. return $this->createConnection($parameters);
  98. }
  99. if ($parameters instanceof IConnection) {
  100. return $parameters;
  101. }
  102. return $this->createConnection($parameters);
  103. }
  104. /**
  105. * Creates a new connection to a single server with the provided parameters.
  106. *
  107. * @param mixed $parameters Connection parameters.
  108. * @return IConnectionSingle
  109. */
  110. protected function createConnection($parameters)
  111. {
  112. $connection = $this->connectionFactory->create($parameters);
  113. $parameters = $connection->getParameters();
  114. if (isset($parameters->password)) {
  115. $command = $this->createCommand('auth', array($parameters->password));
  116. $connection->pushInitCommand($command);
  117. }
  118. if (isset($parameters->database)) {
  119. $command = $this->createCommand('select', array($parameters->database));
  120. $connection->pushInitCommand($command);
  121. }
  122. return $connection;
  123. }
  124. /**
  125. * Returns the server profile used by the client.
  126. *
  127. * @return IServerProfile
  128. */
  129. public function getProfile()
  130. {
  131. return $this->profile;
  132. }
  133. /**
  134. * Returns the client options specified upon initialization.
  135. *
  136. * @return ClientOptions
  137. */
  138. public function getOptions()
  139. {
  140. return $this->options;
  141. }
  142. /**
  143. * Returns the connection factory object used by the client.
  144. *
  145. * @return IConnectionFactory
  146. */
  147. public function getConnectionFactory()
  148. {
  149. return $this->connectionFactory;
  150. }
  151. /**
  152. * Returns a new instance of a client for the specified connection when the
  153. * client is connected to a cluster. The new instance will use the same
  154. * options of the original client.
  155. *
  156. * @return Client
  157. */
  158. public function getClientFor($connectionAlias)
  159. {
  160. if (($connection = $this->getConnection($connectionAlias)) === null) {
  161. throw new \InvalidArgumentException("Invalid connection alias: '$connectionAlias'");
  162. }
  163. return new Client($connection, $this->options);
  164. }
  165. /**
  166. * Opens the connection to the server.
  167. */
  168. public function connect()
  169. {
  170. $this->connection->connect();
  171. }
  172. /**
  173. * Disconnects from the server.
  174. */
  175. public function disconnect()
  176. {
  177. $this->connection->disconnect();
  178. }
  179. /**
  180. * Disconnects from the server.
  181. *
  182. * This method is an alias of disconnect().
  183. */
  184. public function quit()
  185. {
  186. $this->disconnect();
  187. }
  188. /**
  189. * Checks if the underlying connection is connected to Redis.
  190. *
  191. * @return Boolean True means that the connection is open.
  192. * False means that the connection is closed.
  193. */
  194. public function isConnected()
  195. {
  196. return $this->connection->isConnected();
  197. }
  198. /**
  199. * Returns the underlying connection instance or, when connected to a cluster,
  200. * one of the connection instances identified by its alias.
  201. *
  202. * @param string $id The alias of a connection when connected to a cluster.
  203. * @return IConnection
  204. */
  205. public function getConnection($id = null)
  206. {
  207. if (isset($id)) {
  208. if (!Helpers::isCluster($this->connection)) {
  209. throw new ClientException(
  210. 'Retrieving connections by alias is supported only with clustered connections'
  211. );
  212. }
  213. return $this->connection->getConnectionById($id);
  214. }
  215. return $this->connection;
  216. }
  217. /**
  218. * Dinamically invokes a Redis command with the specified arguments.
  219. *
  220. * @param string $method The name of a Redis command.
  221. * @param array $arguments The arguments for the command.
  222. * @return mixed
  223. */
  224. public function __call($method, $arguments)
  225. {
  226. $command = $this->profile->createCommand($method, $arguments);
  227. return $this->connection->executeCommand($command);
  228. }
  229. /**
  230. * Creates a new instance of the specified Redis command.
  231. *
  232. * @param string $method The name of a Redis command.
  233. * @param array $arguments The arguments for the command.
  234. * @return ICommand
  235. */
  236. public function createCommand($method, $arguments = array())
  237. {
  238. return $this->profile->createCommand($method, $arguments);
  239. }
  240. /**
  241. * Executes the specified Redis command.
  242. *
  243. * @param ICommand $command A Redis command.
  244. * @return mixed
  245. */
  246. public function executeCommand(ICommand $command)
  247. {
  248. return $this->connection->executeCommand($command);
  249. }
  250. /**
  251. * Executes the specified Redis command on all the nodes of a cluster.
  252. *
  253. * @param ICommand $command A Redis command.
  254. * @return array
  255. */
  256. public function executeCommandOnShards(ICommand $command)
  257. {
  258. if (Helpers::isCluster($this->connection)) {
  259. $replies = array();
  260. foreach ($this->connection as $connection) {
  261. $replies[] = $connection->executeCommand($command);
  262. }
  263. return $replies;
  264. }
  265. return array($this->connection->executeCommand($command));
  266. }
  267. /**
  268. * Calls the specified initializer method on $this with 0, 1 or 2 arguments.
  269. *
  270. * TODO: Invert $argv and $initializer.
  271. *
  272. * @param array $argv Arguments for the initializer.
  273. * @param string $initializer The initializer method.
  274. * @return mixed
  275. */
  276. private function sharedInitializer($argv, $initializer)
  277. {
  278. switch (count($argv)) {
  279. case 0:
  280. return $this->$initializer();
  281. case 1:
  282. list($arg0) = $argv;
  283. return is_array($arg0) ? $this->$initializer($arg0) : $this->$initializer(null, $arg0);
  284. case 2:
  285. list($arg0, $arg1) = $argv;
  286. return $this->$initializer($arg0, $arg1);
  287. default:
  288. return $this->$initializer($this, $argv);
  289. }
  290. }
  291. /**
  292. * Creates a new pipeline context and returns it, or returns the results of
  293. * a pipeline executed inside the optionally provided callable object.
  294. *
  295. * @param mixed $arg,... Options for the context, a callable object, or both.
  296. * @return PipelineContext|array
  297. */
  298. public function pipeline(/* arguments */)
  299. {
  300. return $this->sharedInitializer(func_get_args(), 'initPipeline');
  301. }
  302. /**
  303. * Pipeline context initializer.
  304. *
  305. * @param array $options Options for the context.
  306. * @param mixed $callable Optional callable object used to execute the context.
  307. * @return PipelineContext|array
  308. */
  309. protected function initPipeline(Array $options = null, $callable = null)
  310. {
  311. $pipeline = new PipelineContext($this, $options);
  312. return $this->pipelineExecute($pipeline, $callable);
  313. }
  314. /**
  315. * Executes a pipeline context when a callable object is passed.
  316. *
  317. * @param array $options Options of the context initialization.
  318. * @param mixed $callable Optional callable object used to execute the context.
  319. * @return PipelineContext|array
  320. */
  321. private function pipelineExecute(PipelineContext $pipeline, $callable)
  322. {
  323. return isset($callable) ? $pipeline->execute($callable) : $pipeline;
  324. }
  325. /**
  326. * Creates a new transaction context and returns it, or returns the results of
  327. * a transaction executed inside the optionally provided callable object.
  328. *
  329. * @param mixed $arg,... Options for the context, a callable object, or both.
  330. * @return MultiExecContext|array
  331. */
  332. public function multiExec(/* arguments */)
  333. {
  334. return $this->sharedInitializer(func_get_args(), 'initMultiExec');
  335. }
  336. /**
  337. * Transaction context initializer.
  338. *
  339. * @param array $options Options for the context.
  340. * @param mixed $callable Optional callable object used to execute the context.
  341. * @return MultiExecContext|array
  342. */
  343. protected function initMultiExec(Array $options = null, $callable = null)
  344. {
  345. $transaction = new MultiExecContext($this, $options ?: array());
  346. return isset($callable) ? $transaction->execute($callable) : $transaction;
  347. }
  348. /**
  349. * Creates a new Publish / Subscribe context and returns it, or executes it
  350. * inside the optionally provided callable object.
  351. *
  352. * @param mixed $arg,... Options for the context, a callable object, or both.
  353. * @return MultiExecContext|array
  354. */
  355. public function pubSub(/* arguments */)
  356. {
  357. return $this->sharedInitializer(func_get_args(), 'initPubSub');
  358. }
  359. /**
  360. * Publish / Subscribe context initializer.
  361. *
  362. * @param array $options Options for the context.
  363. * @param mixed $callable Optional callable object used to execute the context.
  364. * @return PubSubContext
  365. */
  366. protected function initPubSub(Array $options = null, $callable = null)
  367. {
  368. $pubsub = new PubSubContext($this, $options);
  369. if (!isset($callable)) {
  370. return $pubsub;
  371. }
  372. foreach ($pubsub as $message) {
  373. if ($callable($pubsub, $message) === false) {
  374. $pubsub->closeContext();
  375. }
  376. }
  377. }
  378. /**
  379. * Returns a new monitor context.
  380. *
  381. * @return MonitorContext
  382. */
  383. public function monitor()
  384. {
  385. return new MonitorContext($this);
  386. }
  387. }