无法关闭PDO连接

时间:2019-08-02 19:34:26

标签: php pdo slim

我使用API开发了一个SLIM,效果很好,但是很不幸,我在PDO连接上遇到了问题。本质上,在调用REST-API时遵循以下逻辑:

1. Route call controller class
2. Controller class call the model

例如:

$this->post('/user/add', \User::class . ':add');

以上路线将在类add上调用方法User

<?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);
    }

您可以看到该类扩展了Model并实例化了一个新连接,问题在于,当API返回响应时,该连接未关闭:

<?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类包含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

我有:

enter image description here

我的代码有问题吗?这个问题实在令人讨厌,因为TOO MANY CONNECTION被多次调用会得到API

有什么解决办法吗?

更新

我看到只有在API中有错误时才会发生问题,我实际上使用Container处理错误:

$container['errorHandler'] = function ($c) {
    return new \Core\ErrorHandler($c);  
};

这是完整的课程:

<?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);
    }
}

由于某种原因,应用程序实例仍处于打开状态,并且连接也保持打开状态

2 个答案:

答案 0 :(得分:1)

我在这里看到两个问题。

第一:

PDO::ATTR_PERSISTENT, false,

PDO::ATTR_PERSISTENTfalse之间有一个逗号,而不是=>。这可能会导致意外的行为。至少,它不会像预期的那样将ATTR_PERSISTENT设置为false。

第二:

function __construct($settings, $tenant = false)
{
    $this->db = new Database($settings, $tenant);
}

每次创建Model对象时,您都在创建一个新的Database对象-从而创建了到数据库的新连接。毫不奇怪,这会创建许多数据库连接。

不要这样做。 Database对象应该作为一个单例存在,可能作为应用程序全局或框架类的静态属性存在。不应常规实例化它。

答案 1 :(得分:1)

在Slim中,应将容器用作应用程序服务(如数据库连接)的“工厂”。请不要从PDO扩展类,因为它会导致很多奇怪的错误。

为共享数据库连接对象创建容器条目,如下所示:

(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会自动为您关闭连接。