无法关闭 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_PERSISTENTfalse 之间有一个逗号,而不是 =>。这可能会导致意外行为。至少,它不会像预期的那样将 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 会在请求完成后自动为您关闭连接。