diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md index 41901572f5..1e65630c01 100644 --- a/CHANGELOG-3.0.md +++ b/CHANGELOG-3.0.md @@ -51,6 +51,7 @@ composer analyse - [#4700](https://github.com/hyperf/hyperf/pull/4700) Support coroutine style server for `socketio-server`. - [#4852](https://github.com/hyperf/hyperf/pull/4852) Added `NullDisableEventDispatcher` to disable event dispatcher by default. - [#4866](https://github.com/hyperf/hyperf/pull/4866) [#4869](https://github.com/hyperf/hyperf/pull/4869) Added Annotation `Scene` which use scene in FormRequest easily. +- [#4908](https://github.com/hyperf/hyperf/pull/4908) Added `Db::beforeExecuting()` to register a hook which to be run just before a database query is executed. ## Optimized diff --git a/src/database/src/Connection.php b/src/database/src/Connection.php index 5d7e717e29..e041dbf22a 100755 --- a/src/database/src/Connection.php +++ b/src/database/src/Connection.php @@ -38,14 +38,14 @@ class Connection implements ConnectionInterface /** * The active PDO connection. * - * @var \Closure|\PDO + * @var Closure|PDO */ protected $pdo; /** * The active PDO connection used for reads. * - * @var \Closure|\PDO + * @var Closure|PDO */ protected $readPdo; @@ -149,10 +149,17 @@ class Connection implements ConnectionInterface */ protected static array $resolvers = []; + /** + * All the callbacks that should be invoked before a query is executed. + * + * @var Closure[] + */ + protected static array $beforeExecutingCallbacks = []; + /** * Create a new database connection instance. * - * @param \Closure|\PDO $pdo + * @param Closure|PDO $pdo * @param string $database * @param string $tablePrefix */ @@ -489,6 +496,22 @@ public function disconnect() $this->setPdo(null)->setReadPdo(null); } + /** + * Register a hook to be run just before a database query is executed. + */ + public static function beforeExecuting(Closure $callback): void + { + static::$beforeExecutingCallbacks[] = $callback; + } + + /** + * Clear all hooks which will be run before a database query. + */ + public static function clearBeforeExecutingCallbacks(): void + { + static::$beforeExecutingCallbacks = []; + } + /** * Register a database query listener with the connection. */ @@ -623,7 +646,7 @@ public function getReadPdo() /** * Set the PDO connection. * - * @param null|\Closure|\PDO $pdo + * @param null|Closure|PDO $pdo * @return $this */ public function setPdo($pdo) @@ -638,7 +661,7 @@ public function setPdo($pdo) /** * Set the PDO connection used for reading. * - * @param null|\Closure|\PDO $pdo + * @param null|Closure|PDO $pdo * @return $this */ public function setReadPdo($pdo) @@ -983,7 +1006,7 @@ protected function getPdoForSelect($useReadPdo = true) /** * Execute the given callback in "dry run" mode. * - * @param \Closure $callback + * @param Closure $callback * @return array */ protected function withFreshQueryLog($callback) @@ -1014,6 +1037,10 @@ protected function withFreshQueryLog($callback) */ protected function run(string $query, array $bindings, Closure $callback) { + foreach (static::$beforeExecutingCallbacks as $beforeExecutingCallback) { + $beforeExecutingCallback($query, $bindings, $this); + } + $this->reconnectIfMissingConnection(); $start = microtime(true); diff --git a/src/database/tests/ModelRealBuilderTest.php b/src/database/tests/ModelRealBuilderTest.php index f8b18ace9b..2daa0d60c8 100644 --- a/src/database/tests/ModelRealBuilderTest.php +++ b/src/database/tests/ModelRealBuilderTest.php @@ -452,6 +452,35 @@ public function testSelectForBindingIntegerWhenUsingVarcharIndex() $this->assertSame('ref', $res[0]->type); } + public function testBeforeExecuting() + { + $container = $this->getContainer(); + $container->shouldReceive('get')->with(Db::class)->andReturn(new Db($container)); + + $res = Db::selectOne('SELECT * FROM `user` WHERE id = ?;', [1]); + $this->assertSame('Hyperf', $res->name); + + try { + $chan = new Channel(2); + Db::beforeExecuting(function (string $sql, array $bindings, Connection $connection) use ($chan) { + $this->assertSame(null, $connection->getConfig('name')); + $chan->push(1); + }); + Db::beforeExecuting(function (string $sql, array $bindings, Connection $connection) use ($chan) { + $this->assertSame('SELECT * FROM `user` WHERE id = ?;', $sql); + $this->assertSame([1], $bindings); + $chan->push(2); + }); + + $res = Db::selectOne('SELECT * FROM `user` WHERE id = ?;', [1]); + $this->assertSame('Hyperf', $res->name); + $this->assertSame(1, $chan->pop(1)); + $this->assertSame(2, $chan->pop(1)); + } finally { + Connection::clearBeforeExecutingCallbacks(); + } + } + protected function getContainer() { $dispatcher = Mockery::mock(EventDispatcherInterface::class); diff --git a/src/db-connection/src/Db.php b/src/db-connection/src/Db.php index f7b2839657..94eb3c3224 100644 --- a/src/db-connection/src/Db.php +++ b/src/db-connection/src/Db.php @@ -11,7 +11,9 @@ */ namespace Hyperf\DbConnection; +use Closure; use Generator; +use Hyperf\Database\Connection as Conn; use Hyperf\Database\ConnectionInterface; use Hyperf\Database\ConnectionResolverInterface; use Hyperf\Database\Query\Builder; @@ -33,12 +35,12 @@ * @method static int affectingStatement(string $query, array $bindings = []) * @method static bool unprepared(string $query) * @method static array prepareBindings(array $bindings) - * @method static mixed transaction(\Closure $callback, int $attempts = 1) + * @method static mixed transaction(Closure $callback, int $attempts = 1) * @method static void beginTransaction() * @method static void rollBack() * @method static void commit() * @method static int transactionLevel() - * @method static array pretend(\Closure $callback) + * @method static array pretend(Closure $callback) * @method static ConnectionInterface connection(string $pool) */ class Db @@ -69,4 +71,9 @@ private function __connection(?string $name = null): ConnectionInterface $resolver = $this->container->get(ConnectionResolverInterface::class); return $resolver->connection($name); } + + public static function beforeExecuting(Closure $closure): void + { + Conn::beforeExecuting($closure); + } }