mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-12-05 13:18:22 +08:00
Merge pull request #827 from PandaLIU-1111/master
添加一个db的组件,databases配置文件添加一个port配置
This commit is contained in:
commit
87c3ecc986
@ -38,6 +38,6 @@ before_script:
|
||||
- composer config -g process-timeout 900 && composer update
|
||||
|
||||
script:
|
||||
- composer analyse src/di src/json-rpc src/tracer src/metric src/redis src/nats
|
||||
- composer analyse src/di src/json-rpc src/tracer src/metric src/redis src/nats src/db
|
||||
- composer test -- --exclude-group NonCoroutine
|
||||
- vendor/bin/phpunit --group NonCoroutine
|
||||
|
@ -1,5 +1,9 @@
|
||||
# v1.1.6 - TBD
|
||||
|
||||
## Added
|
||||
|
||||
- [#827](https://github.com/hyperf/hyperf/pull/827) Added a simple db component.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#897](https://github.com/hyperf/hyperf/pull/897) Fixed `pool` for `Hyperf\Nats\Annotation\Consumer` does not works.
|
||||
|
@ -133,6 +133,7 @@
|
||||
"Hyperf\\Consul\\": "src/consul/src/",
|
||||
"Hyperf\\Contract\\": "src/contract/src/",
|
||||
"Hyperf\\Crontab\\": "src/crontab/src/",
|
||||
"Hyperf\\DB\\": "src/db/src/",
|
||||
"Hyperf\\Database\\": "src/database/src/",
|
||||
"Hyperf\\DbConnection\\": "src/db-connection/src/",
|
||||
"Hyperf\\Devtool\\": "src/devtool/src/",
|
||||
@ -200,6 +201,7 @@
|
||||
"HyperfTest\\Constants\\": "src/constants/tests/",
|
||||
"HyperfTest\\Consul\\": "src/consul/tests/",
|
||||
"HyperfTest\\Crontab\\": "src/crontab/tests/",
|
||||
"HyperfTest\\DB\\": "src/db/tests/",
|
||||
"HyperfTest\\Database\\": "src/database/tests/",
|
||||
"HyperfTest\\DbConnection\\": "src/db-connection/tests/",
|
||||
"HyperfTest\\Di\\": "src/di/tests/",
|
||||
@ -256,6 +258,7 @@
|
||||
"Hyperf\\Constants\\ConfigProvider",
|
||||
"Hyperf\\Consul\\ConfigProvider",
|
||||
"Hyperf\\Crontab\\ConfigProvider",
|
||||
"Hyperf\\DB\\ConfigProvider",
|
||||
"Hyperf\\DbConnection\\ConfigProvider",
|
||||
"Hyperf\\Devtool\\ConfigProvider",
|
||||
"Hyperf\\Di\\ConfigProvider",
|
||||
|
69
doc/zh/db/db.md
Normal file
69
doc/zh/db/db.md
Normal file
@ -0,0 +1,69 @@
|
||||
# 极简的DB组件
|
||||
|
||||
[hyperf/database](https://github.com/hyperf/database) 功能十分强大,但也不可否认效率上确实些许不足。这里提供一个极简的 `DB` 组件,支持 `PDO` 和 `SwooleMysql`。
|
||||
|
||||
> 压测对比 database 1800qps,db 6800qps。
|
||||
|
||||
## 组件配置
|
||||
|
||||
默认配置 `autoload/db.php` 如下,数据库支持多库配置,默认为 `default`。
|
||||
|
||||
| 配置项 | 类型 | 默认值 | 备注 |
|
||||
|:--------------------:|:------:|:------------------:|:--------------------------------:|
|
||||
| driver | string | 无 | 数据库引擎 支持 `pdo` 和 `mysql` |
|
||||
| host | string | `localhost` | 数据库地址 |
|
||||
| port | int | 3306 | 数据库地址 |
|
||||
| database | string | 无 | 数据库默认DB |
|
||||
| username | string | 无 | 数据库用户名 |
|
||||
| password | string | null | 数据库密码 |
|
||||
| charset | string | utf8 | 数据库编码 |
|
||||
| collation | string | utf8_unicode_ci | 数据库编码 |
|
||||
| fetch_mode | int | `PDO::FETCH_ASSOC` | PDO查询结果集类型 |
|
||||
| pool.min_connections | int | 1 | 连接池内最少连接数 |
|
||||
| pool.max_connections | int | 10 | 连接池内最大连接数 |
|
||||
| pool.connect_timeout | float | 10.0 | 连接等待超时时间 |
|
||||
| pool.wait_timeout | float | 3.0 | 超时时间 |
|
||||
| pool.heartbeat | int | -1 | 心跳 |
|
||||
| pool.max_idle_time | float | 60.0 | 最大闲置时间 |
|
||||
| options | array | | PDO 配置 |
|
||||
|
||||
## 组件支持的方法
|
||||
|
||||
具体接口可以查看 `Hyperf\DB\ConnectionInterface`。
|
||||
|
||||
| 方法名 | 返回值类型 | 备注 |
|
||||
|:----------------:|:----------:|:--------------------------------------:|
|
||||
| beginTransaction | void | 开启事务 支持事务嵌套 |
|
||||
| commit | void | 提交事务 支持事务嵌套 |
|
||||
| rollBack | void | 回滚事务 支持事务嵌套 |
|
||||
| insert | int | 插入数据,返回主键ID,非自称主键返回 0 |
|
||||
| execute | int | 执行SQL,返回受影响的行数 |
|
||||
| query | array | 查询SQL |
|
||||
| fetch | array | object|查询SQL,返回首行数据 |
|
||||
|
||||
## 使用
|
||||
|
||||
### 使用DB实例
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\DB\DB;
|
||||
|
||||
$db = ApplicationContext::getContainer()->get(DB::class);
|
||||
|
||||
$res = $db->query('SELECT * FROM `user` WHERE gender = ?;',[1]);
|
||||
|
||||
```
|
||||
|
||||
### 使用静态方法
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Hyperf\DB\DB;
|
||||
|
||||
$res = DB::query('SELECT * FROM `user` WHERE gender = ?;',[1]);
|
||||
|
||||
```
|
@ -49,6 +49,7 @@
|
||||
* [模型事件](zh/db/event.md)
|
||||
* [模型缓存](zh/db/model-cache.md)
|
||||
* [数据库迁移](zh/db/migration.md)
|
||||
* [极简的DB组件](zh/db/db.md)
|
||||
|
||||
* 微服务
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
<directory suffix="Test.php">./src/constants/tests</directory>
|
||||
<directory suffix="Test.php">./src/consul/tests</directory>
|
||||
<directory suffix="Test.php">./src/database/tests</directory>
|
||||
<directory suffix="Test.php">./src/db/tests</directory>
|
||||
<directory suffix="Test.php">./src/db-connection/tests</directory>
|
||||
<directory suffix="Test.php">./src/di/tests</directory>
|
||||
<directory suffix="Test.php">./src/dispatcher/tests</directory>
|
||||
|
@ -14,6 +14,7 @@ return [
|
||||
'default' => [
|
||||
'driver' => env('DB_DRIVER', 'mysql'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'hyperf'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
|
1
src/db/.gitattributes
vendored
Normal file
1
src/db/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
/tests export-ignore
|
21
src/db/LICENSE
Normal file
21
src/db/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Hyperf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
49
src/db/composer.json
Normal file
49
src/db/composer.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "hyperf/db",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"php",
|
||||
"hyperf"
|
||||
],
|
||||
"description": "",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Hyperf\\DB\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"HyperfTest\\DB\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"ext-swoole": ">=4.4",
|
||||
"hyperf/config": "~1.1.0",
|
||||
"hyperf/contract": "~1.1.0",
|
||||
"hyperf/pool": "~1.1.0",
|
||||
"hyperf/utils": "~1.1.0",
|
||||
"psr/container": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.14",
|
||||
"phpstan/phpstan": "^0.10.5",
|
||||
"hyperf/testing": "1.1.*",
|
||||
"mockery/mockery": "^1.0",
|
||||
"swoft/swoole-ide-helper": "dev-master"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"scripts": {
|
||||
"test": "co-phpunit -c phpunit.xml --colors=always",
|
||||
"analyze": "phpstan analyse --memory-limit 300M -l 0 ./src",
|
||||
"cs-fix": "php-cs-fixer fix $1"
|
||||
},
|
||||
"extra": {
|
||||
"hyperf": {
|
||||
"config": "Hyperf\\DB\\ConfigProvider"
|
||||
}
|
||||
}
|
||||
}
|
40
src/db/publish/db.php
Normal file
40
src/db/publish/db.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'driver' => 'pdo',
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', 3306),
|
||||
'database' => env('DB_DATABASE', 'hyperf'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'fetch_mode' => PDO::FETCH_ASSOC,
|
||||
'pool' => [
|
||||
'min_connections' => 1,
|
||||
'max_connections' => 10,
|
||||
'connect_timeout' => 10.0,
|
||||
'wait_timeout' => 3.0,
|
||||
'heartbeat' => -1,
|
||||
'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60),
|
||||
],
|
||||
'options' => [
|
||||
PDO::ATTR_CASE => PDO::CASE_NATURAL,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
],
|
||||
],
|
||||
];
|
75
src/db/src/AbstractConnection.php
Normal file
75
src/db/src/AbstractConnection.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Pool\Connection;
|
||||
use Hyperf\Pool\Exception\ConnectionException;
|
||||
|
||||
abstract class AbstractConnection extends Connection implements ConnectionInterface
|
||||
{
|
||||
use DetectsLostConnections;
|
||||
use ManagesTransactions;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function release(): void
|
||||
{
|
||||
if ($this->transactionLevel() > 0) {
|
||||
$this->rollBack(0);
|
||||
if ($this->container->has(StdoutLoggerInterface::class)) {
|
||||
$logger = $this->container->get(StdoutLoggerInterface::class);
|
||||
$logger->error('Maybe you\'ve forgotten to commit or rollback the MySQL transaction.');
|
||||
}
|
||||
}
|
||||
$this->pool->release($this);
|
||||
}
|
||||
|
||||
public function getActiveConnection()
|
||||
{
|
||||
if ($this->check()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (! $this->reconnect()) {
|
||||
throw new ConnectionException('Connection reconnect failed.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function retry(\Throwable $throwable, $name, $arguments)
|
||||
{
|
||||
if ($this->causedByLostConnection($throwable)) {
|
||||
try {
|
||||
$this->reconnect();
|
||||
return $this->{$name}(...$arguments);
|
||||
} catch (\Throwable $throwable) {
|
||||
if ($this->container->has(StdoutLoggerInterface::class)) {
|
||||
$logger = $this->container->get(StdoutLoggerInterface::class);
|
||||
$logger->error('Connection execute retry failed. message = ' . $throwable->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
41
src/db/src/ConfigProvider.php
Normal file
41
src/db/src/ConfigProvider.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
public function __invoke(): array
|
||||
{
|
||||
return [
|
||||
'dependencies' => [
|
||||
],
|
||||
'commands' => [
|
||||
],
|
||||
'annotations' => [
|
||||
'scan' => [
|
||||
'paths' => [
|
||||
__DIR__,
|
||||
],
|
||||
],
|
||||
],
|
||||
'publish' => [
|
||||
[
|
||||
'id' => 'db',
|
||||
'description' => 'The config for db.',
|
||||
'source' => __DIR__ . '/../publish/db.php',
|
||||
'destination' => BASE_PATH . '/config/autoload/db.php',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
64
src/db/src/ConnectionInterface.php
Normal file
64
src/db/src/ConnectionInterface.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
interface ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Start a new database transaction.
|
||||
*/
|
||||
public function beginTransaction(): void;
|
||||
|
||||
/**
|
||||
* Commit the active database transaction.
|
||||
*/
|
||||
public function commit(): void;
|
||||
|
||||
/**
|
||||
* Rollback the active database transaction.
|
||||
*/
|
||||
public function rollBack(?int $toLevel = null): void;
|
||||
|
||||
/**
|
||||
* Run an insert statement against the database.
|
||||
*
|
||||
* @return int last insert id
|
||||
*/
|
||||
public function insert(string $query, array $bindings = []): int;
|
||||
|
||||
/**
|
||||
* Run an execute statement against the database.
|
||||
*
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function execute(string $query, array $bindings = []): int;
|
||||
|
||||
/**
|
||||
* Execute an SQL statement and return the number of affected rows.
|
||||
*
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function exec(string $sql): int;
|
||||
|
||||
/**
|
||||
* Run a select statement against the database.
|
||||
*/
|
||||
public function query(string $query, array $bindings = []): array;
|
||||
|
||||
/**
|
||||
* Run a select statement and return a single result.
|
||||
*/
|
||||
public function fetch(string $query, array $bindings = []);
|
||||
|
||||
public function call(string $method, array $argument = []);
|
||||
}
|
118
src/db/src/DB.php
Normal file
118
src/db/src/DB.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
use Hyperf\DB\Pool\PoolFactory;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\Utils\Context;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @method beginTransaction()
|
||||
* @method commit()
|
||||
* @method rollback()
|
||||
* @method insert(string $query, array $bindings = [])
|
||||
* @method execute(string $query, array $bindings = [])
|
||||
* @method query(string $query, array $bindings = [])
|
||||
* @method fetch(string $query, array $bindings = [])
|
||||
*/
|
||||
class DB
|
||||
{
|
||||
/**
|
||||
* @var PoolFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $poolName;
|
||||
|
||||
public function __construct(PoolFactory $factory, string $poolName = 'default')
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->poolName = $poolName;
|
||||
}
|
||||
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
$hasContextConnection = Context::has($this->getContextKey());
|
||||
$connection = $this->getConnection($hasContextConnection);
|
||||
|
||||
try {
|
||||
$connection = $connection->getConnection();
|
||||
$result = $connection->{$name}(...$arguments);
|
||||
} catch (Throwable $exception) {
|
||||
$result = $connection->retry($exception, $name, $arguments);
|
||||
} finally {
|
||||
if (! $hasContextConnection) {
|
||||
if ($this->shouldUseSameConnection($name)) {
|
||||
// Should storage the connection to coroutine context, then use defer() to release the connection.
|
||||
Context::set($this->getContextKey(), $connection);
|
||||
defer(function () use ($connection) {
|
||||
$connection->release();
|
||||
});
|
||||
} else {
|
||||
// Release the connection after command executed.
|
||||
$connection->release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
$container = ApplicationContext::getContainer();
|
||||
$db = $container->get(static::class);
|
||||
return $db->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the commands that needs same connection to execute.
|
||||
* When these commands executed, the connection will storage to coroutine context.
|
||||
*/
|
||||
protected function shouldUseSameConnection(string $methodName): bool
|
||||
{
|
||||
return in_array($methodName, [
|
||||
'beginTransaction',
|
||||
'commit',
|
||||
'rollBack',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection from coroutine context, or from mysql connectio pool.
|
||||
*/
|
||||
protected function getConnection(bool $hasContextConnection): AbstractConnection
|
||||
{
|
||||
$connection = null;
|
||||
if ($hasContextConnection) {
|
||||
$connection = Context::get($this->getContextKey());
|
||||
}
|
||||
if (! $connection instanceof AbstractConnection) {
|
||||
$pool = $this->factory->getPool($this->poolName);
|
||||
$connection = $pool->get();
|
||||
}
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key to identify the connection object in coroutine context.
|
||||
*/
|
||||
private function getContextKey(): string
|
||||
{
|
||||
return sprintf('db.connection.%s', $this->poolName);
|
||||
}
|
||||
}
|
49
src/db/src/DetectsLostConnections.php
Normal file
49
src/db/src/DetectsLostConnections.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
use Hyperf\Utils\Str;
|
||||
use Throwable;
|
||||
|
||||
trait DetectsLostConnections
|
||||
{
|
||||
/**
|
||||
* Determine if the given exception was caused by a lost connection.
|
||||
*/
|
||||
protected function causedByLostConnection(Throwable $e): bool
|
||||
{
|
||||
$message = $e->getMessage();
|
||||
|
||||
return Str::contains($message, [
|
||||
'server has gone away',
|
||||
'no connection to the server',
|
||||
'Lost connection',
|
||||
'is dead or not enabled',
|
||||
'Error while sending',
|
||||
'decryption failed or bad record mac',
|
||||
'server closed the connection unexpectedly',
|
||||
'SSL connection has been closed unexpectedly',
|
||||
'Error writing data to the connection',
|
||||
'Resource deadlock avoided',
|
||||
'Transaction() on null',
|
||||
'child connection forced to terminate due to client_idle_limit',
|
||||
'query_wait_timeout',
|
||||
'reset by peer',
|
||||
'Physical connection is not usable',
|
||||
'TCP Provider: Error code 0x68',
|
||||
'Name or service not known',
|
||||
'ORA-03114',
|
||||
'Packets out of order. Expected',
|
||||
]);
|
||||
}
|
||||
}
|
17
src/db/src/Exception/DriverNotFoundException.php
Normal file
17
src/db/src/Exception/DriverNotFoundException.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB\Exception;
|
||||
|
||||
class DriverNotFoundException extends RuntimeException
|
||||
{
|
||||
}
|
17
src/db/src/Exception/QueryException.php
Normal file
17
src/db/src/Exception/QueryException.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB\Exception;
|
||||
|
||||
class QueryException extends \PDOException
|
||||
{
|
||||
}
|
17
src/db/src/Exception/RuntimeException.php
Normal file
17
src/db/src/Exception/RuntimeException.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB\Exception;
|
||||
|
||||
class RuntimeException extends \RuntimeException
|
||||
{
|
||||
}
|
19
src/db/src/Frequency.php
Normal file
19
src/db/src/Frequency.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
use Hyperf\Pool\Frequency as DefaultFrequency;
|
||||
|
||||
class Frequency extends DefaultFrequency
|
||||
{
|
||||
}
|
172
src/db/src/ManagesTransactions.php
Normal file
172
src/db/src/ManagesTransactions.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
use Throwable;
|
||||
|
||||
trait ManagesTransactions
|
||||
{
|
||||
/**
|
||||
* The number of active transactions.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $transactions = 0;
|
||||
|
||||
/**
|
||||
* Start a new database transaction.
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function beginTransaction(): void
|
||||
{
|
||||
$this->createTransaction();
|
||||
|
||||
++$this->transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the active database transaction.
|
||||
*/
|
||||
public function commit(): void
|
||||
{
|
||||
if ($this->transactions == 1) {
|
||||
$this->call('commit');
|
||||
}
|
||||
|
||||
$this->transactions = max(0, $this->transactions - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the active database transaction.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function rollBack(?int $toLevel = null): void
|
||||
{
|
||||
// We allow developers to rollback to a certain transaction level. We will verify
|
||||
// that this given transaction level is valid before attempting to rollback to
|
||||
// that level. If it's not we will just return out and not attempt anything.
|
||||
$toLevel = is_null($toLevel)
|
||||
? $this->transactions - 1
|
||||
: $toLevel;
|
||||
|
||||
if ($toLevel < 0 || $toLevel >= $this->transactions) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Next, we will actually perform this rollback within this database and fire the
|
||||
// rollback event. We will also set the current transaction level to the given
|
||||
// level that was passed into this method so it will be right from here out.
|
||||
try {
|
||||
$this->performRollBack($toLevel);
|
||||
} catch (Throwable $e) {
|
||||
$this->handleRollBackException($e);
|
||||
}
|
||||
|
||||
$this->transactions = $toLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of active transactions.
|
||||
*/
|
||||
public function transactionLevel(): int
|
||||
{
|
||||
return $this->transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction within the database.
|
||||
*/
|
||||
protected function createTransaction(): void
|
||||
{
|
||||
if ($this->transactions == 0) {
|
||||
try {
|
||||
$this->call('beginTransaction');
|
||||
} catch (Throwable $e) {
|
||||
$this->handleBeginTransactionException($e);
|
||||
}
|
||||
} elseif ($this->transactions >= 1) {
|
||||
$this->createSavepoint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a save point within the database.
|
||||
*/
|
||||
protected function createSavepoint()
|
||||
{
|
||||
$this->exec(
|
||||
$this->compileSavepoint('trans' . ($this->transactions + 1))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception from a transaction beginning.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
protected function handleBeginTransactionException(Throwable $e)
|
||||
{
|
||||
if ($this->causedByLostConnection($e)) {
|
||||
$this->reconnect();
|
||||
|
||||
$this->call('beginTransaction');
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a rollback within the database.
|
||||
*/
|
||||
protected function performRollBack(int $toLevel)
|
||||
{
|
||||
if ($toLevel == 0) {
|
||||
$this->call('rollBack');
|
||||
} else {
|
||||
$this->exec(
|
||||
$this->compileSavepointRollBack('trans' . ($toLevel + 1))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception from a rollback.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
protected function handleRollBackException(Throwable $e)
|
||||
{
|
||||
if ($this->causedByLostConnection($e)) {
|
||||
$this->transactions = 0;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL statement to define a savepoint.
|
||||
*/
|
||||
protected function compileSavepoint(string $name): string
|
||||
{
|
||||
return 'SAVEPOINT ' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL statement to execute a savepoint rollback.
|
||||
*/
|
||||
protected function compileSavepointRollBack(string $name): string
|
||||
{
|
||||
return 'ROLLBACK TO SAVEPOINT ' . $name;
|
||||
}
|
||||
}
|
167
src/db/src/MySQLConnection.php
Normal file
167
src/db/src/MySQLConnection.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
use Hyperf\DB\Exception\RuntimeException;
|
||||
use Hyperf\Pool\Pool;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Swoole\Coroutine\MySQL;
|
||||
use Swoole\Coroutine\MySQL\Statement;
|
||||
|
||||
class MySQLConnection extends AbstractConnection
|
||||
{
|
||||
/**
|
||||
* @var MySQL
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'driver' => 'pdo',
|
||||
'host' => 'localhost',
|
||||
'port' => 3306,
|
||||
'database' => 'hyperf',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'pool' => [
|
||||
'min_connections' => 1,
|
||||
'max_connections' => 10,
|
||||
'connect_timeout' => 10.0,
|
||||
'wait_timeout' => 3.0,
|
||||
'heartbeat' => -1,
|
||||
'max_idle_time' => 60.0,
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(ContainerInterface $container, Pool $pool, array $config)
|
||||
{
|
||||
parent::__construct($container, $pool);
|
||||
$this->config = array_replace_recursive($this->config, $config);
|
||||
$this->reconnect();
|
||||
}
|
||||
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
return $this->connection->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect the connection.
|
||||
*/
|
||||
public function reconnect(): bool
|
||||
{
|
||||
$connection = new MySQL();
|
||||
$connection->connect([
|
||||
'host' => $this->config['host'],
|
||||
'port' => $this->config['port'],
|
||||
'user' => $this->config['username'],
|
||||
'password' => $this->config['password'],
|
||||
'database' => $this->config['database'],
|
||||
'timeout' => $this->config['pool']['connect_timeout'],
|
||||
'charset' => $this->config['charset'],
|
||||
'fetch_mode' => true,
|
||||
]);
|
||||
|
||||
$this->connection = $connection;
|
||||
$this->lastUseTime = microtime(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection.
|
||||
*/
|
||||
public function close(): bool
|
||||
{
|
||||
unset($this->connection);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function insert(string $query, array $bindings = []): int
|
||||
{
|
||||
$statement = $this->prepare($query);
|
||||
|
||||
$statement->execute($bindings);
|
||||
|
||||
return $statement->insert_id;
|
||||
}
|
||||
|
||||
public function execute(string $query, array $bindings = []): int
|
||||
{
|
||||
$statement = $this->prepare($query);
|
||||
|
||||
$statement->execute($bindings);
|
||||
|
||||
return $statement->affected_rows;
|
||||
}
|
||||
|
||||
public function exec(string $sql): int
|
||||
{
|
||||
$res = $this->connection->query($sql);
|
||||
if ($res === false) {
|
||||
throw new RuntimeException($this->connection->error);
|
||||
}
|
||||
|
||||
return $this->connection->affected_rows;
|
||||
}
|
||||
|
||||
public function query(string $query, array $bindings = []): array
|
||||
{
|
||||
// For select statements, we'll simply execute the query and return an array
|
||||
// of the database result set. Each element in the array will be a single
|
||||
// row from the database table, and will either be an array or objects.
|
||||
$statement = $this->prepare($query);
|
||||
|
||||
$statement->execute($bindings);
|
||||
|
||||
return $statement->fetchAll();
|
||||
}
|
||||
|
||||
public function fetch(string $query, array $bindings = [])
|
||||
{
|
||||
$records = $this->query($query, $bindings);
|
||||
|
||||
return array_shift($records);
|
||||
}
|
||||
|
||||
public function call(string $method, array $argument = [])
|
||||
{
|
||||
$timeout = $this->config['pool']['wait_timeout'];
|
||||
switch ($method) {
|
||||
case 'beginTransaction':
|
||||
return $this->connection->begin($timeout);
|
||||
case 'rollBack':
|
||||
return $this->connection->rollback($timeout);
|
||||
case 'commit':
|
||||
return $this->connection->commit($timeout);
|
||||
}
|
||||
|
||||
return $this->connection->{$method}(...$argument);
|
||||
}
|
||||
|
||||
protected function prepare(string $query): Statement
|
||||
{
|
||||
$statement = $this->connection->prepare($query);
|
||||
|
||||
if ($statement === false) {
|
||||
throw new RuntimeException($this->connection->error);
|
||||
}
|
||||
|
||||
return $statement;
|
||||
}
|
||||
}
|
176
src/db/src/PDOConnection.php
Normal file
176
src/db/src/PDOConnection.php
Normal file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB;
|
||||
|
||||
use Hyperf\Pool\Exception\ConnectionException;
|
||||
use Hyperf\Pool\Pool;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class PDOConnection extends AbstractConnection
|
||||
{
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'driver' => 'pdo',
|
||||
'host' => 'localhost',
|
||||
'port' => 3306,
|
||||
'database' => 'hyperf',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'fetch_mode' => PDO::FETCH_ASSOC,
|
||||
'pool' => [
|
||||
'min_connections' => 1,
|
||||
'max_connections' => 10,
|
||||
'connect_timeout' => 10.0,
|
||||
'wait_timeout' => 3.0,
|
||||
'heartbeat' => -1,
|
||||
'max_idle_time' => 60.0,
|
||||
],
|
||||
'options' => [
|
||||
PDO::ATTR_CASE => PDO::CASE_NATURAL,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Current mysql database.
|
||||
* @var null|int
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
public function __construct(ContainerInterface $container, Pool $pool, array $config)
|
||||
{
|
||||
parent::__construct($container, $pool);
|
||||
$this->config = array_replace_recursive($this->config, $config);
|
||||
$this->reconnect();
|
||||
}
|
||||
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
return $this->connection->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect the connection.
|
||||
*/
|
||||
public function reconnect(): bool
|
||||
{
|
||||
$host = $this->config['host'];
|
||||
$dbName = $this->config['database'];
|
||||
$username = $this->config['username'];
|
||||
$password = $this->config['password'];
|
||||
$dsn = "mysql:host={$host};dbname={$dbName}";
|
||||
try {
|
||||
$pdo = new \PDO($dsn, $username, $password, $this->config['options']);
|
||||
} catch (\Throwable $e) {
|
||||
throw new ConnectionException('Connection reconnect failed.:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->connection = $pdo;
|
||||
$this->lastUseTime = microtime(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection.
|
||||
*/
|
||||
public function close(): bool
|
||||
{
|
||||
unset($this->connection);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function query(string $query, array $bindings = []): array
|
||||
{
|
||||
// For select statements, we'll simply execute the query and return an array
|
||||
// of the database result set. Each element in the array will be a single
|
||||
// row from the database table, and will either be an array or objects.
|
||||
$statement = $this->connection->prepare($query);
|
||||
|
||||
$this->bindValues($statement, $bindings);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$fetchModel = $this->config['fetch_mode'];
|
||||
|
||||
return $statement->fetchAll($fetchModel);
|
||||
}
|
||||
|
||||
public function fetch(string $query, array $bindings = [])
|
||||
{
|
||||
$records = $this->query($query, $bindings);
|
||||
|
||||
return array_shift($records);
|
||||
}
|
||||
|
||||
public function execute(string $query, array $bindings = []): int
|
||||
{
|
||||
$statement = $this->connection->prepare($query);
|
||||
|
||||
$this->bindValues($statement, $bindings);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
return $statement->rowCount();
|
||||
}
|
||||
|
||||
public function exec(string $sql): int
|
||||
{
|
||||
return $this->connection->exec($sql);
|
||||
}
|
||||
|
||||
public function insert(string $query, array $bindings = []): int
|
||||
{
|
||||
$statement = $this->connection->prepare($query);
|
||||
|
||||
$this->bindValues($statement, $bindings);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
return (int) $this->connection->lastInsertId();
|
||||
}
|
||||
|
||||
public function call(string $method, array $argument = [])
|
||||
{
|
||||
return $this->connection->{$method}(...$argument);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind values to their parameters in the given statement.
|
||||
*/
|
||||
protected function bindValues(PDOStatement $statement, array $bindings): void
|
||||
{
|
||||
foreach ($bindings as $key => $value) {
|
||||
$statement->bindValue(
|
||||
is_string($key) ? $key : $key + 1,
|
||||
$value,
|
||||
is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
24
src/db/src/Pool/MySQLPool.php
Normal file
24
src/db/src/Pool/MySQLPool.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB\Pool;
|
||||
|
||||
use Hyperf\Contract\ConnectionInterface;
|
||||
use Hyperf\DB\MySQLConnection;
|
||||
|
||||
class MySQLPool extends Pool
|
||||
{
|
||||
protected function createConnection(): ConnectionInterface
|
||||
{
|
||||
return new MySQLConnection($this->container, $this, $this->config);
|
||||
}
|
||||
}
|
24
src/db/src/Pool/PDOPool.php
Normal file
24
src/db/src/Pool/PDOPool.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB\Pool;
|
||||
|
||||
use Hyperf\Contract\ConnectionInterface;
|
||||
use Hyperf\DB\PDOConnection;
|
||||
|
||||
class PDOPool extends Pool
|
||||
{
|
||||
protected function createConnection(): ConnectionInterface
|
||||
{
|
||||
return new PDOConnection($this->container, $this, $this->config);
|
||||
}
|
||||
}
|
58
src/db/src/Pool/Pool.php
Normal file
58
src/db/src/Pool/Pool.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB\Pool;
|
||||
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\DB\Frequency;
|
||||
use Hyperf\Pool\Pool as HyperfPool;
|
||||
use Hyperf\Utils\Arr;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
abstract class Pool extends HyperfPool
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
public function __construct(ContainerInterface $container, string $name)
|
||||
{
|
||||
$config = $container->get(ConfigInterface::class);
|
||||
$key = sprintf('db.%s', $name);
|
||||
if (! $config->has($key)) {
|
||||
throw new \InvalidArgumentException(sprintf('config[%s] is not exist!', $key));
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->config = $config->get($key);
|
||||
$options = Arr::get($this->config, 'pool', []);
|
||||
$this->frequency = make(Frequency::class);
|
||||
|
||||
parent::__construct($container, $options);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
}
|
60
src/db/src/Pool/PoolFactory.php
Normal file
60
src/db/src/Pool/PoolFactory.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DB\Pool;
|
||||
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\DB\Exception\DriverNotFoundException;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class PoolFactory
|
||||
{
|
||||
/**
|
||||
* @var Pool[]
|
||||
*/
|
||||
protected $pools = [];
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function getPool(string $name)
|
||||
{
|
||||
if (isset($this->pools[$name])) {
|
||||
return $this->pools[$name];
|
||||
}
|
||||
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
$driver = $config->get(sprintf('db.%s.driver', $name), 'pdo');
|
||||
$class = $this->getPoolName($driver);
|
||||
|
||||
return $this->pools[$name] = make($class, [$this->container, $name]);
|
||||
}
|
||||
|
||||
protected function getPoolName(string $driver)
|
||||
{
|
||||
switch (strtolower($driver)) {
|
||||
case 'mysql':
|
||||
return MySQLPool::class;
|
||||
case 'pdo':
|
||||
return PDOPool::class;
|
||||
}
|
||||
|
||||
throw new DriverNotFoundException(sprintf('Driver %s is not found.', $driver));
|
||||
}
|
||||
}
|
79
src/db/tests/Cases/AbstractTestCase.php
Normal file
79
src/db/tests/Cases/AbstractTestCase.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\DB\Cases;
|
||||
|
||||
use Hyperf\Config\Config;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\DB\DB;
|
||||
use Hyperf\DB\Frequency;
|
||||
use Hyperf\DB\Pool\MySQLPool;
|
||||
use Hyperf\DB\Pool\PDOPool;
|
||||
use Hyperf\DB\Pool\PoolFactory;
|
||||
use Hyperf\Di\Container;
|
||||
use Hyperf\Pool\Channel;
|
||||
use Hyperf\Pool\PoolOption;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\Utils\Context;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Class AbstractTestCase.
|
||||
*/
|
||||
abstract class AbstractTestCase extends TestCase
|
||||
{
|
||||
protected $driver = 'pdo';
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
Context::set('db.connection.default', null);
|
||||
}
|
||||
|
||||
protected function getContainer($options = [])
|
||||
{
|
||||
$container = Mockery::mock(Container::class);
|
||||
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([
|
||||
'db' => [
|
||||
'default' => [
|
||||
'driver' => $this->driver,
|
||||
'password' => '',
|
||||
'database' => 'hyperf',
|
||||
'pool' => [
|
||||
'max_connections' => 20,
|
||||
],
|
||||
'options' => $options,
|
||||
],
|
||||
],
|
||||
]));
|
||||
$container->shouldReceive('make')->with(PDOPool::class, Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new PDOPool(...array_values($args));
|
||||
});
|
||||
$container->shouldReceive('make')->with(MySQLPool::class, Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new MySQLPool(...array_values($args));
|
||||
});
|
||||
$container->shouldReceive('make')->with(Frequency::class, Mockery::any())->andReturn(new Frequency());
|
||||
$container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new PoolOption(...array_values($args));
|
||||
});
|
||||
$container->shouldReceive('make')->with(Channel::class, Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new Channel(...array_values($args));
|
||||
});
|
||||
$container->shouldReceive('get')->with(PoolFactory::class)->andReturn($factory = new PoolFactory($container));
|
||||
$container->shouldReceive('get')->with(DB::class)->andReturn(new DB($factory, 'default'));
|
||||
$container->shouldReceive('has')->with(StdoutLoggerInterface::class)->andReturn(false);
|
||||
ApplicationContext::setContainer($container);
|
||||
return $container;
|
||||
}
|
||||
}
|
52
src/db/tests/Cases/MySQLDriverTest.php
Normal file
52
src/db/tests/Cases/MySQLDriverTest.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\DB\Cases;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class MySQLDriverTest extends PDODriverTest
|
||||
{
|
||||
protected $driver = 'mysql';
|
||||
|
||||
public function testFetch()
|
||||
{
|
||||
parent::testFetch();
|
||||
}
|
||||
|
||||
public function testQuery()
|
||||
{
|
||||
parent::testQuery();
|
||||
}
|
||||
|
||||
public function testInsertAndExecute()
|
||||
{
|
||||
parent::testInsertAndExecute();
|
||||
}
|
||||
|
||||
public function testTransaction()
|
||||
{
|
||||
parent::testTransaction();
|
||||
}
|
||||
|
||||
public function testConfig()
|
||||
{
|
||||
parent::testConfig();
|
||||
}
|
||||
|
||||
public function testMultiTransaction()
|
||||
{
|
||||
parent::testMultiTransaction();
|
||||
}
|
||||
}
|
118
src/db/tests/Cases/PDODriverTest.php
Normal file
118
src/db/tests/Cases/PDODriverTest.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\DB\Cases;
|
||||
|
||||
use Hyperf\DB\DB;
|
||||
use Hyperf\DB\Pool\PoolFactory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class PDODriverTest extends AbstractTestCase
|
||||
{
|
||||
public function testFetch()
|
||||
{
|
||||
$db = $this->getContainer()->get(DB::class);
|
||||
|
||||
$res = $db->fetch('SELECT * FROM `user` WHERE id = ?;', [2]);
|
||||
|
||||
$this->assertSame('Hyperflex', $res['name']);
|
||||
}
|
||||
|
||||
public function testQuery()
|
||||
{
|
||||
$db = $this->getContainer()->get(DB::class);
|
||||
|
||||
$res = $db->query('SELECT * FROM `user` WHERE id = ?;', [2]);
|
||||
|
||||
$this->assertSame('Hyperflex', $res[0]['name']);
|
||||
}
|
||||
|
||||
public function testInsertAndExecute()
|
||||
{
|
||||
$db = $this->getContainer()->get(DB::class);
|
||||
|
||||
$id = $db->insert('INSERT INTO `user` (`name`, `gender`) VALUES (?,?);', [$name = uniqid(), $gender = rand(0, 2)]);
|
||||
$this->assertTrue($id > 0);
|
||||
|
||||
$res = $db->fetch('SELECT * FROM `user` WHERE id = ?;', [$id]);
|
||||
$this->assertSame($name, $res['name']);
|
||||
$this->assertSame($gender, $res['gender']);
|
||||
|
||||
$res = $db->execute('UPDATE `user` SET `name` = ? WHERE id = ?', [$name = uniqid(), $id]);
|
||||
$this->assertTrue($res > 0);
|
||||
$res = $db->fetch('SELECT * FROM `user` WHERE id = ?;', [$id]);
|
||||
$this->assertSame($name, $res['name']);
|
||||
}
|
||||
|
||||
public function testTransaction()
|
||||
{
|
||||
$db = $this->getContainer()->get(DB::class);
|
||||
$db->beginTransaction();
|
||||
$id = $db->insert('INSERT INTO `user` (`name`, `gender`) VALUES (?,?);', [$name = uniqid(), $gender = rand(0, 2)]);
|
||||
$this->assertTrue($id > 0);
|
||||
$db->commit();
|
||||
|
||||
$res = $db->fetch('SELECT * FROM `user` WHERE id = ?;', [$id]);
|
||||
$this->assertSame($name, $res['name']);
|
||||
$this->assertSame($gender, $res['gender']);
|
||||
|
||||
$db->beginTransaction();
|
||||
$id = $db->insert('INSERT INTO `user` (`name`, `gender`) VALUES (?,?);', [$name = uniqid(), $gender = rand(0, 2)]);
|
||||
$this->assertTrue($id > 0);
|
||||
$db->rollBack();
|
||||
|
||||
$res = $db->fetch('SELECT * FROM `user` WHERE id = ?;', [$id]);
|
||||
$this->assertNull($res);
|
||||
}
|
||||
|
||||
public function testConfig()
|
||||
{
|
||||
$factory = $this->getContainer()->get(PoolFactory::class);
|
||||
$pool = $factory->getPool('default');
|
||||
|
||||
$this->assertSame('hyperf', $pool->getConfig()['database']);
|
||||
$this->assertSame([], $pool->getConfig()['options']);
|
||||
|
||||
$connection = $pool->get();
|
||||
$this->assertSame(6, count($connection->getConfig()['pool']));
|
||||
$this->assertSame(20, $connection->getConfig()['pool']['max_connections']);
|
||||
}
|
||||
|
||||
public function testMultiTransaction()
|
||||
{
|
||||
$db = $this->getContainer()->get(DB::class);
|
||||
$db->beginTransaction();
|
||||
$id = $db->insert('INSERT INTO `user` (`name`, `gender`) VALUES (?,?);', [$name = 'trans' . uniqid(), $gender = rand(0, 2)]);
|
||||
$this->assertTrue($id > 0);
|
||||
$db->beginTransaction();
|
||||
$id2 = $db->insert('INSERT INTO `user` (`name`, `gender`) VALUES (?,?);', ['rollback' . uniqid(), rand(0, 2)]);
|
||||
$this->assertTrue($id2 > 0);
|
||||
$db->rollBack();
|
||||
$db->commit();
|
||||
|
||||
$res = $db->fetch('SELECT * FROM `user` WHERE id = ?;', [$id2]);
|
||||
$this->assertNull($res);
|
||||
$res = $db->fetch('SELECT * FROM `user` WHERE id = ?;', [$id]);
|
||||
$this->assertNotNull($res);
|
||||
}
|
||||
|
||||
public function testStaticCall()
|
||||
{
|
||||
$this->getContainer();
|
||||
$res = DB::fetch('SELECT * FROM `user` WHERE id = ?;', [1]);
|
||||
|
||||
$this->assertSame('Hyperf', $res['name']);
|
||||
}
|
||||
}
|
13
src/db/tests/bootstrap.php
Normal file
13
src/db/tests/bootstrap.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
require_once dirname(dirname(__FILE__)) . '/vendor/autoload.php';
|
Loading…
Reference in New Issue
Block a user