Programmation PHP/Version imprimable4
Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Programmation_PHP
DOMDocument
Introduction
modifierUne des recherches majeures du développeur a toujours été de chercher à séparer les langages de programmation, clarifiant ainsi ses scripts et simplifiant sa tâche. Ainsi, il est possible d'enregistrer son CSS dans des fichiers externes comportant l'extension .css, de séparer le Javascript du HTML en l'enregistrant dans des fichiers .js.
Il reste cependant le problème de la séparation du PHP et du XML (incluant le HTML). La bibliothèque DOMDocument va repousser ces limites.
Qu'est-ce que DOMDocument ?
modifierDOMDocument est une bibliothèque de fonctions apparue avec PHP5[1] et activée par défaut. Elle permet de concevoir des pages HTML sous forme d'objets.
Les avantages et les inconvénients
modifier- Concevoir des pages HTML par cette méthode permet d’annihiler un problème majeur de la programmation procédurale : l'édition du code n'est plus en fonction de sa position dans le script. Pour être plus clair, chaque balise jusqu'à la DTD peut être modifiée à tout moment dans l'objet HTML.
- Il est possible d'enregistrer la page HTML dans un fichier sans l'afficher.
mais...
- Le code est plus long à éditer.
Principe du DOM
modifierCette bibliothèque présente de nombreuses similitudes avec le Javascript aussi bien dans le fonctionnement que dans le nom de ses fonctions.
Le DOM (Document Object Model) est basé sur un système de nodes (nœuds). Un node est un élément qui est - soit une balise (nodes Tag) - soit du texte - soit un attribut de balise Les nodes sont liés par un système hiérarchique :
<p>Ce texte est <strong>important</strong></p>
On dit alors que le node Tag <strong> est fils du node <p> Le node texte "Ce texte est" est également fils de <p> qui est parent du node texte.
Il existe un certain nombre de classes prédéfinies : DOMDocument, DOMNode, DOMElement, DOMText, DOMAttr, DOMList... Certaines sont très simples, d'autres possèdent des fonctionnalités très avancées.
Importer une page préexistante
modifierIl est possible d'importer une page HTML. Cela simplifiera considérablement la tâche du programmeur qui n'aura qu'à apporter les modifications nécessaires avant de l'afficher ou de réenregistrer la page. Voici le code important la page.
<?php
$doc = DOMDocument::loadHTMLFile("fichier.html");
La variable $doc contient donc un objet DOMDocument avec toutes les balises sous formes de nodes. Il est maintenant possible d'accéder aux nodes par le biais de fonctions préexistantes.
NB : il est également possible d'avoir recours au code suivant.
<?php
$doc = new DOMDocument();
$doc->loadHTMLFile("fichier.html");
Il est également possible d'importer le code à partir d'une chaîne de caractères :
<?php
$code = "<html><head></head><body></body></html>";
$doc = new DOMDocument();
$doc->loadHTML( $code );
Enregistrer une page
modifierUn des grands avantages de cette bibliothèque est la capacité à enregistrer la page générée dans un fichier pour un affichage ultérieur. Il suffit d'avoir recours au code suivant :
$doc->saveHTMLFile("fichier.html");
Si vous voulez l'afficher, il vous suffit d'exécuter la fonction suivante :
<?php
echo $doc->saveHTML();
La méthode retourne une chaîne de caractères que la fonction echo
affiche.
Le résultat n'est pas en Unicode, donc les lettres avec diacritiques seront mal affichées par défaut, en français : àâçéèêëîïôöüù. Cela peut aussi générer un Warning: DOMDocumentFragment::appendXML(): Entity: line 1: parser error : Input is not proper UTF-8, indicate encoding !
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr-FR">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
</head>
<body>
<?php
$code = 'Les àâçéèêëîïôöüù';
echo $code; // affichage normal
$page = new DOMDocument();
$page->loadHTML($code);
echo $page->saveHTML(); // Les à âçéèêëîïôöüù
// Solution
$page2 = new DOMDocument();
$page2->loadHTML(utf8_decode($code));
echo $page2->saveHTML(); // Les àâçéèêëîïôöüù
?>
</body>
</html>
La classe DOMNode
modifierLes classes DOMElement, DOMText et DOMAttribute sont dérivées de cette classe. Ainsi, les méthodes et propriétés présentées ici seront disponibles pour leurs classes filles.
Attention : un nœud une fois créé ne se trouve pas dans le document. Ajouter un nœud va se dérouler en deux étapes :
- on crée le nœud
- on l'insère dans le nœud parent ou à la racine du document.
Voici les propriétés accessibles à tous les nœuds :
$node->nodeType // Type de nœud. Vaut 1 pour un élément XML, 3 pour un texte
$node->childNodes //Retourne un objet NodeList qui contient tous les éléments enfants de ce nœud
$node->firstChild //Retourne le premier nœud enfant
$node->lastChild // Retourne le dernier nœud enfant
$node->previousSibling // Retourne le nœud juste avant celui-ci
$node->nextSibling // Retourne le nœud juste après celui-ci
Les méthodes sont les suivantes :
- AppendChild
- Le nœud $enfant devient enfant de $node
$node->appendChild( $enfant );
- RemoveChild
- Supprime le nœud $enfant du node $node
$node->removeChild( $enfant );
La classe DOMElement
modifier- Les éléments possèdent les propriétés suivantes :
$node->tagName // par exemple "p" pour un paragraphe. Sa valeur ne peut être modifiée
Attention : seules les principales propriétés sont présentées. Si vous voulez en avoir la liste complète, vous pouvez consulter la documentation de référence sur http://php.net/manual/fr/class.domnode.php.
- Et les méthodes suivantes :
- Méthodes d'attributs
$node->hasAttribute(); //Renvoie true si il possède des attributs
$node->getAttribute("name"); //Retourne la valeur de l'attribut
$node->removeAttribute("name"); //Supprime l'attribut ''name''
$node->setAttribute("name","value"); //Modifie un attribut
$node->getAttributeNode ("name" ); //Retourne un objet DOMAttr
- Autres méthodes
$newNode = $node->cloneNode(); //duplique un élément
$nodeList = $node->getElementsByTagName("strong");
Cette fonction retourne un objet nodeList qui contient une liste des balises <strong> enfants du nœud. Pour récupérer le n+1ème nœud de la liste, il suffit d'avoir recours à la méthode de l'objet nodeList suivante :
$strong5 = $nodeList->item(4); //Sélectionne la 5e balise <strong>
L'attribut length donne le nombre d'éléments de la liste. Exemple :
for ($i=0; $i<$nodeList->length; $i++)
{
echo $nodeList->item( $i )->tagName;
}
Comme vous le savez, tagName retourne le nom de la balise. Ici, le code retournera "strongstrongstrong…". En effet, seuls les nœuds strong ont "été sélectionné. Comme vous avez pu le remarquer, il est possible d'exécuter plusieurs méthodes et propriétés en même temps. Voici l'ordre d'exécution :
- La méthode item($i) est exécutée et retourne un nodeTag.
- La propriété tagName du nœud est appelée. Attention : c'est celle de l'objet retourné.
- La fonction echo affiche le nom de la balise retournée par la propriété tagName.
La classe DOMText
modifierLa classe DOMText contient l'unique propriété suivante :
$node->wholeText
Elle n'est accessible qu'en lecture seule.
La classe DOMText admet deux méthodes :
/* Retourne true si la chaîne de caractère contient des espaces */
$node->isWhitespaceInElementContent();
/* Retourne dans $end un objet Text qui contient la fin du texte de $node. $node ne contiendra plus que les 5 premiers caractères de sa chaîne */
$end = $node->splitText(5)
Exemple :
<?php
$doc = new DOMDocument();
$text = $doc->createTextNode("Je suis celui qui est");
$text2 = $text->splitText(7);
echo $text->wholeText."<br />";
echo $text2->wholeText
Ce code retournera "je suis<br /> celui qui est"
La classe DOMAttr
modifierLa classe DOMAttr est comme son nom l'indique un attribut, qui est donc dépendant de la balise. Elle contient les propriétés suivantes :
$node->name // Nom de l'attribut
$node->value // Valeur de l'attribut
/* Nom de la balise qui contient l'attribut. La valeur retournée est un objet DOMElement */
$node->ownerElement
Seule la propriété value n'est pas en lecture seule, c'est à dire qu'il est possible d'avoir recours au code suivant :
$node->value = "maValeur";
Accéder à un nœud
modifierIl existe plusieurs modes de recherche du nœud. Il est par exemple possible de le sélectionner par son id :
$node = $doc->getElementById("sonId"); // Retourne un objet nœud
Sachant qu'il ne peut y avoir qu'un nœud possédant l'id recherché, la méthode retournera un objet nœud au lieu d'un objet nodeList.
Il est cependant possible de récupérer une liste de nœuds en les sélectionnant par leur nom de balise. Évidemment, seuls les nœuds Tag peuvent être sélectionnés.
$nodeList = $doc->getElementsByTagName("acronym"); //Sélectionne toutes les balises <acronym>
Comme nous l'avons déjà dit, il faut, pour récupérer un nœud particulier de la liste, utiliser la méthode suivante :
$acronym5 = $nodeList->item(4); //Sélectionne le 5e nœud de la liste;
XML
modifierCréation :
$xml = new DOMDocument("1.0", "ISO-8859-15"); $xml_node1 = $xml->createElement("Node_1"); $xml_node2 = $xml->createElement("Node_2", "Feuille"); $xml_node2->setAttribute("Attribut_1", "Valeur_1"); $xml_node1->appendChild($xml_node2); $xml->appendChild($xml_node1); $xml->save('Fichier_1.xml');
Lecture :
$xml = new DomDocument; $xml->load('Fichier_1.xml'); $fields = $xml->getElementsByTagName('fields'); foreach ($fields as $field) { /** @var DOMElement|DomText $fieldChild */ foreach ($field->childNodes as $fieldChild) { var_dump($fieldChild->nodeValue); } }
Références
modifier
JSON
Bibliothèque PHP-JSON
modifierLe format de données JavaScript Object Notation (JSON) peut être utilisé en PHP grâce à différentes fonctions natives depuis PHP 5.2.0.
Installation (pour PHP < 5.2)
modifierLinux
modifierapt-get install php5-json
Windows
modifier- Télécharger le fichier json-1.2.1.tgz sur https://pecl.php.net/package/json.
- Décompresser et compiler le code source en json.so.
- Le copier dans le dossier des extensions PHP.
- Dans le php.ini (ex : C:\Program Files (x86)\EasyPHP\binaries\php\php_runningversion\php.ini), ajouter :
extension=json.so
json_encode()
modifierCette fonction convertit un objet PHP en JSON exploitable en JavaScript[1]. Ex :
$tableau = array('colonne 1' => 'valeur 1', 'colonne 2' => 'valeur 2', 'colonne 3' => 'valeur 3');
echo json_encode($tableau);
{"colonne 1":"valeur 1","colonne 2":"valeur 2","colonne 3":"valeur 3"}
json_encode() sur une instance de classe n'en n'affiche que les attributs publics. Pour encoder les privés, il faut que la classe implémente JsonSerializable
en qui impose une méthode jsonSerialize()[2]. Ex :
public function jsonSerialize() {
return $this->array;
}
Options
modifier
Par défaut json_encode() échappe les caractères spéciaux non ASCII (ex : "é" devient "\u00e9"). Pour éviter cela on utilise l'option suivante :
$a = json_encode($monTableau, JSON_UNESCAPED_UNICODE);
Par ailleurs, les erreurs de json_encode() sont accessibles avec json_last_error()
ou json_last_error_msg()
. Ex :
$a = json_encode($monTableau);
if (json_last_error() === JSON_ERROR_INF_OR_NAN) {
$this->logger->error(json_last_error_msg());
$a = json_encode($monTableau, JSON_PARTIAL_OUTPUT_ON_ERROR);
}
Pour afficher dans un format plus lisible par un humain (indenté), utiliser JSON_PRETTY_PRINT
.
json_decode()
modifierConvertit une chaine de caractères JSON en :
- Si aucun paramètre 2 n'est passé, un objet PHP dont chaque attribut correspond à une clé du tableau.
- Si le paramètre 2 vaut "true", un tableau associatif[3].
La gestion des erreurs est semblable à celle de json_encode()
.
PEAR Services_JSON
modifierLe framework PEAR possède aussi un package Services_JSON contenant des .php avec des exemples, à télécharger en tant que JSON.tar.gz sur http://pear.php.net/pepr/pepr-proposal-show.php?id=198.
Il requiert PHPUnit.phpTélécharger.
La classe Services_JSON de JSON.php peut s'utiliser comme décrit dans Test-JSON.php.
Références
modifier
MING
Conceptions d'animations pour pages web
modifierCréer les animations en Flash (.swf) se fait par des logiciels payants, cependant la librairie MING écrite en C, et utilisable en PHP, C++, Python et Ruby, permet de les générer gratuitement (mais pas d'éditer les .swf existant).
<?php
// Dessine deux boutons interactifs
function BoutonCarré($r, $g, $b)
{
$s = new SWFShape();
$s->setRightFill($s->addFill($r, $g, $b));
$s->movePenTo(-20,-20);
$s->drawLineTo(20,-20);
$s->drawLineTo(20,20);
$s->drawLineTo(-20,20);
$s->drawLineTo(-20,-20);
return $s;
}
function BoutonRond($r, $g, $b)
{
$s = new SWFShape();
$s->setRightFill($s->addFill($r, $g, $b));
$s->movePenTo(20, 20);
$s->drawCircle(20);
return $s;
}
$carré = new SWFButton();
$carré->setUp(BoutonCarré(0xff, 0, 0));
$carré->setOver(BoutonCarré(0, 0xff, 0));
$carré->setDown(BoutonCarré(0, 0, 0xff));
$carré->setHit(BoutonCarré(0, 0, 0));
$rond = new SWFButton();
$rond->setUp(BoutonRond(0xff, 0, 0));
$rond->setOver(BoutonRond(0, 0xff, 0));
$rond->setDown(BoutonRond(0, 0, 0xff));
$rond->setHit(BoutonRond(0, 0, 0));
$m = new SWFMovie();
$m->setDimension(320, 240);
$m->setBackground(0xff, 0xff, 0xff);
$i = $m->add($carré);
$i->moveTo(50, 50);
$i = $m->add($rond);
$i->moveTo(100, 50);
header('Content-type: application/x-shockwave-flash');
$m->output();
Depuis février 2016, Firefox bloque tous les contenus Flash par défaut, pour des raisons de sécurité. Cette technologie est donc amenée à être remplacée par JavaScript.
Liens externes
modifier- Fonctions
- Tutoriel JournalDuNet
- Tutoriel GazbMing
- Tutoriel developpez.com
- Tutoriel supportduweb.com
- Bibliothèque de scripts
- Perl graphics programming, Shawn P. Wallace, 2002
SPL
La Standard PHP Library (SPL) est une bibliothèque intégrée depuis PHP 5[1].
Elle comprend les classes suivantes :
Structures de données
modifier- SplDoublyLinkedList
- SplStack
- SplQueue
- SplHeap
- SplMaxHeap
- SplMinHeap
- SplPriorityQueue
- SplFixedArray
- SplObjectStorage
Itérateurs
modifierInterfaces
modifierExceptions
modifierRéférences
modifier
ADOdb
Fonctionnalités
modifierCette librairie permet comme PEAR DB de supporter différents types de bases de données (MySQL, Oracle ...). Il est ainsi possible par le biais d'un fichier de configuration de modifier le type de base de données sans que cela n'aie d'impact dans le code de l'application en elle-même.
Il s'agit ici, comme pour les autres produits décrits plus haut, d'exploiter les possibilités de programmation orienté objet en ce sens qu'elles optimisent la programmation, la rendant plus efficiente.
Reformulons l'intérêt d'utilisation de cette classe. Nous savons par exemple que le langage Java permet "d'attaquer" différents types de bases de données sans qu'une ligne de programmation ne soit modifiée, déportant cette disparité en concentrant notre attention sur une seule ligne de configuration : la déclaration du driver de base de données.
C'est un peu ce que veut faire pour PHP ADODB, tout comme PEAR DB, qui furent des produits concurrents, dans le but de tester un développement PHP.
Installation
modifierLes fichiers se téléchargent sur http://adodb.sourceforge.net/#download.
Exemple
modifierExemple sur une base Access :
include('adodb.inc.php'); # charge le code de ADOdb
$conn = &ADONewConnection('access'); # crée une connexion
$conn->PConnect('northwind'); # se connecte à MS-Access, northwind DSN
mais ensuite, que vous créez, puis exploitez en base réelle sur Postgres ou encore MySQL, les modifications sur les lignes de codes pour une version en exploitation seront alors des modifications de type paramétrage dans l'appel de la méthode d’accès, mais non point sur chaque ligne d'instruction, qu'il s'agisse d'un accès en lecture, en mise à jour, etc.
include('adodb.inc.php');
$conn = &ADONewConnection('mysql');
$conn->PConnect('localhost','userid','','agora');# se connecte à MySQL, agora db
Il existe aussi des méthodes permettant de passer rapidement en conversion HTML après exploitation en séquence de tuples de la base de données cible, ou encore de créer rapidement un fichier CSV, ce qui est très prisé en bureautique (pour les logiciel de type "Office").
Voir aussi
modifierUn excellent tutoriel se trouve à l'adresse suivante :
- chez phplens : Tutorial ADODB français
- chez phpfreaks : Tutorial ADODB anglais
Une courte description en anglais au database journal : ADODB class library
DOMPDF
DOMPDF permet de générer des fichiers PDF à partir d'une page HTML. C'est une alternative à HTML2PDF, qui lui est basé sur TCPDF.
Exemples
modifier
FPDF
FPDF est d'origine française, il est gratuit, et facile d'utilisation. Cette librairie permet d'exploiter les possibilités de production de documents en PDF à l'aide de PHP. Elle se télécharge sur http://www.fpdf.org/.
PHPExcel
Introduction
modifierCette bibliothèque open source permet de lire et d'écrire dans des tableurs, XLS et XLSX. Mais il peut aussi générer des CSV, des PDF, et des HTML[1].
Elle comprend toute sorte de fonctions de manipulations de tableurs, telles que le changement de couleur des champs, l'ajout de graphiques et de filtres, la protection de feuilles...
Il faut la télécharger sur https://github.com/PHPOffice/PHPExcel :
composer require phpoffice/phpexcel
Pour l'utiliser, l'inclure en début de fichiers :
include 'PHPExcel/Classes/PHPExcel.php';
On appellera ses instances "$objPHPExcel", qui représentent les classeurs.
Création
modifierPour créer un fichier à partir de rien, soit CreateXLS.php un fichier situé à côté du répertoire de la bibliothèque nommé PHPExcel, brut de téléchargement (on appelle la feuille avec un nom très court car elle est souvent utilisée, "$s" pour "sheet") :
$objPHPExcel = new PHPExcel;
$s = $objPHPExcel->getActiveSheet();
$s->setCellValue('A1','Hello');
$s->setCellValueByColumnAndRow(2, 1, 'World!');
$writer = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
// Option 1 : fichier .xlsx apparaissant à côté du .php
$writer->save('./HelloWorld1.xlsx');
// Option 2 : fichier à télécharger par le navigateur
header('Content-Disposition: attachment;filename="HelloWorld2.xlsx"');
$writer->save('php://output');
Ouverture
modifierPour ouvrir et lire un fichier existant :
$objReader = PHPExcel_IOFactory::createReader('Excel2007');
$objPHPExcel = $objReader->load('./HelloWorld1.xlsx');
print $objPHPExcel->getActiveSheet()->getCell('A1')->getValue();
Conversion
modifierConversion d'un XLSX en CSV :
$xlsx = PHPExcel_IOFactory::load('./HelloWorld1.xlsx');
$writer = PHPExcel_IOFactory::createWriter($xlsx, 'CSV');
$writer->setDelimiter(";");
$writer->setEnclosure("");
$writer->save('./HelloWorld1.csv');
Conversion d'un CSV en XLSX :
$objReader = PHPExcel_IOFactory::createReader('CSV');
$objReader->setDelimiter(';');
$objReader->setEnclosure(' ');
$objPHPExcel = $objReader->load('./HelloWorld1.csv');
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->save('./HelloWorld3.xlsx');
Modifications
modifierLes propriétés des cellules sont présentées sous forme de tableaux multidimensionnels :
$style = array(
'borders' => array(
'outline' => array(
'style' => PHPExcel_Style_Border::BORDER_THICK,
'color' => array('argb' => 'FFFF0000'),
),
),
'font' => array(
'bold' => true,
'name' => 'Tahoma',
'size' => 10,
'color' => array('rgb' => 'FF0000'),
),
'fill' => array(
'type' => PHPExcel_Style_Fill::FILL_SOLID,
'color' => array('rgb' => 'C3C3E5')
),
'alignment' => array(
'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
'vertical' => PHPExcel_Style_Alignment::VERTICAL_CENTER,
'wrap' => true // retour à la ligne automatique
)
);
// Ajout du style ci-dessus en feuille 2 d'un nouveau fichier
$objPHPExcel = new PHPExcel;
$writer = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$s = $objPHPExcel->createSheet();
$s->setTitle('Feuille style');
$s = $objPHPExcel->setActiveSheetIndex($objPHPExcel->getSheetCount()-1);
$s->setCellValue('A1','Hello style');
$s->getStyle('A1')->applyFromArray($style);
$s->getStyle('B1')->getNumberFormat()->setFormatCode( PHPExcel_Style_NumberFormat::FORMAT_TEXT );
$s->setCellValue('B1','9999999999999999999'); // Sans le format texte les nombres de plus de 15 chiffres sont arrondis
$writer->save('./HelloWorld4.xlsx');
On peut aussi :
// Récupérer la dernière ligne d'une feuille
$ligne = $s->getHighestRow();
// Insérer une ligne
$s->insertNewRowBefore($ligne + 1, 1);
Références
modifierVoir aussi
modifier- https://github.com/PHPOffice/PhpSpreadsheet (.xlsx et .ods)
- https://github.com/PHPOffice/PHPPresentation (PowerPoint)
PHPMailer
PHPMailer est une bibliothèque open source[1] pour envoyer des emails plus rapidement qu'à partir de la commande mail()
.
Installation
modifierTélécharger sur GitHub ou bien ajouter à composer.json : "phpmailer/phpmailer": "~5.2"
.
Utilisation
modifier// Pour la v5.0.0 (2009)
require_once('PHPMailer/class.phpmailer.php');
// Pour la v5.2.14 (2016)
require('PHPMailer/PHPMailerAutoload.php');
Exemple
modifierExemple de base :
$mail = new PHPMailer();
$mail->Subject = 'Hello World!';
$mail->SetFrom('expediteur@mon_domaine.com');
$mail->AddAddress('destinataire1@son_domaine.com');
$mail->MsgHTML('Corps de l\'email');
if (!$mail->Send()) {
echo 'Erreur : ' . $mail->ErrorInfo;
} else {
echo 'Message envoyé !';
}
Bien sûr, on peut ensuite ajouter en une ligne une pièce jointe, une copie cachée, une signature DKIM...
Références
modifier
PHPWord
Introduction
modifierCette bibliothèque open source permet de lire et d'écrire des documents de traitement de texte.
Il faut la télécharger sur https://github.com/PHPOffice/PHPWord.
composer require phpoffice/phpword
RabbitMQ
RabbitMQ est un logiciel de messages en protocole AMQP. Il permet donc à des processus de produire des messages JSON dans des files d'attente pour que d'autres les consomme ensuite[1].
Installation
modifierClient PHP
modifiercomposer require php-amqplib/php-amqplib
Serveur
modifierL'installation du serveur est multi-plateforme. Sous Linux[2] :
apt-get install rabbitmq-server
Test de fonctionnement :
telnet localhost 5672
Site de gestion
modifierUne interface graphique existe pour lire et manipuler les messages manuellement, c'est le management plugin[3]. Pour l'activer :
/usr/sbin/rabbitmq-plugins enable rabbitmq_management
Test de fonctionnement depuis le serveur :
curl localhost:15672
Depuis le client : http://mon_serveur:15672
On la trouve aussi sur Docker[4].
Pour trouver le fichier de configuration :
cat /usr/sbin/rabbitmq-server | grep RABBITMQ_ENV
Connexion
modifierLes identifiants par défaut de RabbitMQ dépendent des versions. On trouve soit le login / mot de passe "user / password", soit "guest / guest". Pour tester :
curl -i -u guest:guest http://localhost:15672/api/whoami
Si cela ne fonctionne pas, configurer le serveur avec rabbitmqctl
. Exemple sous Linux :
/usr/sbin/rabbitmqctl add_user userDev mon_mot_de_passe
/usr/sbin/rabbitmqctl set_permissions -p / userDev '.*' '.*' '.*'
/usr/sbin/rabbitmqctl set_user_tags userDev management
/usr/sbin/rabbitmqctl list_users
Connexion PHP
modifier$connection = new AMQPStreamConnection($host, $port, $login, $password);
...
$connection->close();
Création de queue et routage
modifierPour créer une queue simple prête à recevoir des messages :
$this->rabbitMqConnection->getChannel()->queue_declare('Wikibooks.Queue1', false, false, false, false);
Exchange
modifierImage externe | |
---|---|
Schéma des différents types de routage RabbitMQ sur le site : (en) Jyoti Sachdeva, « Getting Started With RabbitMQ: Python », |
Une autre manière de poster des messages est en passant par un exchange. On en distingue plusieurs types[5] :
- direct : une seule queue recevra le message (patron de conception producteur/consommateur).
- fanout : toutes les queues liée à l’exchange recevront le message (patron de conception producteur/abonné).
- topic : les queues de l’exchange inscrites aux sujets concernés recevront le message (selon un motif dans la "routing key" où "*" représente un seul mot séparé par un point, et "#" au moins un)[6].
- headers : routage par en-tête de message plutôt que par "routing key".
Dans cet exemple, on rattache la queue à un exchange "Bus" :
$this->rabbitMqConnection->getChannel()->exchange_declare('Bus', 'fanout', false, true, false);
$this->rabbitMqConnection->getChannel()->queue_declare('Wikibooks.Queue2', false, true, false, false);
$this->rabbitMqConnection->getChannel()->queue_bind('Wikibooks.Queue2', 'Bus');
$this->rabbitMqConnection->getChannel()->queue_declare('Wikibooks.Queue3', false, true, false, false);
$this->rabbitMqConnection->getChannel()->queue_bind('Wikibooks.Queue3', 'Bus');
Exemple de topic : on ne publie pas dans la queue mais dans l’exchange qui leur routera ensuite le message.
$this->rabbitMqConnection->getChannel()->exchange_declare('Topic_bus', 'topic', false, false, false);
$this->rabbitMqConnection->getChannel()->queue_declare('Wikibooks.Queue4', false, true, false, false);
$this->rabbitMqConnection->getChannel()->queue_bind('Wikibooks.Queue4', 'Topic_bus');
$this->rabbitMqConnection->getChannel()->queue_declare('Wikibooks.Queue5', false, true, false, false);
$this->rabbitMqConnection->getChannel()->queue_bind('Wikibooks.Queue5', 'Topic_bus');
QoS
modifierPour demander à RabbitMQ de ne pas surcharger les consommateurs d'une queue en leur répartissant les messages que s'ils ont terminé de traiter le précédent :
$this->rabbitMqConnection->getChannel()->basic_qos(null, 1, null);
DLX
modifierLe mode DLX (Dead Letter Exchanges) permet de transférer un message d'une queue dans une autre après un certain temps[7].
Production
modifier $amqpMessage = new AMQPMessage(json_encode('Hello World!'),
['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]
);
$this->rabbitMqConnection->getChannel()->basic_publish($amqpMessage, 'Bus', 'Wikibooks.Queue1');
Consommation
modifierPar défaut on consomme un seul message de la queue. Pour tous les lire un par un, utiliser basic_ack() après basic_consume().
$this->rabbitMqConnection->getChannel()->basic_consume(
'Wikibooks.Queue1',
gethostname() . '#' . rand(1, 9999),
false,
false,
false,
false,
[$this, 'consumeCallback']
);
while (count($this->rabbitMqConnection->getChannel()->callbacks)) {
$this->rabbitMqConnection->getChannel()->wait();
}
public function consumeCallback(?AMQPMessage $msg)
{
if (empty($msg)) {
return null;
}
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
var_dump(json_decode($msg->getBody()));
}
En mode "topic", on peut remplacer 'Wikibooks.Queue1' par 'Wikibooks.*' pour récupérer toutes les queues.
Références
modifier- ↑ https://www.rabbitmq.com/tutorials/tutorial-one-php.html
- ↑ https://www.rabbitmq.com/install-debian.html
- ↑ https://www.rabbitmq.com/management.html
- ↑ https://hub.docker.com/_/rabbitmq
- ↑ https://www.rabbitmq.com/tutorials/tutorial-three-php.html
- ↑ https://www.rabbitmq.com/tutorials/tutorial-five-php.html
- ↑ https://www.rabbitmq.com/dlx.html
Cadriciels
Les cadriciels, plus connus sous le terme framework, sont un ensemble de bibliothèques, scripts destinés aux projets professionnels et au travail en équipe.
Issu d'une longue réflexion et de maturation, le cadriciel permet au développeur de s'affranchir des contingences de sécurité, ergonomie (bouton, habillage) afin de ne se concentrer que sur les fonctionnalités de son application.
Sur le modèle des cadriciels Java ou Ruby on rails, certains cadriciels PHP se démarquent comme CakePHP, PRADO et Symfony.
Modèle-vue-contrôleur
modifierIls se basent sur le plus utilisé des patrons de conception : le modèle-vue-contrôleur qui sépare le modèle de données, l'interface utilisateur et les traitements, ce qui donne trois parties fondamentales dans l'application finale : le modèle, la vue et le contrôleur.
- Le modèle ne s'occupe que du traitements des données, lire depuis une base de données, insérer des lignes dans une base, vérifier que les données sont bien formatées (validation).
- La vue correspond à tout ce qui concerne l'affichage pour l'utilisateur. Cela ne contient généralement que des modèles (template) de pages avec la logique de présentation. C'est une caractéristique forte de MVC, seule cette partie est à mettre-à-jour pour changer l'apparence de votre application.
- Le contrôleur prend en charge le déroulement du programme. La liste des actions sera dans le contrôleur.
Le programmeur peut être dérouté par la séparation en morceaux imposée mais le premier objectif de MVC est la maintenance à long terme.
Voir aussi
modifier- Liste de frameworks PHP sur Wikipédia
CakePHP
Le cadriciel CakePHP
modifierCakePHP est un framework de type Rapid Application Developpement (RAD) utilisant le motif de conception modèle-vue-contrôleur.
Le moyen d'appréhender l'intérêt d'un cadriciel est par l'exemple. Après l'installation nous l'emploierons pour créer un formulaire simple d'enregistrement des données d'un utilisateur (nom, prénom, email, mot de passe) stockée dans une table de base de données. Ensuite, ce formulaire sera amélioré par l'ajout de vérifications et de validation des données. Pour conclure, l'on ajoutera une page d'identification.
Ce chapitre va décrire les grandes étapes d'installation puis de configuration du cadriciel CakePHP dans un but pédagogique. Pour une installation de production la référence reste le manuel de l'éditeur[1].
Prérequis
modifierCoté serveur l'installation nécessite un serveur HTTP comme Apache avec le module de réécriture d'URL activé (rewrite), le langage PHP 5 avec la bibliothèque des sessions et un système de base de données PostgreSQL ou MySQL.
Connaissances
modifier- PHP 5 objet : Programmation PHP/La programmation orientée objet
- Avoir complété : Programmation_PHP/Exemples/Formulaire
- Être au moins débutant en programmation SQL
- Utilisation courante d'une base de donnée relationnelle : Découverte de MySQL, PostgreSQL et Oracle
Outils
modifierInstallation
modifierBase de données
modifierCréez une nouvelle base de données cake_monapp
pour l'utilisateur propriétaire cakedev
:
MySQL :
CREATE DATABASE cake_monapp;
GRANT ALL PRIVILEGES ON cake_monapp.* TO 'cakedev'@'localhost' IDENTIFIED BY 'plop';
PostgreSQL :
CREATE USER cakedev WITH PASSWORD 'plop';
CREATE DATABASE cake_monapp OWNER cakedev;
GRANT ALL PRIVILEGES ON DATABASE cake_monapp TO cakedev;
Dans /cake/app/config/ renommez database.php.default en database.php puis modifiez y les paramètres de connexion à la base de donnée (nom de serveur, nom de base, utilisateur, mot de passe).
/cake/app/config/database.php
:
Pour MySQL :
var $default = array('driver' => 'mysql',
'connect' => 'mysql_connect',
'host' => 'localhost',
'login' => 'cakedev',
'password' => 'plop',
'database' => 'cake_monapp',
'prefix' => '');
Pour PostgreSQL :
var $default = array('driver' => 'postgres',
'connect' => 'pg_connect',
'host' => 'localhost',
'login' => 'cakedev',
'password' => 'plop',
'database' => 'cake_monapp',
'prefix' => '');
Configurations
modifierToujours dans /cake_xxx/app/config/ modifiez core.php en changeant la constante CAKE_SESSION_STRING par une chaîne aléatoire, avec par exemple en ligne de commande :
perl -e '@c=("A".."Z","a".."z",0..9);print join("",@c[map{rand @c}(1..36)]),"\n"'
Rendre /cake_xxx/app/tmp accessible en écriture.
$ chmod -R a+x tmp
Vérifiez que le module de réécriture d'URL est activé pour le serveur web Apache :
Vous créerez un nouveau projet qui contiendra la copie locale de l'arborescence sous app/.
Notes et références
modifier
PEAR
Qu'est-ce que PEAR ?
modifierPEAR, acronyme de PHP Extension and Application Repository, est un framework PHP libre issu d'un groupe de développeurs qui proposent des extensions PHP en garantissant un code de qualité. La liste complète des extensions est téléchargeable gratuitement sur le site officiel[1].
Pour l'installer le framework, il y a quatre solutions :
- Télécharger les packages un par un sur http://pear.php.net/packages.php.
- Télécharger le gestionnaire sur https://pear.php.net/go-pear, et le lancer (ex : http://localhost/Frameworks/go-pear.php). Avec l'installation par défaut, les fichiers sont téléchargés dans un sous-dossier "PEAR".
- La commande
pear install Nom_du_package
. - La commande
php pyrus.phar install pear/Nom_du_package
.
DB
modifierL'extension PEAR DB fournit une gamme de fonctions de gestion de base de données permettant d'utiliser le même code quel que soit la base de données. Cela permet, si vous décidez de changer de BDD de ne pas être obligé de modifier de nouveau tous vos scripts. Un simple changement de variable vous permettra de passer de MySQL à Oracle par exemple.
Elle n'est plus maintenue depuis 2015[2], ce qui offre un inconvénient par rapport à PEAR MDB2.
Connexion à la base
modifierSe connecter à une base de données revêt la syntaxe suivante :
require_once('DB.php'); // Indispensable
$dbType = "mysql";
$host = "127.0.0.1";
$account = "Mon_Compte";
$pass = "Mon_Mot_de_passe";
$dbName = "Ma_Base";
$dsn = "$dbType://$account:$pass@$host/$dbName";
$db = DB::connect($dsn);
if (PEAR::isError($db)) {
echo "Erreur : ".$db->getMessage();
}
Il est également possible de remplacer la chaîne de caractères par un tableau contenant vos informations :
$dsn = array(
'phptype' => 'mysql',
'username' => 'myAccount',
'password' => '****',
'hostspec' => '127.0.0.1',
'database' => 'tests',
);
Vous êtes donc connectés à votre base de données. Il s'agit maintenant d'effectuer des opérations avec celle-ci.
Fermeture de la connexion
modifierIl est important de fermer votre connexion une fois vos opérations terminées pour augmenter la sécurité de votre code, réduisant les risques d'atteinte à vos données par un individu mal intentionné. Voici donc le code détruisant la connexion :
$db->disconnect();
Envoyer une requête
modifierUne fois connecté, vous allez pouvoir envoyer des requêtes à votre BDD comme suit :
$query = "SELECT * FROM table WHERE id=5";
$rsc = $db->query($query);
Récupérer des informations
modifierComme avec n'importe quelle base de données, vous aurez à récupérer le résultat de votre requête. Voici une fonction équivalente de mysql_fetch_array()
:
$query = "SELECT * FROM table WHERE id=5";
$rsc = $db->query($query);
if ( DB::isError($rsc) )
die($rsc->getMessage());
while($result = $rsc->fetchRow(DB_FETCHMODE_ASSOC)) {
echo $result['id']."\n";
}
MDB2
modifierCette bibliothèque issue de la précédente, offre une API de gestion des SGBD. Elle se télécharge sur http://pear.php.net/package/MDB2.
Spreadsheet_Excel_Writer
modifierCette bibliothèque fournit des classes de manipulation de fichier .xls sont[3][4].
Pour l'installer, il faut simplement télécharger le paquetage Spreadsheet_Excel_Writer, qui utilise OLE et Getopt.
Pour l'utiliser :
include "Spreadsheet/Excel/Writer.php;
Limites
modifierCette bibliothèque n'est plus maintenue depuis 2012[5], on lui préfèrera donc PHPExcel[6][7], qui gère en plus l'auto-ajustement, les filtres, et les XLSX (plus de limite de 65 536 lignes par feuille).
D'autant plus qu'elle remplit rapidement les logs avec des centaines de warnings : Object of class Spreadsheet_Excel_Writer_Format could not be converted to int
.
phpDocumentor
modifierCe générateur de documentation analyse aussi le code. Ainsi, on peut décrire dans l'annotation qui précède une méthode, les types de ses arguments ou de son résultat[8] :
/**
* @param bool $condition
*
* @return String|null
*/
Une méthode qui hérite d'une autre peut aussi hériter de sa doc :
@inheritdoc
PECL
modifierPECL (prononcé "pickle", pour PHP Extension Community Library), est un gestionnaire de paquets. Exemples :
sudo pecl install memcached
sudo pecl install redis
sudo pecl install apcu
sudo pecl install xdebug
sudo pecl install amqp
sudo pecl install igbinary
sudo pecl install imagick
Pour désinstaller :
sudo pecl uninstall memcached
Sur un serveur avec plusieurs versions de PHP actives, il peut être nécessaire de préciser sur laquelle installer le paquet au préalable[9]. Sinon l'erreur suivante survient : Unable to load dynamic library 'memcached.so'. Plusieurs solutions :
export PATH="/usr/local/opt/php@7.2/bin:$PATH"
export PATH="/usr/local/opt/php@7.2/sbin:$PATH"
sudo pecl uninstall memcached; sudo pecl install memcached
ou
sudo phpdismod -v 7.2 memcached; sudo phpenmod -v 7.2 memcached
Pour vérifier :
php7.0 -r "phpinfo();" |grep -i memcache
php7.1 -r "phpinfo();" |grep -i memcache
php7.2 -r "phpinfo();" |grep -i memcache
php7.3 -r "phpinfo();" |grep -i memcache
php7.4 -r "phpinfo();" |grep -i memcache
Références
modifier
Symfony
- Pour plus de détails voir : Programmation PHP avec Symfony.
Présentation
modifierSymfony (parfois abrégé SF) est un cadriciel MVC libre écrit en PHP (> 5). En tant que framework, il facilite et accélère le développement de sites et d'applications Internet et Intranet. Il propose en particulier :
- Une séparation du code en trois couches, selon le modèle MVC, pour une plus grande maintenabilité et évolutivité.
- Des performances optimisées et un système de cache pour garantir des temps de réponse optimums.
- Le support de l'Ajax.
- Une gestion des URL parlantes (liens permanents), qui permet de formater l'URL d'une page indépendamment de sa position dans l'arborescence fonctionnelle.
- Un système de configuration en cascade qui utilise de façon extensive le langage YAML.
- Un générateur de back-office et un "démarreur de module" (scaffolding).
- Un support de l'I18N - Symfony est nativement multi-langue.
- Une architecture extensible, permettant la création et l'utilisation de composants, par exemple un mailer ou un gestionnaire de fichiers .css et .js (minification).
- Des bundles :
- Un templating simple, basé sur PHP et des jeux de "helpers", ou fonctions additionnelles pour les gabarits... Comme alternative au PHP, on peut aussi utiliser le moteur de templates Twig dont la syntaxe est plus simples.
- Une couche de mapping objet-relationnel (ORM) et une couche d'abstraction de données (cf. Doctrine et son langage DQL[1]).
Utilisations
modifierPlusieurs autres projets notables utilisent Symfony, parmi lesquels :
- https://github.com/drupal/drupal : le système de gestion de contenu (CMS) Drupal.
- https://github.com/joomla/joomla-cms : le CMS Joomla.
- https://github.com/sulu/sulu : le CMS Sulu.
- https://github.com/Sylius/Sylius : Sylius, un CMS d'e-commerce.
- https://github.com/x-tools/xtools : Xtools, un compteur d'éditions des wikis.
Différences entre les versions
modifierDepuis la version 4, des pages récapitulant les nouvelles fonctionnalités sont mises à disposition :
- https://symfony.com/blog/symfony-4-1-curated-new-features
- https://symfony.com/blog/symfony-4-2-curated-new-features
- https://symfony.com/blog/symfony-4-3-curated-new-features
- https://symfony.com/blog/symfony-4-4-curated-new-features
- https://symfony.com/blog/symfony-5-0-curated-new-features
- https://symfony.com/blog/symfony-5-1-curated-new-features
- https://symfony.com/blog/symfony-5-2-curated-new-features
- https://symfony.com/blog/symfony-5-3-curated-new-features
- https://symfony.com/blog/symfony-6-1-curated-new-features
- https://symfony.com/blog/symfony-6-2-curated-new-features
- https://symfony.com/blog/symfony-6-3-curated-new-features
Créer un projet
modifierVous trouverez sur la page dédiée Symfony 4 comment installer cette version sortie en 2017, et Symfony 3 ici. La v5 s'installe comme la v6, mais pour migrer de la v5 à la v6 il faut vérifier plusieurs choses.
Pour créer un nouveau projet sous Symfony 6, tapez la commande :
composer create-project "symfony/skeleton:^6" mon_projet
ou avec la commande "symfony" :
wget https://get.symfony.com/cli/installer -O - | bash symfony new mon_projet
Cette commande a pour effet la création d'un dossier contenant les bases du site web à développer.
Lancer le projet
modifierOn entend par cette expression le lancement d'un serveur web local pour le développement de l'application et le choix d'un hébergeur pour la déployer (autrement dit "la mettre en production").
Serveur web de développement
modifierSymfony intègre un serveur web local qu'on peut lancer avec la commande (se placer dans le répertoire du projet auparavant) :
$ symfony server:start -d
En passant open:local
en argument de la commande symfony
, le projet s'ouvre dans un navigateur :
$ symfony open:local
Ou bien en utilisant le serveur web intégré à php
$ php -S localhost:8000 -t public
Serveur web de production
modifierPour le déploiement dans le monde "réel", il faut choisir un hébergeur web sur internet supportant PHP (nous l’appellerons "serveur web distant" pour le distinguer du précédent). Voici quelques exemples :
- https://www.lws.fr/hebergement_web.php
- https://www.hostinger.fr/hebergeur-web
- et surtout... https://symfony.com/cloud/
Autrement il est aussi possible d'installer un deuxième serveur web (autre que celui intégré à Symfony) sur sa machine pour se rendre compte du résultat final. Par exemple... Apache qui est très répandu chez les hébergeurs profesionnels. Il faudra alors ajouter un vhost et un nom de domaine dédiés au site Symfony[2][3]. Pour le test, le domaine peut juste figurer dans /etc/hosts
.
Le nom de domaine du site doit absolument rediriger vers le dossier /public. En effet, si on cherche à utiliser le site Symfony dans le sous-répertoire "public" d'un autre site, la page d'accueil s'affichera mais le routing ne fonctionnera pas.
Configurer le projet
modifierParamètres dev et prod
modifierLes différences de configuration entre le site de développement et celui de production (par exemple les mots de passe) peuvent être définies de deux façons :
- Dans le dossier
config/packages
. config.yml contient la configuration commune aux sites, config_dev.yml celle de développement et config_prod.yml celle de production. - Via le composant Symfony/Dotenv (abordé au chapitre suivant).
Par exemple, on constate l'absence de la barre de débogage (web_profiler) par défaut en prod. Une bonne pratique serait d'ajouter au config_dev.yml :
web_profiler:
toolbar: true
intercept_redirects: false
twig:
cache: false
# Pour voir tous les logs dans la console shell (sans paramètre -vvv)
monolog:
handlers:
console:
type: console
process_psr_3_messages: false
channels: ['!event', '!doctrine', '!console']
verbosity_levels:
VERBOSITY_NORMAL: DEBUG
Les fichiers .yml contenant les variables globales sont dans app\config\.
Par exemple en SF2 et 3, le mot de passe et l'adresse de la base de données sont modifiables en éditant parameters.yml
(non versionné et créé à partir du parameters.yml.dist
). L'environnement de test passe par web/app_dev.php, et le mode debug y est alors activé par la ligne Debug::enable();
(testable avec %kernel.debug% = 1
).
Depuis SF4, il faut utiliser un fichier .env non versionné à la racine du projet, dont les lignes sont injectées ensuite dans les .yaml avec la syntaxe : '%env(APP_SECRET)%'
. Le mode debug est activé avec APP_DEBUG=1
dans ce fichier .env.
Les variables d'environnement du système d'exploitation peuvent remplacer celles des .env.
Références
modifier- « Wiki officiel »
- « Tutoriel openclassrooms.com »
- « Tutoriel developpez.com »
- (en) « Symfony 3.1 cookbook » : livre officiel de 500 pages
- (en) « Charming Development in Symfony 5 » (texte et vidéo)
Voir aussi
modifier- #symfony : canal IRC (#symfony sur Freenode)
- #symfony-fr : canal IRC francophone (#symfony-fr sur Freenode)
- https://sonata-project.org/get-started : un CMS basé sur Symfony
PHPUnit
PHPUnit est utilisé dans un certain nombre de frameworks connus pour réaliser des tests unitaires. Sa documentation en anglais est disponible au format PDF[1].
Installation
modifierVia composer
modifier composer require --dev phpunit/phpunit ^8
Via wget
modifierUne fois le .phar téléchargé depuis le site officiel[2], le copier dans le dossier où il sera toujours exécuté. Exemple :
Unix-like
modifierwget https://phar.phpunit.de/phpunit-8.phar
mv phpunit.phar /usr/local/bin/phpunit
chmod +x phpunit.phar
Windows
modifier- Ajouter à la variable d'environnement
PATH
, le dossier où se trouve le fichier (ex :;C:\bin
). - Créer un fichier exécutable à côté (ex :
C:\bin\phpunit.cmd
) contenant le code :@php "%~dp0phpunit.phar" %*
.
Par ailleurs, le code source de cet exécutable est sur GitHub[3].
Test
modifierTest de l'installation :
phpunit --version
Utilisation
modifierIl faut indiquer au programme les dossiers contenant des tests dans le fichier phpunit.xml.dist. Exemple sur Symfony[4] :
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/9.0/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
<server name="KERNEL_CLASS" value="AppKernel" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory suffix=".php">./tests</directory>
<exclude>tests/FunctionalTests/*</exclude>
</testsuite>
</testsuites>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
</phpunit>
Si on a plusieurs dossiers à exclure, mieux vaut sélectionner plutôt ceux à traiter :
<directory suffix=".php">tests/UnitTests</directory>
<directory suffix=".php">tests/FunctionalTests/QuickTests</directory>
Ensuite, pour tester tous les fichiers du dossier /test et ignorer ceux de /src, il suffit de lancer :
./bin/phpunit
Pour exclure un seul fichier ou une seule méthode des tests, lui mettre $this->markTestIncomplete('This test has to be fixed.');
Options
modifierCe .xml donne des options par défaut qui peuvent être modifiées dans les commandes. Par exemple stopOnFailure="true"
dans la balise <phpunit>
peut être par défaut, et phpunit --stop-on-failure
seulement pour ce lancement.
Choisir les tests à lancer
modifierSi les tests sont longs et qu'on ne travaille que sur un seul fichier, une seule classe ou une seule méthode, on peut demander à ne tester qu'elle en précisant son nom (ce qui évite d'afficher des dumps que l'on ne souhaite pas voir lors des autres tests) :
bin/phpunit tests/MaClasseTest.php
bin/phpunit --filter=MaClasseTest
bin/phpunit --filter=MaMethodeTest
Si une méthode dépend d'une autre, on ne n'appeler que ces deux-là (peu importe l'ordre) :
bin/phpunit --filter='test1|test2'
Détails de chaque test
modifierPour afficher les noms des tests et le temps qu'ils prennent, utiliser : --testdox
Rapports
modifierOutre les résultats des tests, on peut avoir besoin de mesurer et suivre leur complétude, via le taux de couverture de code. PhpUnit permet d'afficher ce taux en installant Xdebug et en activant son option xdebug.mode = coverage
.
Le calcul du taux de couverture peut ensuite être obtenu avec :
bin/phpunit --coverage-text
Certains fichiers ne peuvent en aucun cas être testés, et doivent donc être exclus du calcul du taux de couverture dans phpunit.xml.dist. Par exemple pour les migrations et fixtures :
<exclude>
<directory suffix=".php">src/Migrations/</directory>
<file>src/DataFixtures/AppFixtures.php</file>
</exclude>
Dans un fichier
modifierLe résultat des tests peut être sauvegardé dans un fichier de rapport XML avec l'option --log-junit phpunit.logfile.xml
.
L'ajout de l'option --coverage-html reports/
générera un rapport du taux de couverture des tests en HTML (mais d'autres formats sont disponibles tels que l'XML ou le PHP), dans le dossier "reports" (créé automatiquement).
Exemple récupérable par l'outil d'analyse de code SonarQube :
phpunit --coverage-clover phpunit.coverage.xml --log-junit phpunit.logfile.xml
Écriture des tests
modifieruse PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class SuiteDeTests1 extends TestCase
{
/** @var MockObject */
private $monMock1;
protected function setUp(): void
{
// Création des mocks et instanciation de la classe à tester...
$this->monMock1 = $this->getMockBuilder(maMockInterface::class)->getMock();
}
protected function tearDown(): void
{
// Libération des ressources après les tests...
}
public static function setUpBeforeClass(): void
{
// Pour réinitialiser une connexion déclarée dans setUp()
}
public static function tearDownAfterClass(): void
{
// Pour fermer une connexion déclarée dans setUp()
}
protected function test1()
{
// Lancement du premier test...
$this->assertTrue($condition);
}
}
La classe de test PHPUnit propose des dizaines d'assertions différentes.
$this->fail()
modifierPhpUnit distingue pour chaque test, les erreurs (ex : division par zéro) des échecs (assertion fausse).
Dans le cas où on on souhaiterait transformer les erreurs en échecs, on peut utiliser $this->fail()
:
try {
$response = $this->MonTestEnErreur();
} catch (\Throwable $e) {
$this->fail($e->getMessage());
}
MockObject
modifierLes mocks sont des objets PhpUnit qui permettent de simuler des résultats de classes existantes[5].
Psr\Log\NullLogger
qui peut être instancié depuis les tests des classes utilisant Psr\Log\LoggerInterface
.willReturn()
modifierPar exemple, pour simuler le résultat de deux classes imbriquées (en appelant la méthode d'une méthode), on leur crée une méthode de test chacune :
public function mainTest()
{
$this->monMock1
->expects($this->once())
->method('MaMéthode1')
->willReturn($this->mockProvider())
;
$this->assertEquals(null, $this->monMock1->MaMéthode1()->MaMéthode2());
}
private function mockProvider()
{
$monMock = $this
->getMockBuilder('MaClasse1')
->getMock()
;
$monMock->method('MaMéthode2')
->willReturn('MonRésultat1')
;
return $monMock;
}
Pour qu'une méthode de mock réalise un "set" quand elle est appelée, il ne faut pas le faire directement dans le willReturn
, auquel cas il s'effectue lors de sa définition, mais dans un callback. Ex :
$monMock->method('MaMéthode3')
->will($this->returnCallback(function($item) use ($quantity) {
return $item->setQuantity($quantity);
}))
;
willReturnArgument()
modifierRenvoie l'argument dont le numéro est en paramètre.
willThrowException()
modifierPour qu'un mock simule une erreur. Ex :
$monMock->method('MaMéthode3')->willThrowException(new Exception());
expects()
modifierDans l'exemple précédent, expects()
est un espion qui compte le nombre de passage dans la méthode, et le test échoue si ce résultat n'est pas 1. Ses valeurs possibles sont :
$this->never()
: 0.$this->once()
: 1.$this->exactly(x)
: x.$this->any()
.
De plus, on trouve $this->at()
pour définir un comportement dépendant du passage.
onConsecutiveCalls
modifierSi la valeur retournée par le mock doit changer à chaque appel, il faut remplacer willReturn()
par onConsecutiveCalls()
.
Exemple :
$this->enumProvider->method('getEnumFromVariable')
->will($this->onConsecutiveCalls(
ProductStatusEnum::ON_LINE,
OrderStatusEnum::VALIDATED
));
;
with()
modifierCette méthode permet de définir les paramètres avec lesquels doit être lancé une méthode mock. Ex :
$this->enumProvider->method('getEnumFromVariable')
->with($this->equalTo('variable 1'))
disableOriginalConstructor()
modifierCette méthode s'emploie quand il est inutile de passer par le constructeur du mock.
expectException()
modifierS'utilise quand le test unitaire doit provoquer une exception dans le code testé (ex : s'il contient un throw).
$this->expectException(Exception::class);
$monObjetTesté->method('MaMéthodeQuiPète');
Si au contraire on veut vérifier que le code testé ne renvoie pas d'exception, on peut le lancer suivi d'une incrémentation des assertions :
$monObjetTesté->method('MaMéthodeSansErreur');
$this->addToAssertionCount(1);
Attributs
modifierPHPUnit depuis sa version 10 offre plusieurs attributs pour influencer les tests. Exemples :
#[DataProvider()]
: indique un tableau d'entrées et de sorties attendues lors d'un test[6].#[Depends()]
: spécifie qu'une méthode récupère le résultat d'une autre (son return) dans ses arguments.
Annotations
modifierPHPUnit offre plusieurs annotations pour influencer les tests[7]. Exemples :
@covers
: renseigne la méthode testée par une méthode de test afin de calculer le taux de couverture du programme par les tests.@uses
: indique les classes instanciées par le test.@dataProvider
: indique un tableau d'entrées et de sorties attendues lors d'un test[8].@depends
: spécifie qu'une méthode récupère le résultat d'une autre (son return) dans ses arguments. Si elle appartient à un autre fichier, il faut renseigner son namespace :@depends App\Tests\FirstTest::testOne
. Et comme PhpUnit exécute les tests dans l'ordre alphabétique des fichiers, il faut que le test se trouve après celui dont il dépend.
JavaScript
modifierEn PHP, Selenium peut s'interfacer avec PHPUnit[9] pour tester du JavaScript.
Avec Symfony, il existe aussi Panther[10].
Symfony
modifierPour récupérer une variable d'environnement ou un service dans un test unitaire Symfony, il faut passer par setUpBeforeClass()
pour booter le kernel du framework :
/** @var string */
private static $maVariableYaml;
/** @var Translator */
private static $translator;
public static function setUpBeforeClass(): void
{
$kernel = static::createKernel();
$kernel->boot();
self::$maVariableYaml = $kernel->getContainer()->getParameter('ma_variable');
self::$translator = $kernel->getContainer()->get('translator');
}
Seuls les services publics seront accessibles. Mais il est possible de créer des alias publics des services accessibles uniquement en environnement de test grâce au fichier de config services_test.yaml.
Test fonctionnel :
public function testPost(): void
{
$route = '/api/test';
$body = [
'data' =>
['post_parameter_1' => 'value 1'],
];
static::$client->request('POST', $route, [], [], [], json_encode($body));
$response = static::$client->getResponse();
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertTrue($response->isSuccessful(), $response);
$content = json_decode($response->getContent(), true);
$this->assertNotEmpty($content['message'], json_encode($content));
}
Références
modifier- ↑ https://phpunit.de/manual/current/en/phpunit-book.pdf
- ↑ https://phpunit.de/
- ↑ https://github.com/sebastianbergmann/phpunit
- ↑ https://symfony.com/doc/current/testing.html
- ↑ https://phpunit.de/manual/current/en/test-doubles.html
- ↑ https://docs.phpunit.de/en/10.5/writing-tests-for-phpunit.html#data-providers
- ↑ https://phpunit.readthedocs.io/fr/latest/annotations.html
- ↑ https://blog.martinhujer.cz/how-to-use-data-providers-in-phpunit/
- ↑ Chaine complète de test avec Selenium IDE, Selenium RC et PHPUnit
- ↑ https://github.com/symfony/panther
SimpleTest
SimpleTest est un framework de test open source en PHP qui possède une documentation en français sur http://www.simpletest.org/fr/.
Une fois le .gz téléchargé depuis le site officiel, le décompresser dans un répertoire lisible par Apache.
Il existe également sous la forme d'un plugin Eclipse[1].
HelloWorld
modifierSoit le fichier HelloWorld.php
situé dans le répertoire du framework :
<?php
require_once('autorun.php');
class TestHelloWorld extends UnitTestCase
{
function TestExitenceFichiers()
{
$this->assertTrue(file_exists($_SERVER['SCRIPT_FILENAME']));
$this->assertFalse(file_exists('HelloWikibooks.php'));
}
}
En exécutant ce script dans un navigateur, toutes les méthodes des classes de test sont exécutées séquentiellement. Il devrait donc comme prévu, se trouver lui-même, puis ne pas trouver un fichier HelloWikibooks avec succès.
Les nombres de tests réussis et échoués sont comptabilisés en bas de page, mais seuls les noms des fonctions en échec sont affichés.
Test d'un formulaire web
modifierPlusieurs méthodes sont disponibles pour interagir avec les formulaires[2]. Voici un exemple qui recherche certains mots sur un célèbre site, tente de s'y authentifier, et d'écrire dedans :
<?php
require_once('autorun.php');
require_once('web_tester.php');
class TestWikibooks extends WebTestCase
{
function TestTextesSurPage()
{
$this->assertTrue($this->get('http://fr.wikibooks.org/wiki/Accueil'));
$this->assertTitle('Wikilivres');
$this->assertText('licence');
$this->assertPattern('/Bienvenue/i');
$this->authenticate('MonLogin', 'MonMDP');
$this->assertField('search', 'test');
$this->clickSubmit('Lire');
$this->assertText('Introduction au test logiciel');
}
}
Sous réserve que le site possède bien un champ "name=search".
Références
modifier
Behat
Behat est un framework de test pour faire du behavior-driven development. Cela consiste à rédiger plusieurs scénarios en langage Gherkin, proche de l'anglais naturel, avec indentation comme syntaxe, dans des fichiers .feature. Ces tests peuvent également tester du JavaScript.
Installation
modifierLancer les tests avec en ligne de commande.
Syntaxe
modifierFeature: Function to test description
Texte libre
Scenario: Scenario 1
Given preconditions
When actions
Then results
Scenario: Scenario 2
...
Les préconditions après "Given" correspondent au nom de la méthode PHP à exécuter.
Exemples
modifieruse Behat\Behat\Context\Context;
class Context1 implements Context
{
public function iAmOnTheHomePage()
{
echo 'ok';
throw new PendingException();
}
}
Feature: Visit the homepage
Scenario: Click a link from the homepage
Given I am on the homepage
Compléments
modifierMink[1] est une bibliothèque PHP permettant de simuler un navigateur Web, ce qui permet à Behat de tester du JavaScript avec Selenium[2].
Références
modifier- ↑ http://mink.behat.org/en/latest/
- ↑ (en) Junade Ali, Mastering PHP Design Patterns, Packt Publishing Ltd, (lire en ligne)
Problèmes connus
Le processus de débogage est relativement le même d'un bug à l'autre :
- En cas d'erreur 400, regarder d'abord les logs les plus spécifiques (ex : le var/log de l'application), puis les logs des serveurs Web (ex : /var/log/nginx), puis ceux du système (/var/log).
- En cas d'erreur 500 ou de non réponse, passer directement aux logs des serveurs.
- En cas d'absence de log, localiser le problème avec :
La liste suivante doit permettre de gagner du temps pour solutionner les erreurs que l'on peut trouver dans les logs.
Erreurs sans message PHP
modifierIIS tourne dans le vide après la mise à jour de PHP
modifierVérifier que les dépendances sont bien installées (ex : Visual C++)[1].
Les modifications du php.ini ne sont pas prises en compte dans phpinfo
modifierSur Wamp, redémarrer PHP et Apache ne suffit pas car Apache contient une copie du php.ini dans C:\wamp64\bin\apache\apache2.4.54.2\bin, créée lors de la sélection de la version de PHP (au clic sur PHP/Version).
La connexion a été réinitialisée
modifierErreur sous Firefox provenant d'un mysql_close()
ou d'une directive Apache.
La page n'est pas redirigée correctement
modifierUn header
revient en boucle après une suite de conditions. S'il est local, le remplacer par chdir()
.
Le code PHP n'est pas interprété (et est affiché)
modifierSi a2enmod php7.4
indique que le module est déjà installé, c'est peut-être lié à a2enmod userdir
. Cela peut se régler avec :
vim /etc/apache2/mods-enabled/php7.4.conf
Commenter les lignes :
<IfModule mod_userdir.c>
...
</IfModule>
Et relancer Apache.
Sinon c'est peut-être le vhost utilisé qui n'est pas le bon (voir /var/log/apache2) ou qu'il ne contient pas :
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
Require all granted
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Allow from all
</Directory>
Les dernières lignes d'un POST sont ignorées
modifierIl faut soit augmenter la variable PHP max_input_vars (ce qui ne peut pas être fait avec ini_set()
[2]), soit il faut fragmenter en plusieurs requêtes, par exemple toutes les 300 lignes.
Un regex marche sur https://regex101.com/ mais pas en local
modifierSi la machine est Windows, tenir compte de la différence de retour chariot : \r\n
au lieu de \n
.
Une addition ou soustraction de dates ajoute ou retire un jour
modifierCela survient quand on part d'un mois à 31 jours pour arriver à un mois qui en a moins.
Par exemple :
var_dump((new DateTime('2024-12-31'))->add(new DateInterval("P6M"))); // 2025-07-01 00:00:00 var_dump((new DateTime('2024-12-31'))->add(new DateInterval("P12M"))); // 2025-12-31 00:00:00
Pour avoir un calcul juste, on peut ajuster le jour du résultat ainsi :
$startDate = new DateTime('2024-12-31'); $originalDay = $startDate->format('d'); $startDate->add(new DateInterval('P6M')); if ($startDate->format('d') !== $originalDay) { // Ajuster au dernier jour du mois précédent $startDate->modify('last day of previous month'); } var_dump($startDate); // 2025-06-30 00:00:00
Une addition ou soustraction de dates ajoute ou retire une heure
modifierC'est à cause des changements d'heure d'hiver et heure été.
Par exemple :
var_dump((new DateTime('2024-12-31'))->add(new DateInterval("PT4320H"))); // 2025-06-29 01:00:00 var_dump((new DateTime('2024-12-31'))->add(new DateInterval("PT8640H"))); // 2025-12-26 00:00:00
Pour avoir un calcul juste, on peut changer de fuseau horaire le temps du calcul, vers un qui n'est pas soumis aux changements d'heures[3] :
var_dump((new DateTime('2024-12-31 UTC'))->add(new DateInterval("PT4320H"))); // 2025-06-29 00:00:00 var_dump((new DateTime('2024-12-31 UTC'))->add(new DateInterval("PT8640H"))); // 2025-12-26 00:00:00
ou à l'échelle du serveur :
$timeZone = date_default_timezone_get(); date_default_timezone_set('UTC'); // calcul date_default_timezone_set($timeZone);
PHP natif
modifierConnect Error, 2002: Aucune connexion n'a pu être établie car l'ordinateur cible l'a expressément refusée.
modifierRelancer le serveur de base de données.
Connect Error, 2002: Une tentative de connexion a échoué car le parti connecté n'a pas répondu convenablement au-delà d'une certaine durée ou une connexion établie a échoué car l'hôte de connexion n'a pas répondu.
modifierOuvrir les pare-feux.
Invalid body indentation level (expecting an indentation level of at least 8)
modifierEn PHP7.3, la syntaxe heredoc impose de supprimer l'indentation de la balise fermante : elle soit suivre "\n".
json_decode renvoie NULL (aléatoirement)
modifierAjouter le paramètre 4 : JSON_THROW_ON_ERROR.
json_decode throw "Control character error, possibly incorrectly encoded"
modifierLa string à décoder est trop longue.
Malformed UTF-8 characters, possibly incorrectly encoded
modifierChanger l'encodage de la chaine avant son encodage en JSON, avec[4] :
$chaine = mb_convert_encoding($chaine, 'UTF-8', 'auto');
MySQL server has gone away
modifierLa limite des 61 jointures a peut-être été atteinte dans une requête. Sinon vérifier les limites des ressources (du .ini) : par défaut default_socket_timeout égal 60 s.
This extension requires the Microsoft ODBC Driver 11 for SQL Server
modifierInstaller le pilote depuis https://www.microsoft.com/en-us/download/details.aspx?id=36434.
Unable to initialize module. Module compiled with module API=x. PHP compiled with module API=y. These options need to match
modifierSe procurer une autre DLL à renseigner dans PHP.ini.
You can only iterate a generator by-reference if it declared that it yields by-reference
modifierSe produit quand on itère sur la référence d'un générateur PHP :
foreach ($generator as &$item) {
...
}
Il faut donc retirer l'opérateur de référence (&) de l'itération...
Cannot traverse an already closed generator
modifierUn iterator_to_array($generator)
le ferme en le convertissant en tableau.
child exited on signal 7 (SIGBUS)
modifierModifier le php.ini[5] :
pm.max_children = 80
pm.max_spare_servers = 20
pm.max_requests = 200
apc.stat = 0
max_children est calculé en divisant la RAM par la taille des processus[6].
Invalid resource type: unknown type
modifierLe paramètre n°2 de fopen() ne permet pas la lecture[7].
Each stream must be readable
modifierOn essaie de lire un fichier fermé : déplacer le fclose()
après s'il y en a un avant.
fclose(): supplied resource is not a valid stream resource
modifierOn essaie de fermer un fichier fermé : utiliser if (is_resource($f))
avant.
SSL peer certificate or SSH remote key was not OK
modifierLors d'un curl_exec
, ajouter :
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
ou :
$options['verify_host'] = false;
$options['verify_peer'] = false;
Fatal error
modifier[] operator not supported for strings
modifier// On récupère une variable dont on ne connait pas le type pour en faire un tableau
if (!isset($tableau1)) {
$tableau1 = array();
} elseif (is_string($tableau1)) {
$tableau1 = array($tableau1);
}
$tableau1[] = 'paramètre suivant';
Allowed memory size of x bytes exhausted
modifierModifier le PHP.ini ou bien ajouter une autre limite dans le programme :
ini_set('memory_limit', '100M');
Pour faire sauter la limitation :
ini_set('memory_limit', '-1');
Si c'est dans une commande :
php -d memory_limit=-1 ma_commande
Si c'est Composer : COMPOSER_MEMORY_LIMIT=-1 ./composer.phar update
Sinon, utiliser Xdebug en mode pas à pas pour visualiser les variables à supprimer (avec unset()
).
Call to a member function ... on a non-object
modifierLa méthode est invoquée sur une variable qui n'est pas une classe.
Call to undefined function
modifierSi une fonction est définie mais qu'on ne peut pas l'invoquer dans une méthode de classe, il faut préalablement l'importer en tenant compte du polymorphisme.
Call to undefined function sqlsrv_connect()
modifierInstaller le pilote correspondant à la version de PHP du serveur Web :
- Télécharger sur https://www.microsoft.com/en-us/download/details.aspx?id=20098.
- Copier dans le dossier PHP (ex : C:\Program Files (x86)\EasyPHP\binaries\php\php_runningversion\ext).
- Ajouter à PHP.ini.
- Redémarrer le serveur Web.
Call to undefined method
modifierLa méthode est invoquée sur une classe qui ne l'a pas.
Si elle est censée l'avoir c'est qu'elle n'est pas récupérée, ce qui peut arriver avec les jointures entre entités d'ORM.
Cannot access empty property
modifierUne variable non définie ne peut pas fournir de propriété. Si elle était définie ailleurs c'est qu'elle est inaccessible, et donc qu'il faut la récupérer (ex : avec global
).
Error connecting to the ODBC database: [Microsoft][SQL Server Native Client 10.0][SQL Server]échec de l'ouverture de session de l'utilisateur
modifierLa précédente connexion n'a pas dû être fermée proprement avant une tentative de reconnexion. Essayer au choix :
mysql_close($conn); // MySQL
sqlsrv_close($conn); // MS-SQL
odbc_close($conn); // ODBC
Out of memory
modifierÉditer le PHP.ini pour augmenter la ligne :
memory_limit = 256M
pdo_sqlsrv_db_handle_factory: Unknown exception caught
modifierInstaller et configurer le paquet suivant :
apt-get install -y locales
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
Uncaught exception 'com_exception' with message 'Failed to create COM object `xxx.application': Accès refusé.
modifierSur le serveur Web Windows, dans démarrer, exécuter dcomcnfg (sinon y aller dans Outils d'administration, Service de composants), puis Ordinateurs, Poste de travail, Configuration DCOM, aller dans les propriétés de l'application mentionnée, puis dans l'onglet Sécurité, définir la première permission (Autorisations d'exécution et d'activation à Personnaliser et ajouter le compte du serveur ou site qui exécute le script (ex pour ISS : IIS_IUSRS ou IUSR).
Par exemple pour Word l'application se prénomme "Document Microsoft Office Word", et Excel "Microsoft Excel Application".
Uncaught exception 'com_exception' with message 'Source: Microsoft Office Excel Description: Mémoire insuffisante.
modifierIl faut certainement rerégler la Configuration DCOM voire le serveur Web.
Uncaught exception 'PDOException' with message
modifiercould not find driver
modifierSe référer à la liste des pilotes connus dans Programmation PHP/PDO#Installation.
SQLSTATE[HY000]: General error
modifierPDO ne gère pas le code SQL "set @ma_variable" ou "if". Donc il faut plutôt faire ces calculs en PHP.
SQLSTATE[28000] [1045] Access denied for user
modifierSi l'utilisateur existe avec les privilèges nécessaires, y compris depuis toute localisation (@%), il faut quand-même créer un deuxième compte homonyme pour l'emplacement distant (ex : 'username'@'example.com'). Par exemple dans phpMyAdmin, remplir la provenance mentionnée en erreur ('example.com') dans le champ "Client".
SQLSTATE[28000] SQLConnect: 18456 [Microsoft][ODBC SQL Server Driver][SQL Server] Échec de l'ouverture de session de l'utilisateur
modifierSe référer à Programmation PHP/PDO#Accès à la base de données avec PDO : la source de données ODBC doit être suivie du compte pour y accéder.
SQLSTATE[IM002] SQLConnect: 0 [Microsoft][Gestionnaire de pilotes ODBC] Source de données introuvable et nom de pilote non spécifié
modifierSe référer à Programmation PHP/PDO#Accès à la base de données avec PDO : la source de données ODBC doit être créée dans C:\Windows\SysWOW64\odbcad32.exe.
SQLSTATE[IMSSP]: An invalid keyword 'host' was specified in the DSN string
modifierSe référer à Programmation PHP/PDO#Accès à la base de données avec PDO : le paramètre 'host' est valide pour MySQL mais pas pour MS-SQL.
SQLSTATE[IMSSP]: The DSN string ended unexpectedly
modifierSe référer à Programmation PHP/PDO#Accès à la base de données avec PDO : les virgules et points-virgules changent d'un pilote à l'autre.
SQLSTATE[IMSSP]: This extension requires the Microsoft SQL Server 2012 Native Client ODBC Driver to communicate with SQL Server
modifierSe référer à Programmation PHP/PDO#Accès à la base de données avec PDO : utiliser la syntaxe ODBC.
Notice
modifierUndefined property
modifierLa propriété de la variable n'est pas déclarée.
Undefined index: SERVER_NAME in ...php
modifierCertaines versions de PHP utilisent $_SERVER['HTTP_HOST']
au lieu de $_SERVER['SERVER_NAME']
.
Undefined variable
modifierLa variable n'est pas déclarée.
Use of undefined constant
modifierLa constante n'est pas déclarée.
Parse error
modifiersyntax error, unexpected '(', expecting variable (T_VARIABLE) or '$' in...
modifierSéparer le $ dans la chaine (ex : {$
-> { $
).
syntax error, unexpected '' (T_ENCAPSED_AND_WHITESPACE), expecting identifier (T_STRING) or variable (T_VARIABLE) or number (T_NUM_STRING)
modifierRemplacer les variables incluses dans des chaines. Ex :
$query="select $contact['member']"; // pas bien
$query="select ".$contact['member']; // bien
syntax error, unexpected '$Variable' (T_VARIABLE), expecting function (T_FUNCTION)
modifierDans une classe en dehors des méthodes, il faut déclarer les variables avec leur portée :
private $Variable;
Strict Standards
modifierDeclaration of extFunctions::logs() should be compatible with functions::logs($chaine)
modifierLa fonction (logs()
dans l'exemple) existe déjà, peut-être avec des majuscules (peu importe les arguments).
Only variables should be passed by reference
modifierIl faut appliquer la fonction end()
sur une variable au lieu du résultat d'une opération[8]. Ex :
// $extension = end(explode('.', $fichier));
$ext = explode('.', $fichier);
$extension = end($ext);
Warning
modifierCannot use a scalar value as an array
modifierUn tableau de valeurs ne peut pas être redéfini en tableau de tableaux si elles existent. Remplacer les cas ainsi :
$tab['1'] = 1;
//$tab['1']['un'] = 'un';
$tab['un']['un'] = 'un';
Creating default object from empty value
modifierSe produit quand on appelle l'attribut d'un objet NULL.
date_diff() expects parameter 1 to be DateTimeInterface
modifierLa classe native DateTime()
est plus pratique que la fonction date_diff()
:
$Avant = new DateTime('20140101');
$Apres = new DateTime();
print $Avant->diff($Apres)->format("%d");
Illegal string offset
modifierOn invoque une entrée inexistante dans un tableau associatif. Lever l'exception avec Try
ou if (!isset(
.
mkdir(): File exists
modifierSous Docker Desktop pour Windows, mkdir() appelle file_exists(), et ce dernier renvoie true si le dossier a existé.
PHP Startup: Unable to load dynamic library 'memcached.so'
modifiersudo pecl install memcached
Si cela persiste :
sudo apt-get install php-igbinary sudo apt-get install php-msgpack sudo service php7.2-fpm reload
PHP Startup: Unable to load dynamic library 'redis.so'
modifiersudo pecl install redis
Erreurs SMTP
modifierLa connexion a échoué
modifierVérifier le serveur HTTP qui interprète le .php.
SMTP Error: Could not connect to SMTP host
modifierChanger de SMTP, ex : http://www.commentcamarche.net/faq/893-parametres-de-serveurs-pop-imap-et-smtp-des-principaux-fai
Si les mails partent sans arriver
modifier- Vérifier que l'IP de l'expéditeur n'est pas blacklistée : http://whatismyipaddress.com/blacklist-check
- Définir un reverse DNS si absent
- Veiller à ce que le mail ne soit pas présumé spam, en évitant les sujets vides par exemple, ou les pièces jointes exécutables non compressées (.exe, .cmd, .vbs...).
Composer
modifier1 package has known vulnerabilities
modifierExemple avec un guzzlehttp/guzzle (7.4.3)
absent du composer.json :
composer depends guzzlehttp/guzzle
puis composer update
du résultat.
Si cela ne suffit pas, installer Guzzle puis le désinstaller pour mettre à jour sa dépendance indirecte :
composer require guzzlehttp/guzzle && composer remove guzzlehttp/guzzle
"./composer.json" does not contain valid JSON
modifierLors d'un composer install, si ce message survient à tort, c'est qu'un autre fichier .json du projet contient le problème.
Si cela persiste malgré la correction, il se peut qu'il faille redémarrer Docker Desktop sur Windows.
Conclusion: don't install xxx
modifierLors d'un composer require, spécifier une version inférieure du paquet requis.
No driver found to handle VCS repository
modifierVCS fonctionne en protocole git, vérifier que l'URL est bien au format "git@repo:bundle.git".
Sinon il y a deux alternatives :
- Pour HTTPS, remplacer la dépendance de type "vcs" par une de type "package"[9].
- Pour décompresser un .zip, utiliser le type "artifact".
no matching package found
modifierAjouter le paramètre suivant :
composer require mon_paquet --update-with-all-dependencies
Permission denied (public key)
modifierSi le dépôt privé se clone bien sans passer par "composer" : voir la page Programmation PHP/Composer.
You must be using the interactive console to authenticate
modifierPour installer cette bibliothèque, il faut que Composer puisse se loguer. Pour ce faire, il utilise auth.json qui peut se trouver dans[10] :
$HOME/.composer/auth.json
- ou à côté du
composer.json
Exemple de auth.json[11] :
{
"github-oauth": {
"github.com": "<snip>"
},
"http-basic": {
"repo.magento.com": {
"username": "<snip>",
"password": "<snip>"
}
}
}
Your Composer dependencies require a PHP version ">= 8.0.0".
modifierAjouter au paragraphe "config" :
"platform-check": false
Puis relancer composer install
pour que cela soit pris en compte.
Your requirements could not be resolved to an installable set of packages
modifierSi deux dépendances s'empêchent mutuellement de se mettre à jour, les demander dans la même commande :
composer require mon-bundle ^1.0 symfony/http-client 5.3.* -W
Timeout sur composer install
modifierDésactiver Xdebug et relancer.
PHPUnit
modifierLes tests ne se lancent pas
modifierSi phpunit.xml.dist utiliser un bootstrap.php, y ajouter error_reporting(E_ALL);
.
Sinon, si un var_dump() fonctionne dans le setUp() du test unitaire mais pas dans ses méthodes de test, c'est peut-être une exception qui se lance dans un trait ou dans le vendor PHPUnit. Pour la trouver, lancer l'application et regarder les logs (par exemple depuis un contrôleur).
Sinon, si ça fonctionne en commentant le "extends", tester la classe mère pour y trouver l'exception.
Sinon, dans Symfony, tail var/log/test/mon_log.log
.
Sinon, lancer Xdebug pour comprendre.
et echo() ou var_dump() dans les tests n'affiche rien
modifierLancer le test en mode le plus verbeux :
- Avec le paramètre : -vvv
- Modifier phpunit.xml.dist avec :
- <server name="SHELL_VERBOSITY" value="3" />[12]
- <ini name="error_reporting" value="true" />
Les tests fonctionnels renvoient toujours 404
modifierSur Symfony, self::createClient()
appelle directement l'API sans serveur HTTP. Si on utilise phpunit dans symfony/phpunit-bridge, il va chercher sur example.com.
Sinon il manque peut-être un trailing slash dans la route appelée.
Les tests se lancent mais s'arrêtent sans explication, en renvoyant "killed"
modifierUn var_dump() sature la mémoire.
Did you forget a "use" statement for MaClasse ou Class 'MaClasse' not found
modifierSi des classes existent mais que PHPUnit n'arrive pas à les charger :
- Vérifier les namespaces racines définis dans composer.json par autoload et autoload-dev.
- Retirer suffix=".php" du phpunit.xml utilisé.
The .git directory is missing from...
modifierSupprimer vendor/ et relancer composer.
THE ERROR HANDLER HAS CHANGED!
modifierPlusieurs solutions possibles :
- phpunit --self-update
- Dans Symfony, changer phpunit.xml.dist avec SYMFONY_DEPRECATIONS_HELPER = weak_vendors
- set_error_handler(array(&$this, 'handleGeoError'));
- Si le projet Symfony a Sentry, on peut le retirer des tests dans bundles.php.
Trying to configure method "get" which cannot be configured because it does not exist, has not been specified, is final, or is static
modifierUn mock ne récupère aucune méthode de sa classe car elle n'a pas pu être instanciée.
- La casse du nom de la classe ou du namespace de son
use
n'est probablement pas exacte. - Sinon c'est la portée de la méthode mockée qui n'a pas publique. Si un dump du mock montre les attributs mais pas les méthodes, remplacer
->getMockForAbstractClass()
par->getMock()
. - S'il s'agit d'une méthode finale, il faut la définir lors de l'instanciation du mock. Ex :
$this->serializerMock = $this
->getMockBuilder(SerializerInterface::class)
->setMethods(['serialize', 'deserialize', 'decode'])
->getMock()
;
$this->serializerMock
->method('decode')
->willReturn('')
;
Trying to @cover or @use not existing method
modifierSi la méthode existe bien, c'est que la classe testée n'a pas été définie en annotation (avec son namespace) :
/**
* @coversDefaultClass App\Service\MyService
*/
class MyServiceTest extends TestCase
{
...
}
TypeError: Argument 1 passed to PHPUnit\Framework\TestCase::registerMockObjectsFromTestArguments() must be of the type array, null given
modifierSi cela survient dans tous les tests qui invoquent un trait, retirer le constructeur de ce trait.
Warning No tests found in class "Xxx".
modifierSi les méthodes de tests contiennent des assertions invisibles de PHPUnit, leur ajouter /** @test */
pour afficher pourquoi ils ne se lancent pas. Par exemple, il peut s'agir d'un mock qui demande un constructeur.
Si c'est normal de ne pas lancer de test dans une classe mère, la rendre abstraite ou statique.
Symfony
modifierEn cas d'erreur, un composant de débogage appelé "Profiler", est accessible en bas à gauche de la page d'erreur, avec des logs et mesures de performances. Installation :
composer require --dev symfony/profiler-pack
On peut par exemple accéder au phpinfo() via l'URL .../_profiler/phpinfo.
Le profiler fonctionne pour les contrôleurs mais pas pour les commandes.
La première soumission d'un formulaire ne marche pas, mais les suivantes oui
modifierRetirer le :
$uow = $em->getUnitOfWork(); $uow->computeChangeSets();
Le contrôle de formulaire avec name=‘xxx’ ne peut recevoir le focus.
modifierIl s'agit généralement d'un champ caché : le passer en required=false ou faire en sorte qu'il soit toujours rempli même caché.
Un champ de formulaire ChoiceType n'affiche pas sa valeur par défaut, qui est pourtant dans la liste
modifierLa liste contient une clé d'un type different. Exemple de solution :
'data' => (string) $myInteger,
Une route de contrôleur fonctionne dans un client HTTP mais pas dans un autre
modifierSi $request est vide avec certains clients HTTP :
- Utiliser HTTPS au lieu de HTTP.
- Vérifier les paramètres d'en-tête HTTP (Accept: 'application/json' et Content-Type: 'application/json').
- Utiliser $request->getContent() ou ou $request->toArray() au lieu de $request->request[13] (ça ne marche pas à la place de $request->query).
Une route de contrôleur fonctionne dans un client HTTP mais pas en PhpUnit, ex : No matching accepted Response format could be determined (406 Not Acceptable)
modifiercomposer remove friendsofsymfony/rest-bundle
Attempted to load class "WebProfilerBundle" from namespace "Symfony\Bundle\WebProfilerBundle"
modifierSi cela fonctionne avec "composer install" mais pas avec " composer install --no-dev", il faut définir APP_ENV=prod
dans le .env.
Attribute "autowire" on service "xxx" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly.
modifierLors d'utilisation de service abstrait, on ne peut pas utiliser l'autowiring. On peut alors transformer ce service abstrait en dépendance à injecter plutôt qu'à hériter.
Cannot autowire service... alors qu'il existe
modifierPeut se produire :
- quand le dossier de la classe est dans "App\.exclude" du services.yaml.
- quand il n'est pas exclus et qu'on le déclare dans un .yaml importé dans services.yaml (donc en doublon de l'autowiring). Il faut alors soit le désactiver, soit exclure les namespaces concernés, soit déplacer ces déclarations dans services.yaml.
- dans un bundle, si
$loader->load('services.yaml');
est bien effectué[14], alors le déclarer dans le services.yaml du bundle. Sinon, vérifier qu'il n'y a pas un chemin erroné dans l'extension prepend(). - Depuis Symfony 6.1 on peut utiliser l'attribut
#[Autowire]
[15].
#[AsEventListener(event: KernelEvents::CONTROLLER)]
class MyListener
{
public function __construct(
private readonly Security $security, // exemple
private readonly ControllerResolverInterface $controllerResolver
) {
}
}
L'erreur est :
Cannot autowire service "App\EventListener\MyListener": argument "$controllerResolver" of method "__construct()" references interface "Symfony\Component\HttpKernel\Controller\ControllerResolverInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "debug.controller_resolver", "debug.controller_resolver.inner".
La solution est d'écrire :
#[AsEventListener(event: KernelEvents::CONTROLLER)]
class MyListener
{
public function __construct(
private readonly Security $security, // exemple
#[Autowire(service: 'controller_resolver')] private readonly ControllerResolverInterface $controllerResolver
) {
}
}
Cannot load resource "../../src/Controller/". Make sure to use PHP 8+ or that annotations are installed and enabled.
modifier composer require sensio/framework-extra-bundle
Circular reference detected
modifierL'autoloader se heurte à un argument du constructeur d'une classe : il faut le sortir de la méthode __construct()
pour le définir dans une méthode portant son nom. Exemple de déclaration en YAML :
app.ma_classe:
class: App\MaClasse
arguments:
- '@service.sans.probleme'
calls:
- method: setServiceAvecProbleme
arguments:
- '@service.avec.probleme'
tags:
- { name: doctrine.event_subscriber }
A circular reference has been detected when serializing the object
modifierIdem en fetch="EXTRA_LAZY"
dans l'entité.
Pour résoudre cela sans changer de relation entre les entités, il y a plusieurs solutions :
- Dans l'entité,
use Symfony\Component\Serializer\Annotation\Ignore;
et annotation/** @Ignore() */
au dessus de l'attribut en erreur. - Dans le contrôleur, éviter de renvoyer la réponse brute, mais filtrer les attributs avec
use Symfony\Component\Serializer\SerializerInterface;
et$this->serializer->serialize($data, 'json', $context)
.
curl error 6 while downloading https://flex.symfony.com/versions.json: Could not resolve host: flex.symfony.com
modifiercomposer update symfony/flex --no-plugins --no-scripts
CURLPIPE_HTTP1 is no longer supported
modifierSi cela se produit sur un projet Symfony avec composer, il faut juste :
composer global require symfony/flex ^1.5
rm -Rf vendor/symfony/flex
Environment variables "xxx" are never used. Please, check your container's configuration.
modifierPazsser par des variables intermédiaires dans services.yaml :
yyy: '%env(xxx)%'
Error 400 Bad Request Your browser sent a request that this server could not understand.
modifierLes règles de réécriture d'URL Apache sont erronées (voir ci-dessous).
Error 404 Not Found The requested URL /xxx was not found on this server.
modifierSi la page d'accueil fonctionne mais pas les sous-pages (alors qu'un nom de domaine est déjà dédié au site dans le vhost), les règles de réécriture d'URL de la configuration Apache sont manquantes ou erronées. Il faut donc créer public/.htaccess
:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
Invalid header: CR/LF/NUL found
modifierUn JSON envoyé en POST dans symfony/http-client contient des retours chariots inattendus : les retirer. Ex :
$json = preg_replace("(\r?\n)", '', $json);
manifest.json does not exist
modifieryarn install && yarn add --dev @symfony/webpack-encore && yarn build
Maximum function nesting level of '6000' reached
modifierSi cela se produit par exemple en vidant le cache, c'est que deux services se renvoient la balle (même indirectement) depuis leurs constructeurs.
NetworkError when attempting to fetch resource (Firefox) / Failed to fetch (Chrome)
modifierDans l'API avec Swagger UI : violation du CQRS empêchant l'affichage du résultat de l'API dans /api/doc, à causes des domaines. Sur un domaine local, cela peut être résolu en changeant l'URL avant /api/doc pour qu'elle soit valide (ex : passer de wikibooks/api/doc à wikibooks.org/api/doc).
Sinon réessayer en HTTP au lieu de HTTPS.
Sinon c'est la clé renseignée (du .env) qui est différente de celle en BDD.
Si le serveur Web est Nginx, retirer du vhost "add_header Access-Control-Allow-Origin *;" et "limit_exept".
request->request et request->query sont vides à tort
modifierUtiliser $request->getContent() ou $request->toArray() à la place[16].
SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known
modifierUn service inexistant ou avec des paramètres en erreur est appelé. Cela peut être provoqué par une variable d'environnement d'URL manquante ou erronée (ex : retirer le protocole, ou ajouter la version du SGBD).
SSL certificate problem: unable to get local issuer certificate
modifierUtiliser l'installation avec "Composer.phar" plutôt que "Symfony.phar".
InvalidArgumentException Invalid env(bool:xxx) name: only "word" characters are allowed
modifierSoit retirer la résolution "bool:" sur la variable d'environnement concernée, soit la renommer si elle contient des symboles autres que des lettres, des nombres ou underscore (ex : "=", "%", ":", etc.).
The autoloader expected class "App\MaClasse" to be defined in file "/var/www/.../MaClasse.php". The file was found but the class was not in it, the class name or namespace probably has a typo.
modifierRevérifier que :
- le fichier a bien l'extension .php
- la balise ouvrante
<?php
- comporte bien la classe du même nom à la majuscule prêt
- avec le bon namespace et qui correspond aux dossiers (vérifiable dans la déclaration avec CTRL + clic dans PhpStorm).
Uncaught ReflectionException
modifierSi c'est lors du passage de PHP 7 à 8, essayer de lancer le script sans Symfony pour avoir le détail (ex : utiliser PhpUnit directement au lieu de PhpUnit bridge). Cela peut par exemple provenir de return type différents entre une interface (ex : ArrayAccess) et son implémentation.
You have requested a non-existent parameter "kernel.secret". Did you mean this: "kernel.charset"?
modifierÀ l'installation d'une dépendance, si les champs sont renseignés dans config/packages, alors c'est qu'ils ne sont pas chargé dans le bundle par Kernel.php.
Peut se traduire aussi par des erreurs de chargement de bundle du type : The child config "x" under "y" must be configured.
"The controller for URI \"/api/ma_route/123\" is not callable: Controller \"MonController\" does neither exist a service nor as class."
modifierSi bin/console debug:autowiring --all
montre le service et bin/console debug:route
la route, renseigner routes.yaml et vider le cache. Ex :
index:
methods: DELETE
path: /api/ma_route/{id}
controller: App\Controller\MonController::__invoke
Si ça ne fonctionne pas, passer par un évènement sur la méthode générique.
Doctrine
modifierLe champ ne se sauvegarder pas en base
modifier- Est-ce qu'il y a le flush après la modification du champ ?
- Le cache Doctrine a-t-il bien été vidé depuis l'ajout du champ ?
- Est-il bien dans la variable, sinon est-il mappé dans un formulaire ?
- Est-il en annotation PHP alors que Symfony est configuré pour les attributs ?
Le champ sauvegardé en base est toujours 0
modifierSe produit quand on utilise l'annotation @ORM\GeneratedValue(strategy="IDENTITY")
sur un champ qui n'est pas AUTOINCREMENT en base de données.
Binding an entity with a composite primary key to a query is not supported
modifierSe produit quand on utilise la méthode magique find() d'un repository sur une entité qui a une clé composite (au moins deux attributs avec @ORM\Id
). Il faut alors utiliser findBy(['id' => xxx])
.
Call to undefined function Closure at EventDispatcher.php:299
modifierAjouter dans composer.json :
"conflict": {
"symfony/symfony": "*",
"doctrine/common": ">=3.0",
"doctrine/persistence": "<1.3"
},
Puis lancer "composer update".
Cannot autowire service "App\Components\CRM\Repository\MonRepository": argument "$class" of method "Doctrine\ORM\EntityRepository::__construct()" references class "Doctrine\ORM\Mapping\ClassMetadata" but no such service exists.
modifierSe produit avec l'autowiring, quand on ajoute l'annotation vers un repo dans une entité. Ex :
@ORM\Entity(repositoryClass="App\MonRepository")
En fait depuis Doctrine 1.8[17], le repository doit étendre "ServiceEntityRepository" au lieu de "EntityRepository", et avoir un constructeur. Par exemple :
use App\Entity\MonEntite;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\ManagerRegistry; // anciennement Doctrine\Persistence\ManagerRegistry;
class MonEntiteRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, MonEntite::class);
}
Cannot select entity through identification variables without choosing at least one root entity alias
modifierRetirer les ->addSelect()
du queryBuilder, ou utiliser ->from()
.
Class "App\Entity\X" seems not to be a managed Doctrine entity. Did you forget to map it?
modifierOn fait appel à une classe PHP comme si c'était une entité Doctrine : il faut simplement ajouter les annotations Doctrine à la classe, ou bien recréer la table à partir du code PHP.
Column not found: 1054 Unknown column 't0.xxx_id' in 'field list'
modifierIl faut juse ajouter la colonne de jointure sous l'annotation *To*. Ex :
@ORM\JoinColumn(name="id", referencedColumnName="id_personne")
Could not find the entity manager for class '...'
modifierDans doctrine.yaml, retirer type: annotation
.
Entity has to be managed or scheduled for removal for single computation
modifier- Si remove : retirer le ON CASCADE DELETE de l’entité supprimée.
- Si update : faire le add avant.
Entity of type 'App\\MonEntite' for IDs id(1) was not found
modifierS'il s'agit de l'ID d'une entité jointe, le rendre nullable (@ORM\JoinColumn(nullable=true)
)[18].
S'il s'agit d'une entité dont la clé primaire est une clé étrangère, lui ajouter :
@ORM\GeneratedValue(strategy="NONE")
Entity of type App\\MonEntite is missing an assigned ID for field 'id'.
modifierUne entité n'arrive pas à être sauvegardée avec un ID null (non auto-incrémenté). Il faut donc le générer (par exemple avec un new UuidV4()
).
Sinon vérifier que l'erreur ne vient pas d'un listener en sur Doctrine\ORM\Events::prePersist
qui tente de persister l'entité récupérée via $eventArgs->getEntity()
.
Doctrine\ORM\ORMInvalidArgumentException: A new entity was found through the relationship 'X' that was not configured to cascade persist operations for entity: X. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"})
modifierUn service a un tag et un argument constructeur incompatible. Ce service en argument fait un $em->clear() au lieu de clear(object), ou persist(object) au lieu de merge(object).
Invalid PathExpression. Must be a StateFieldPathExpression
modifierCela peut arriver quand on ajoute une clé étrangère dans un select sans sa jointure :
$this->createQueryBuilder('user') ->select(['user.id', 'user.company'])
On peut donc le remplacer par :
$this->createQueryBuilder('user') ->select('user.id') ->leftJoin('user.company', 'u')
Par contre, si on ne veut pas la jointure, utiliser "identity" :
$this->createQueryBuilder('user') ->select(['user.id', 'identity(user.company) company'])
Multiple non-persisted new entities were found through the given association graph:\n\n * A new entity was found through the relationship 'X' that was not configured to cascade persist operations for entity: Y. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={\"persist\"}). / Notice: Undefined index: 00000000...
modifier- Ajouter un persist dans l'entité ou avant le flush.
- Sinon, on tente de flusher une entité récupérée, ou dont une de ses entités liées a été récupérée, par l’entityManager de la BDD slave. Utiliser le master à la place.
- Sinon c'est que le même flush est réalisé plusieurs fois : ajouter du cache d'instance pour ne pas ré-exécuter ce code.
No alias was set before invoking getRootAlias()
modifierSe produit soit :
- Lors d'un
$qb = $this->entityManager->getEntityManager()->createQueryBuilder();
(sans alias comme dans :$this->createQueryBuilder('me')
hérité deEntityRepository
).
Il faut alors rajouter un alias pour l'entité courante ainsi :
$em->createQueryBuilder() ->select('me') ->from(MonEntite::class, 'me')
- Ou pour un update où on répète à tort l'alias ('me') dans
update()
(ce qui peut aussi donnerError: Class 'pn' is not defined
quand il n'y a pas de jointure) :
$this->createQueryBuilder('me') ->update() ->set('me.isDeleted', 1)
Property \"metadata\" on resource \"App\\MonEntite\" is declared as a subresource, but its type could not be determined in
modifierSurvient quand une entité étend une autre sans discriminator[19].
The association mon_entité1#entité2 refers to the owning side field mon_entité2#id which does not exist
modifierSi l'entité 2 ne fait pas référence à l'entité 1, supprimer dans l'entité 1, champ entité2, le mappedBy=
.
The class 'Doctrine\Common\Collections\ArrayCollection' was not found in the chain configured namespaces App\Entity
modifierIl peut y avoir une entité avec une relation ManyToMany dans laquelle on met un ArrayCollection au lieu de l'entité demandée[20].
Sinon vérifier qu'on attend pas un PersistentCollection au lieu d'un ArrayCollection (ou vice-versa avec ->unwrap()).
The EntityManager is closed
modifierCela survient quand l'EntityManager rencontre une exception. On peut[21] :
- la lever avec :
if ($this->em->isOpen()) {
$this->em->persist($entity);
$this->em->flush($entity);
}
- recréer l'entityManager :
if (!$this->em->isOpen()) {
$this->em = $this->em->create(
$this->em->getConnection(),
$this->em->getConfiguration()
);
}
The identifier id is missing for a query
modifierSe produit lors d'un repository->find(null).
The metadata storage is not up to date, please run the sync-metadata-storage command to fix this issue
modifierSi le paramètre "server_version" est présent dans le DSN "DATABASE_URL", l'ajouter, sinon le retirer.
The referenced column name 'xxx' has to be a primary key column on the target entity class
modifierS'il n'est pas possible d'utiliser la clé étrangère comme clé primaire, retirer simplement la ligne :
#[ORM\JoinColumn(name: 'my_id', referencedColumnName: 'my_id')]
The table with name 'xxx' already exists
modifierSi cela survient lors du bin/console doctrine:schema:validate
, create ou update sur une base vide, et qu'il n'y a pas de doublon dans le dossier "Entity"[22] :
- Chercher les doublons dans d'autres namespaces.
- Forcer la version du SGBD dans
DATABASE_URL
, en l'ajoutant dans?serverVersion=
. - Dans le cas du validate, on peut ajouter
--skip-sync
pour bypasser cette partie du test.
Transaction commit failed because the transaction has been marked for rollback only
modifierSe produit quand un flush() rencontre une erreur SQLSTATE (ex : colonne manquante, même d'une autre table). Le commenter pour la voir apparaitre.
Uncaught PHP Exception Doctrine\Common\Proxy\Exception\UnexpectedValueException: "Your proxy directory "var/cache/prod/doctrine/orm/Proxies" must be writable"
modifierSi cela se produit avec APP_ENV=prod
et pas dev
dans le .env :
rm -Rf var/cache/ var/log
Uncaught Symfony\Component\Debug\Exception\UndefinedFunctionException: Attempted to call function "apc_fetch"
modifier sudo pecl install apcu
sudo pecl install apcu_bc
sudo apt-get install -y php7.2-apcu php7.2-apcu-bc
Unexpected non-iterable value for to-many relation
modifierModifier la déclaration du champ en erreur avec un itérable. Ex :
public $mesObjets = new ArrayCollection();
S'il n'y a pas de champ en erreur, il faut le retrouver avec dd($type)
dans AbstractItemNormalizer.
Unknown database
modifierLancer doctrine:database:create :
bin/console d:d:c
Puis rajouter les éventuelles tables :
bin/console doctrine:schema:update --force
Unrecognized field (ORMException)
modifierSe produit quand un findBy de repository ne trouve pas un champ de son entité. Cela peut être résolu avec :
bin/console cache:clear
- Vérifier si on ne recherche pas à tort une valeur sans sa clé (si Unrecognized field: 0).
- Passer par un QueryBuilder plutôt que par un find.
- Ne pas faire hériter le repo de ServiceEntityRepository.
WARNING [cache] Failed to save key... "cache-adapter" => "Symfony\Component\Cache\Adapter\ApcuAdapter"
modifierAjouter apc.enable_cli=1
dans php.ini.
DoctrineMigrations
modifierS'il n'exécute pas tout (sans logs même en -vvv)
modifierSéparer en plusieurs migrations, notamment les créations de tables et de fonctions.
The schema provider is not available
modifierRemplacer "connection" par "em" dans doctrine_migrations.yaml :
doctrine_migrations:
em: default
Syntax error or access violation
modifierIl faut probablement échapper des caractères, par exemple avec la syntaxe heredoc ou $this->connection->quote().
API Platform
modifierInvalid IRI
modifierAjouter le getId() dans l'entité récupérée par IRI.
InvalidArgumentException: "No item route associated with the type xxx
modifierSe produit quand on a pas une route POST sans route GET[23], ou si le GET n'a pas d'ID pour créer des URI. Il faut donc en créer une, mais pas forcément besoin de créer un contrôleur :
* @ApiResource(
* itemOperations={
* "get"={
* "method"="GET",
* "controller"=NotFoundAction::class,
* "read"=false,
* "output"=false,
* },
* },
* )
No identifiers defined for resource of type
modifier /**
* @ApiProperty(identifier=true)
*/
private $id;
Si aucun ID n'est possible, en renvoyer "1" par exemple.
Unable to generate an IRI for
modifierEn général, rajouter un getId() dans l'entité.
The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary with the \"api_platform.eager_loading.max_joins\" configuration key, or limit the maximum serialization depth using the \"enable_max_depth\" option of the Symfony serializer
modifierSur MySQL (ou MariaDB) il existe un maximum de 64 jointures. Donc s'il n'est pas possible d'augmenter max_joins, il faut limiter les jointures par les deux annotations suivantes :
* @API\ApiSubresource(maxDepth=1)
* @ORM\OneToMany(targetEntity="MonEntité", mappedBy="monChamp", fetch="EXTRA_LAZY")
Twig
modifierUn template Twig ne se rafraichit pas dans la navigateur
modifierEn local, dans .env, passer de APP_ENV=prod
à APP_ENV=dev
.
Sinon vider le cache Symfony.
A hash key must be followed by a colon (:)
modifierIl faut probablement mettre des parenthèses autour des variables dans un merge de tableau. Ex :
myArray|merge([{(myKey): (myValue)}])
Array to string conversion
modifierPlusieurs solutions sont possibles pour afficher un tableau dans un template JSON.
- Pour avoir un tableau, ajouter (et retirer les guillemets autour de la valeur si besoin) :
"my_key": {{ my_value |json_encode(constant('JSON_PRETTY_PRINT'))|raw }},
voire :
"my_key": {{ my_value |join(', ') }},
- Pour avoir un objet en chaine de caractères, faire le json_encode en amont puis laisser les guillemets :
"my_key": "{{ my_value }}",
double quoted property
modifierUne virgule de trop après une clé.
key \"id\" for array with keys \"0\" does not exist.
modifierAppel d'une clé absente d'un tableau.
The CSRF token is invalid. Please try to resubmit the form
modifierDans le formulaire concerné, ajouter la ligne :
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
...
'csrf_protection' => false,
]);
}
unexpected token "punctuation" of value "{"
modifierAu choix :
- Ajouter les apostrophes aux clés du tableau concerné.
- Ajouter des parenthèses à ses expressions.
- Remplacer les accolades par des crochets pour le premier niveau.
xmlParseEntityRef: no name
modifierUn "&" n'est pas échappé en XML. Il faut ajouter un filtre "| escape" en Twig pour le faire.
PhpStorm
modifierLes logs sont accessibles par :
tail -f ~/.PhpStorm2019.2/system/log/idea.log
Certains dossiers sont rouges (exclus) dans la navigation et l'indexation, alors qu'ils ne sont pas censés l'être
modifierFermer le projet, supprimer le .idea, et le rouvrir.
Impossible de CTRL + clic dans un .twig (chemin introuvable)
modifierVérifier que le plugin Symfony de PhpStorm est bien installé et activé.
Si cela persiste, dans File\Settings\PHP\Symfony\Twig \ Template, ajouter le chemin vers les .twig importés du bundle concerné.
L'historique Git d'un dossier est tronqué
modifierSi git log .
montre plus de commits que le clic droit / Git / Show history, alors cliquer sur Files / Invalidate Caches, et tout cocher.
Si cela ne fonctionne pas après redémarrage, et que le projet fait partie d'un groupe de projets ouverts, le fermer (clic droit dessus et "Remove from project view"), puis le rouvrir.
Dans l'onglet Git, on ne voit pas la liste des fichiers modifiés
modifierDans Settings, Version Control, Commit, décocher "Use non-modal commit interface".
Un fichier a disparu des onglets, a un icône de point d'interrogation, et ne peut être rouvert
modifierIl n'est pas ou plus associé à un type de fichier, aller dans File\Associate with File Type.
incoming connection from xdebug
modifierLors d'une erreur au remplissage de cette pop-up, on peut la corriger dans .idea/workspace.xml.
No differences files that files have differences only in line separators
modifierLes scripts lancés depuis PhpStorm sous Windows modifient les retours à la ligne (CLRF to LF).
Changer l'option dans Settings/Preferences | Editor | Code Style | Line separator[24] vers "System dependent".
Sinon, configurer git :
git config --global core.autocrlf true
Undefined class xxx
modifierUne classe existe mais PhpStorm ne la voit pas : ajouter son dossier dans File\Settings\Directories, retirer l'exclusion (rouge) du dossier concerné.
Xdebug
modifierDes logs Xdebug sont ajoutables dans php.ini :
xdebug.remote_log = /var/www/xdebug.log
xdebug.show_error_trace = 1
; Profiling (enable via cookie or GET/POST variable: XDEBUG_PROFILE=1).
xdebug.profiler_enable = 1
xdebug.profiler_enable_trigger = 0
xdebug.profiler_output_dir = /tmp/
; var_dump() settings.
xdebug.overload_var_dump = 1
xdebug.cli_color = 1
En CLI, la console s'ouvre sur default_prepend.php et on ne voit pas l'exécution de la commande
modifierCela se produit quand plusieurs projets sont ouverts dans PhpStorm et que la commande n'appartient pas au principal.
Le navigateur déclenche bien le débogage pas à pas, mais on ne voit pas le fichier PHP dans l'IDE
modifierIl manque le mapping (par exemple avec les routes du conteneur). Par exemple dans PhpStorm, Servers, cocher "Use path mappings" et le renseigner.
Messages d'erreur
modifierCannot find file '/usr/local/php/php/auto_prepends/default_prepend.php' locally
modifierSous PhpStorm avec Docker, cliquer sous l'erreur pour modifier le path mapping.
Code coverage needs to be enabled in php.ini by setting 'xdebug.mode' to 'coverage'
modifierPour éviter d'éditer le .ini, on peut remplacer :
bin/phpunit --coverage-text
par :
php -dxdebug.mode=coverage bin/phpunit --coverage-text
Could not connect to client
modifierLe serveur est introuvable, changer :
- En V2, xdebug.remote_host ou remote_port.
- En V3, xdebug.client_host ou xdebug.client_port.
Gateway Timeout
modifierCette erreur du navigateur généralement due au serveur Web.
- Sur Apache, dans httpd.conf augmenter le nombre de secondes par défaut dans
Timeout 60
[25]. - Sur Nginx, augmenter
fastcgi_read_timeout 60s;
[26]. - Sur IIS, étendre la valeur du paramètre Activity Timeout des FastCGI Settings[27].
- Cela peut aussi provenir d'un load balancer en amont du serveur HTTP.
De plus, on peut aussi revoir les variables du php.ini. Ex :
max_execution_time=30 # 30 s par défaut
max_input_time=-1 # Utilisera "max_execution_time" si -1, sinon la valeur indiquée
Failed to read FastCGI header
modifierSi le log Apache affiche cela lors d'un Gateway Timeout, il faut ajouter à httpd.conf[28] :
ProxyTimeout 6000
<IfModule mod_fcgi.c>
FcgidProcessLifeTime 6000
FcgidBusyTimeout 6000
FcgidConnectTimeout 6000
FcgidIdleTimeout 6000
FcgidInitialEnv 6000
FcgidIOTimeout 6000
IdleTimeout 6000
IPCConnectTimeout 6000
IPCCommTimeout 6000
IdleScanInterval 6000
</IfModule>
Parfois il convient aussi de modifier le php.ini[29] :
opcache.optimization_level=0xFFFFFBFF
xdebug.remote_cookie_expire_time=6000
Enfin, sur PhpStorm on rencontre cela dans le cas de sous-requêtes qui dépassent la valeur du paramètre "Max. simultaneous connections".
No code coverage driver is available
modifierInstaller ou activer Xdebug. Il doit apparaitre ensuite dans php -v
.
Remote file path 'default_prepend.php' is not mapped to any file path in project
modifierSous PhpStorm il faut décocher dans les settings, debug, les deux cases Force break..., puis de redémarrer l'IDE[30].
Time-out connecting to client
modifierChanger xdebug.remote_host ou xdebug.remote_port car le serveur existe mais n'écoute pas le port spécifié.
Si le navigateur ne déclenche plus le débogage sur l'IDE, alors que ce dernier écoute et que la clé du navigateur est bien définie, c'est peut-être le pare-feu qui bloque. Exemple de reset sur Linux avec iptables[31] :
iptables --policy INPUT ACCEPT;
iptables --policy OUTPUT ACCEPT;
iptables --policy FORWARD ACCEPT;
iptables -Z; # zero counters
iptables -F; # flush rules
iptables -X; # delete all extra chains
Waiting for incoming connection with ide key 'xxx'
modifierXdebug peut marcher pour certains sites, sauf un qui ne déclenche rien dans l'IDE. Ce message apparait alors dans PhpStorm si on clique sur "Debug".
- Vérifier le serveur et son port) associé à l'URL[32].
- Voir la page Programmation PHP/Xdebug.
GraphQL
modifierFields "maRoute" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.
modifierSi on appelle plusieurs fois la même route dans la même requête, il faut définir des alias. Ex :
mutation {
maRoute(...)
aliasMaRoute: maRoute(...)
}
Le résultat de la requête sera donc un tableau avec ['data' => [[maRoute => ...], [aliasMaRoute => ...]].
Windows
modifierimagecreatefromstring(): gd-png: libpng warning: Interlace handling should be turned on when using png_read_image
modifierRemplacer le php_gd2.dll du PHP 7.4.33 par celui du 7.4.2 téléchargé depuis https://www.pconlife.com/viewfileinfo/php-gd2-dll/.
Exemple du chemin par défaut :
C:\wamp64\bin\php\php7.4.33\ext\
Références
modifier- ↑ http://windows.php.net/download/
- ↑ https://stackoverflow.com/questions/9973555/setting-max-input-vars-php-ini-directive-using-ini-set
- ↑ https://stackoverflow.com/questions/804571/how-to-subtract-two-dates-ignoring-daylight-savings-time-in-php
- ↑ https://stackoverflow.com/questions/31115982/malformed-utf-8-characters-possibly-incorrectly-encoded-in-laravel
- ↑ https://whynhow.info/17522/How-to-get-rid-of-SIGBUS-when-running-php-fpm?
- ↑ https://www.kinamo.fr/fr/support/faq/determiner-le-nombre-de-processes-valide-pour-php-fpm-sur-nginx
- ↑ https://www.php.net/manual/fr/function.fopen.php
- ↑ http://stackoverflow.com/questions/4636166/only-variables-should-be-passed-by-reference
- ↑ https://stackoverflow.com/questions/24443318/getting-error-no-driver-found-to-handle-vcs-repository-on-composer-and-svn?answertab=votes#tab-top
- ↑ https://getcomposer.org/doc/articles/http-basic-authentication.md
- ↑ https://github.com/magento/magento2/issues/2523#issuecomment-159884152
- ↑ https://symfony.com/doc/current/console/verbosity.html
- ↑ https://silex.symfony.com/doc/2.0/cookbook/json_request_body.html
- ↑ https://symfony.com/doc/current/bundles/configuration.html
- ↑ https://symfony.com/doc/current/service_container/autowiring.html
- ↑ https://symfony.com/doc/5.4/components/http_foundation.html
- ↑ https://www.it-swarm.dev/fr/php/service-autowire-impossible-largument-fait-reference-la-classe-mais-ce-service-nexiste-pas/836794307/
- ↑ https://cilefen.github.io/symfony/2016/12/29/doctrine-orm-nulls.html
- ↑ https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/inheritance-mapping.html
- ↑ https://openclassrooms.com/forum/sujet/symfony2-collection-erreur-de-namespace
- ↑ https://www.kerstner.at/2014/09/doctrine-2-exception-entitymanager-closed/
- ↑ https://openclassrooms.com/forum/sujet/doctrine-schema-update-impossible
- ↑ https://github.com/api-platform/core/issues/3501
- ↑ https://stackoverflow.com/questions/40470895/phpstorm-saving-with-linux-line-ending-on-windows
- ↑ https://www.h3xed.com/web-development/php-and-apache-504-gateway-timeout-troubleshooting-and-solutions
- ↑ http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_read_timeout
- ↑ https://www.leighton.com/blog/php-debugging-in-phpstorm-6-0-with-xdebug/
- ↑ https://support.plesk.com/hc/en-us/articles/115000064929-Website-is-not-accessible-The-timeout-specified-has-expired-Error-dispatching-request-to
- ↑ https://www.reddit.com/r/drupal/comments/ase67i/for_issue_reference_service_unavailable_error/
- ↑ https://www.jetbrains.com/help/phpstorm/troubleshooting-php-debugging.html
- ↑ https://ubuntuforums.org/showthread.php?t=1381516
- ↑ https://stackoverflow.com/questions/17715128/xdebug-phpstorm-waiting-for-incoming-connection-with-ide-key
GFDL | Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture. |