Mysql
 sql >> Base de données >  >> RDS >> Mysql

Comment réparer le message :SQLSTATE[08004] [1040] Trop de connexions

Parce que votre Model la classe instancie une nouvelle Database objet dans son constructeur, chaque fois que vous instanciez un Model (ou toute classe l'étendant), vous ouvrez en fait un nouveau connexion à la base de données. Si vous créez plusieurs Model objets, chacun a alors sa propre connexion à la base de données indépendante, ce qui est rare, généralement inutile, pas une bonne utilisation des ressources, mais aussi activement nuisible car il a utilisé toutes les connexions disponibles du serveur.

Par exemple, boucler pour créer un tableau de Model objets :

// 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)

Utiliser l'injection de dépendance :

La solution consiste à utiliser l'injection de dépendance et pass la Database objet dans le Model::__construct() plutôt que de lui permettre d'instancier le sien.

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

Pour l'utiliser ensuite, le code de contrôle (le code qui va instancier vos modèles) doit lui-même appeler new Database() juste une fois. Cet objet créé par le code de contrôle doit ensuite être transmis aux constructeurs de tous les modèles.

// Instantiate one Database
$db = new Database();

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

Pour le cas d'utilisation où vous avez réellement besoin d'une connexion de base de données indépendante différente pour un modèle, vous pouvez lui en donner une autre. En particulier, cela est utile pour tester . Vous pouvez remplacer un objet de base de données de test ou un objet fictif.

// Instantiate one Database
$db = new Database();
$another_db = new Database();

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

Connexions persistantes :

Comme mentionné dans les commentaires, l'utilisation d'une connexion persistante est peut-être une solution, mais pas la solution que je recommanderais . PDO tentera de réutiliser une connexion existante avec les mêmes informations d'identification (comme toutes les vôtres), mais vous ne souhaitez pas nécessairement que la connexion soit mise en cache lors de l'exécution du script. Si vous avez décidé de le faire de cette façon, vous devez passer l'attribut à la Database constructeur.

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'");
}

La documentation pertinente est ici :http://php.net/manual /fr/pdo.connections.php#example-950

Solution singleton :

En utilisant un modèle singleton (également déconseillé), vous pouvez au moins le réduire à une recherche/remplacement dans le code du modèle. La Database La classe a besoin d'une propriété statique pour conserver une connexion pour elle-même. Les modèles appellent alors Database::getInstance() au lieu de new Database() pour récupérer la connexion. Vous auriez besoin de faire une recherche et de remplacer dans le code du modèle pour remplacer Database::getInstance() .

Bien que cela fonctionne bien et ne soit pas difficile à mettre en œuvre, dans votre cas, cela rendrait les tests un peu plus difficiles car vous devriez remplacer l'intégralité de la Database classe avec une classe de test du même nom. Vous ne pouvez pas facilement substituer une classe de test instance par instance.

Appliquer le modèle 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;
   }
}

Maintenant, vous n'auriez plus qu'à changer le Model code à utiliser Database::getInstance() :

class Model {
    
  protected $_db;
    
   public function __construct(){
     // Retrieve the database singleton
     $this->_db = Database::getInstance();
   }
}