Я использую ниже код для подключения к базе данных
class Database extends PDO{ function __construct(){ try { parent::__construct(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'"); } catch(PDOException $e){ Logger::newMessage($e); logger::customErrorMsg(); } } }
все, как логин, выборка данных работала нормально. Теперь у меня появляется сообщение об ошибке исключения
Message: SQLSTATE[08004] [1040] Too many connections Code: 1040
Как исправить эту ошибку?
У меня есть класс модели, я создаю новую базу данных.
class Model { protected $_db; public function __construct(){ //connect to PDO here. $this->_db = new Database(); } }
и каждая модель, которую я делаю, я расширяю от класса модели.
Поскольку ваш класс Model
создает экземпляр нового объекта Database
в его конструкторе, каждый раз, когда вы создаете экземпляр Model
(или любого ее расширения), вы фактически открываете новое соединение с базой данных. Если вы создаете несколько объектов Model
, у каждого из них есть собственное независимое соединение с базой данных, что является необычным, обычно ненужным, а не хорошим использованием ресурсов, но также активно вредным, поскольку оно исчерпало все доступные серверы.
Например, цикл для создания массива объектов Model
:
// If a loop creates an array of Model objects while ($row = $something->fetch()) { $models[] = new Model(); } // each object in $models has an independent database connection // the number of connections now in use by MySQL is now == count($models)
Решение состоит в том, чтобы использовать инъекцию зависимостей и передать объект Database
в Model::__construct()
а не позволять ему создавать свои собственные экземпляры.
class Model { protected $_db; // Accept Database as a parameter public function __construct(Database $db) { // Assign the property, do not instantiate a new Database object $this->_db = $db; } }
Чтобы использовать его, управляющий код (код, который будет создавать ваши модели) должен сам вызвать new Database()
только один раз. Затем этот объект, созданный управляющим кодом, должен быть передан конструкторам всех моделей.
// Instantiate one Database $db = new Database(); // Pass it to models $model = new Model($db);
Для случая использования, когда вам действительно требуется другое независимое соединение с базой данных для модели, вы можете передать ее другой. В частности, это полезно для тестирования . Вы можете заменить объект тестовой базы данных или макет объекта.
// Instantiate one Database $db = new Database(); $another_db = new Database(); // Pass it to models $model = new Model($db); $another_model = new Model($another_db);
Как упоминалось в комментариях, использование постоянного соединения – это, возможно, решение, но не решение, которое я бы рекомендовал . PDO попытается повторно использовать существующее соединение с теми же учетными данными (как и все ваши), но не обязательно, чтобы соединение было кэшировано в процессе выполнения скрипта. Если вы решили сделать это таким образом, вам нужно передать атрибут в конструктор Database
.
try { // Set ATTR_PERSISTENT in the constructor: parent::__construct(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS, array(PDO::ATTR_PERSISTENT => true)); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'"); }
Соответствующая документация находится здесь: http://php.net/manual/en/pdo.connections.php#example-950
Используя одноэлементный шаблон (также не рекомендуется), вы могли бы по крайней мере уменьшить это до поиска / замены в коде модели. Класс Database
требует статического свойства, чтобы поддерживать соединение для себя. Затем модели вызывают Database::getInstance()
вместо new Database()
для извлечения соединения. Вам нужно будет выполнить поиск и заменить в коде модели, чтобы заменить Database::getInstance()
.
Несмотря на то, что он работает хорошо и его не сложно реализовать, в вашем случае это сделает тестирование немного более сложным, так как вам придется заменить весь класс Database
классом тестирования с тем же именем. Вы не можете легко заменить тестовый класс на экземпляр на основе экземпляра.
Применить шаблон singleton к Database
:
class Database extends PDO{ // Private $connection property, static private static $connection; // Normally a singleton would necessitate a private constructor // but you can't make this private while the PDO // base class exposes it as public public function __construct(){ try { parent::__construct(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'"); } catch(PDOException $e){ Logger::newMessage($e); logger::customErrorMsg(); } } // public getInstance() returns existing or creates new connection public static function getInstance() { // Create the connection if not already created if (self::$connection == null) { self::$connection = new self(); } // And return a reference to that connection return self::$connection; } }
Теперь вам нужно будет изменить только код Model
для использования Database::getInstance()
:
class Model { protected $_db; public function __construct(){ // Retrieve the database singleton $this->_db = Database::getInstance(); } }