Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como corrigir a mensagem:SQLSTATE[08004] [1040] Muitas conexões


Porque seu Model classe instancia um novo Database objeto em seu construtor, cada vez que você instanciar um Model (ou qualquer classe estendendo-o), você está abrindo um novo conexão de banco de dados. Se você criar vários Model objetos, cada um tem sua própria conexão de banco de dados independente, o que é incomum, geralmente desnecessário, não é um bom uso de recursos, mas também ativamente prejudicial, pois esgotou todas as conexões disponíveis do servidor.

Por exemplo, fazendo um loop para criar uma matriz de Model objetos:
// 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)

Usar injeção de dependência:


A solução é usar injeção de dependência e passar o Database objeto no Model::__construct() em vez de permitir que ele instancie o seu próprio.
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;
  }
}

Para usá-lo então, o código de controle (o código que instanciará seus modelos) deve chamar new Database() apenas uma vez. Esse objeto criado pelo código de controle deve então ser passado para os construtores de todos os modelos.
// Instantiate one Database
$db = new Database();

// Pass it to models
$model = new Model($db);

Para o caso de uso em que você realmente precisa de uma conexão de banco de dados independente diferente para um modelo, você pode fornecer uma diferente. Em particular, isso é útil para testar . Você pode substituir um objeto de banco de dados de teste ou um objeto simulado.
// Instantiate one Database
$db = new Database();
$another_db = new Database();

// Pass it to models
$model = new Model($db);
$another_model = new Model($another_db);

Conexões persistentes:


Como mencionado nos comentários, usar uma conexão persistente é possivelmente uma solução, mas não é a solução que eu recomendaria . O PDO tentará reutilizar uma conexão existente com as mesmas credenciais (como todas as suas terão), mas você não deseja necessariamente que a conexão seja armazenada em cache na execução do script. Se você decidiu fazer desta forma, você precisa passar o atributo para o Database construtor.
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'");
}

A documentação relevante está aqui:http://php.net/manual /en/pdo.connections.php#example-950

Solução singleton:


Usando um padrão singleton (também não recomendado), você pode pelo menos reduzir isso a uma pesquisa/substituição no código do modelo. O Database classe precisa de uma propriedade estática para manter uma conexão para si mesma. Os modelos então chamam Database::getInstance() em vez de new Database() para recuperar a conexão. Você precisaria fazer uma pesquisa e substituir no código do modelo para substituir Database::getInstance() .

Embora funcione bem e não seja difícil de implementar, no seu caso tornaria o teste um pouco mais difícil, pois você teria que substituir todo o Database classe com uma classe de teste com o mesmo nome. Você não pode substituir facilmente uma classe de teste em uma base de instância por instância.

Aplicar padrão singleton ao 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;
   }
}

Agora você precisaria alterar apenas o Model código para usar Database::getInstance() :
class Model {
    
  protected $_db;
    
   public function __construct(){
     // Retrieve the database singleton
     $this->_db = Database::getInstance();
   }
}