Redis.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Redis;
  12. use Hyperf\Context\Context;
  13. use Hyperf\Redis\Exception\InvalidRedisConnectionException;
  14. use Hyperf\Redis\Pool\PoolFactory;
  15. use function Hyperf\Coroutine\defer;
  16. /**
  17. * @mixin \Redis
  18. */
  19. class Redis
  20. {
  21. use Traits\ScanCaller;
  22. use Traits\MultiExec;
  23. protected string $poolName = 'default';
  24. public function __construct(protected PoolFactory $factory)
  25. {
  26. }
  27. public function __call($name, $arguments)
  28. {
  29. // Get a connection from coroutine context or connection pool.
  30. $hasContextConnection = Context::has($this->getContextKey());
  31. $connection = $this->getConnection($hasContextConnection);
  32. try {
  33. $connection = $connection->getConnection();
  34. // Execute the command with the arguments.
  35. $result = $connection->{$name}(...$arguments);
  36. } finally {
  37. // Release connection.
  38. if (! $hasContextConnection) {
  39. if ($this->shouldUseSameConnection($name)) {
  40. if ($name === 'select' && $db = $arguments[0]) {
  41. $connection->setDatabase((int) $db);
  42. }
  43. // Should storage the connection to coroutine context, then use defer() to release the connection.
  44. Context::set($this->getContextKey(), $connection);
  45. defer(function () use ($connection) {
  46. Context::set($this->getContextKey(), null);
  47. $connection->release();
  48. });
  49. } else {
  50. // Release the connection after command executed.
  51. $connection->release();
  52. }
  53. }
  54. }
  55. return $result;
  56. }
  57. /**
  58. * Define the commands that need same connection to execute.
  59. * When these commands executed, the connection will storage to coroutine context.
  60. */
  61. private function shouldUseSameConnection(string $methodName): bool
  62. {
  63. return in_array($methodName, [
  64. 'multi',
  65. 'pipeline',
  66. 'select',
  67. ]);
  68. }
  69. /**
  70. * Get a connection from coroutine context, or from redis connection pool.
  71. * @param mixed $hasContextConnection
  72. */
  73. private function getConnection($hasContextConnection): RedisConnection
  74. {
  75. $connection = null;
  76. if ($hasContextConnection) {
  77. $connection = Context::get($this->getContextKey());
  78. }
  79. if (! $connection instanceof RedisConnection) {
  80. $pool = $this->factory->getPool($this->poolName);
  81. $connection = $pool->get();
  82. }
  83. if (! $connection instanceof RedisConnection) {
  84. throw new InvalidRedisConnectionException('The connection is not a valid RedisConnection.');
  85. }
  86. return $connection;
  87. }
  88. /**
  89. * The key to identify the connection object in coroutine context.
  90. */
  91. private function getContextKey(): string
  92. {
  93. return sprintf('redis.connection.%s', $this->poolName);
  94. }
  95. }