无法关闭 PDO 连接
Cannot close PDO connection
我使用 SLIM
开发了一个 API
,它运行良好,但不幸的是我在 PDO
连接上遇到了问题。本质上,当调用 REST-API
时,将遵循以下逻辑:
1. Route call controller class
2. Controller class call the model
例如:
$this->post('/user/add', \User::class . ':add');
上面的路由将在 class User
:
上调用方法 add
<?php namespace App\Controllers;
defined('BASEPATH') OR exit('No direct script access allowed');
use Slim\Http\Request;
use Slim\Http\Response;
use Core\Controller;
use App\Models\userModel;
use Exception;
class User extends Controller
{
private $_user_model;
public function __construct($settings)
{
parent::__construct($settings);
$this->_user_model = new UserModel($settings);
}
public function add(Request $request, Response $response, array $args)
{
$data = $request->getParsedBody();
$result = $this->_user_model->add($data['user']);
return $response->withJSON([
"status" => SUCCESS_MSG,
"data" => json_encode($result, true),
"message" => "User stored."
]);
}
}
重要的部分在UserModel
:
<?php namespace App\Models;
defined('BASEPATH') OR exit('No direct script access allowed');
use App\Controllers\License;
use Core\Model;
use Exception;
use PDO;
class UserModel extends Model
{
public function __construct($settings)
{
parent::__construct($settingss);
}
如您所见,class extends Model
实例化了一个新连接,问题是当 API
return 响应时连接未关闭:
<?php namespace Core;
defined('BASEPATH') OR exit('No direct script access allowed');
use Core\Database;
class Model
{
function __construct($settings, $tenant = false)
{
$this->db = new Database($settings, $tenant);
}
function __destruct()
{
$this->db = null;
}
}
Model
class 包含 db
对象,它是 Database
的一部分,还有一个 destructor
应该取消连接:
<?php namespace Core;
defined('BASEPATH') OR exit('No direct script access allowed');
use PDO;
class Database extends PDO
{
public function __construct($settings, $tenant = false)
{
try
{
$options = [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8",
PDO::ATTR_PERSISTENT, false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => ($settings['displayErrorDetails']) ? PDO::ERRMODE_EXCEPTION : PDO::ERRMODE_SILENT
];
$db = $settings['db'];
parent::__construct($db['type'] . ':host=' . $db['host'] . ';dbname=' . $db['name'], $db['user'], $db['pass'], $options);
}
catch(PDOException $e)
{
throw $e->getMessage();
}
}
}
如您所见,我将 ATTR_PERSISTENT
设置为 false。
每次调用 API
时都会创建一个新连接,但不会处理它,事实上如果我执行:
show processlist
我有:
我的代码有问题吗?这个问题真的很烦人,因为当 API
被多次调用时我会得到 TOO MANY CONNECTION
。
有什么解决办法吗?
更新
我看到只有当 API 出现错误时才会出现问题,我实际上使用 Container
:
来处理错误
$container['errorHandler'] = function ($c) {
return new \Core\ErrorHandler($c);
};
这是完整的 class:
<?php namespace Core;
defined('BASEPATH') OR exit('No direct script access allowed');
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
class ErrorHandler extends \Slim\Handlers\Error
{
private $_settings;
public function __construct($c)
{
$this->_settings = $c['settings'];
}
public function __invoke(Request $request, Response $response, \Exception $exception)
{
$status = $exception->getCode() : 500;
$error = [
"status" => ERROR_MSG,
"data" => [
"stack_trace" => ($this->_settings['displayErrorDetails']) ? $exception->getTraceAsString() : '',
],
"message" => $exception->getMessage()
];
$body = json_encode($error, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
return $response
->withStatus($status)
->withHeader("Content-type", "application/json")
->write($body);
}
}
出于某种原因,应用程序实例仍处于打开状态并且连接也处于打开状态
我看到这里有两个问题。
第一个:
PDO::ATTR_PERSISTENT, false,
PDO::ATTR_PERSISTENT
和 false
之间有一个逗号,而不是 =>
。这可能会导致意外行为。至少,它不会像预期的那样将 ATTR_PERSISTENT
设置为 false。
第二个:
function __construct($settings, $tenant = false)
{
$this->db = new Database($settings, $tenant);
}
每次构造模型对象时,您都在创建一个新的数据库对象——因此,一个新的数据库连接。毫不奇怪,这会创建大量数据库连接。
不要这样做。数据库对象应该作为单例存在,可能作为应用程序全局或框架 class 的静态 属性。它不应该被例行实例化。
在 Slim 中,您应该将容器作为 "factory" 用于您的应用程序服务,例如数据库连接。请永远不要从 PDO 扩展 class,因为它会导致很多奇怪的错误。
像这样为共享数据库连接对象创建容器条目:
(Slim 3 示例)
// container.php
use Slim\Container;
use PDO
// ...
$container[PDO::class] = function (Container $container) {
$settings = $container->get('settings')['db'];
$host = $settings['host'];
$dbname = $settings['database'];
$username = $settings['username'];
$password = $settings['password'];
$charset = $settings['charset'];
$collate = $settings['collation'];
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset COLLATE $collate"
];
return new PDO($dsn, $username, $password, $options);
};
// ...
然后创建一个模型(数据映射器),将数据库连接 (PDO) 作为依赖项,如下所示:
namespace App\Model
use PDO;
class UserModel
{
private $connection;
public function __construct(PDO $connection)
{
$this->connection = $connection;
}
public function findAllUsers(): array
{
$this->connection->query('SELECT * FROM users')->fetchAll();
}
}
您不需要析构函数,因为 PHP 会在请求完成后自动为您关闭连接。
我使用 SLIM
开发了一个 API
,它运行良好,但不幸的是我在 PDO
连接上遇到了问题。本质上,当调用 REST-API
时,将遵循以下逻辑:
1. Route call controller class
2. Controller class call the model
例如:
$this->post('/user/add', \User::class . ':add');
上面的路由将在 class User
:
add
<?php namespace App\Controllers;
defined('BASEPATH') OR exit('No direct script access allowed');
use Slim\Http\Request;
use Slim\Http\Response;
use Core\Controller;
use App\Models\userModel;
use Exception;
class User extends Controller
{
private $_user_model;
public function __construct($settings)
{
parent::__construct($settings);
$this->_user_model = new UserModel($settings);
}
public function add(Request $request, Response $response, array $args)
{
$data = $request->getParsedBody();
$result = $this->_user_model->add($data['user']);
return $response->withJSON([
"status" => SUCCESS_MSG,
"data" => json_encode($result, true),
"message" => "User stored."
]);
}
}
重要的部分在UserModel
:
<?php namespace App\Models;
defined('BASEPATH') OR exit('No direct script access allowed');
use App\Controllers\License;
use Core\Model;
use Exception;
use PDO;
class UserModel extends Model
{
public function __construct($settings)
{
parent::__construct($settingss);
}
如您所见,class extends Model
实例化了一个新连接,问题是当 API
return 响应时连接未关闭:
<?php namespace Core;
defined('BASEPATH') OR exit('No direct script access allowed');
use Core\Database;
class Model
{
function __construct($settings, $tenant = false)
{
$this->db = new Database($settings, $tenant);
}
function __destruct()
{
$this->db = null;
}
}
Model
class 包含 db
对象,它是 Database
的一部分,还有一个 destructor
应该取消连接:
<?php namespace Core;
defined('BASEPATH') OR exit('No direct script access allowed');
use PDO;
class Database extends PDO
{
public function __construct($settings, $tenant = false)
{
try
{
$options = [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8",
PDO::ATTR_PERSISTENT, false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => ($settings['displayErrorDetails']) ? PDO::ERRMODE_EXCEPTION : PDO::ERRMODE_SILENT
];
$db = $settings['db'];
parent::__construct($db['type'] . ':host=' . $db['host'] . ';dbname=' . $db['name'], $db['user'], $db['pass'], $options);
}
catch(PDOException $e)
{
throw $e->getMessage();
}
}
}
如您所见,我将 ATTR_PERSISTENT
设置为 false。
每次调用 API
时都会创建一个新连接,但不会处理它,事实上如果我执行:
show processlist
我有:
我的代码有问题吗?这个问题真的很烦人,因为当 API
被多次调用时我会得到 TOO MANY CONNECTION
。
有什么解决办法吗?
更新
我看到只有当 API 出现错误时才会出现问题,我实际上使用 Container
:
$container['errorHandler'] = function ($c) {
return new \Core\ErrorHandler($c);
};
这是完整的 class:
<?php namespace Core;
defined('BASEPATH') OR exit('No direct script access allowed');
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
class ErrorHandler extends \Slim\Handlers\Error
{
private $_settings;
public function __construct($c)
{
$this->_settings = $c['settings'];
}
public function __invoke(Request $request, Response $response, \Exception $exception)
{
$status = $exception->getCode() : 500;
$error = [
"status" => ERROR_MSG,
"data" => [
"stack_trace" => ($this->_settings['displayErrorDetails']) ? $exception->getTraceAsString() : '',
],
"message" => $exception->getMessage()
];
$body = json_encode($error, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
return $response
->withStatus($status)
->withHeader("Content-type", "application/json")
->write($body);
}
}
出于某种原因,应用程序实例仍处于打开状态并且连接也处于打开状态
我看到这里有两个问题。
第一个:
PDO::ATTR_PERSISTENT, false,
PDO::ATTR_PERSISTENT
和 false
之间有一个逗号,而不是 =>
。这可能会导致意外行为。至少,它不会像预期的那样将 ATTR_PERSISTENT
设置为 false。
第二个:
function __construct($settings, $tenant = false)
{
$this->db = new Database($settings, $tenant);
}
每次构造模型对象时,您都在创建一个新的数据库对象——因此,一个新的数据库连接。毫不奇怪,这会创建大量数据库连接。
不要这样做。数据库对象应该作为单例存在,可能作为应用程序全局或框架 class 的静态 属性。它不应该被例行实例化。
在 Slim 中,您应该将容器作为 "factory" 用于您的应用程序服务,例如数据库连接。请永远不要从 PDO 扩展 class,因为它会导致很多奇怪的错误。
像这样为共享数据库连接对象创建容器条目:
(Slim 3 示例)
// container.php
use Slim\Container;
use PDO
// ...
$container[PDO::class] = function (Container $container) {
$settings = $container->get('settings')['db'];
$host = $settings['host'];
$dbname = $settings['database'];
$username = $settings['username'];
$password = $settings['password'];
$charset = $settings['charset'];
$collate = $settings['collation'];
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset COLLATE $collate"
];
return new PDO($dsn, $username, $password, $options);
};
// ...
然后创建一个模型(数据映射器),将数据库连接 (PDO) 作为依赖项,如下所示:
namespace App\Model
use PDO;
class UserModel
{
private $connection;
public function __construct(PDO $connection)
{
$this->connection = $connection;
}
public function findAllUsers(): array
{
$this->connection->query('SELECT * FROM users')->fetchAll();
}
}
您不需要析构函数,因为 PHP 会在请求完成后自动为您关闭连接。