$this
class
public, protected
ou private
->
(comme en C++)$this
et de l'opérateur ->
<?php
class De {
// face visible du dé (propriété publique)
public int $faceVisible;
// nombre de faces du dé (propriété privée)
private int $nbFaces;
// méthode publique
public function afficheFaceVisible() : void {
echo $this->faceVisible;
}
}
?>
__construct()
new
associé à un appel à un constructeur permet de creer une nouvelle instance de classe (un objet)<?php
class De {
private int $nbFaces;
public int $faceVisible;
// constructeur
public function __construct(int $nbFaces=6) {
$this->nbFaces = $nbFaces;
$this->faceVisible = rand(1, 6);
}
public function getFaceVisible() : int {
return $this->faceVisible;
}
}
// script de test
$instanceDe = new De();
$instanceDe->getFaceVisible();
print_r($instanceDe);
?>
2
De Object ( [faceVisible] => 2 [nbFaces:De:private] => 6 )
__
(double underscore) et réservées par PHP__construct()
: constructeur__destruct()
: destructeur__toString()
: méthode destinée à convertir automatiquement en string un objet__clone()
: méthode décrivant les modalités de clonage d'un objetstatic
permet de définir des variables et des méthodes de classe::
<?php
class De {
public static int $nbInstances = 0;
public int $faceVisible;
private int $nbFaces;
public function __construct(int $nbFaces=6) {
self::$nbInstances++;
$this->nbFaces = $nbFaces;
$this->faceVisible = rand(1, 6);
}
public static function getNbInstances(): int {
return self::$nbInstances;
}
}
echo De::getNbInstances(); // affiche 0
$d= new De();
echo De::getNbInstances(); // affiche 1
?>
extends
parent::
class Depipe extends De {
private int $valeur;
public function __construct(int $nbFaces, int $valeur): int {
parent::__construct($nbFaces); // appel du constructeur de la classe mère
$this->valeur = $valeur;
$this->faceVisible = $valeur;
}
public function getValeur(): int {
return $this->valeur;
}
public function setValeur(int $valeur): void {
$this->valeur = $valeur;
$this->faceVisible = $valeur;
}
// surcharge de De::lancerDe
public function lancerDe(): void {
$this->faceVisible = $this->valeur;
}
}
abstract
permet de faire de telles définitionsinterface
est destiné à leur définitionrequire_once("PieceQuarto.php");
class BoitePieceQuarto {
protected $pq;
public static $compteurVide = 0;
public function __construct(PieceQuarto
$pq = null) {
$this->pq = $pq;
}
array
bool, float, int, string
), au typage des valeurs de retours des méthodes et aux fonctions.
public function getPieceDispo(int
$position) : PieceQuarto
{
return $this->piecesDispos[$position];
}
void
et permet de préfixer les autres types de retour avec ?
afin d'accepter NULL
comme valeur de retour.object
.class User {
public int $id;
public string $name;
}
final
class MyClass
{
const CONSTANT = 'constant value';
function showConstant() {
echo self::CONSTANT . "\n";
}
}
echo MyClass::CONSTANT . "\n";
final
permet d'interdire la surcharge d'une méthode par une classe descendante ou d'interdire d'appliquer un héritage à une classesession_start(): bool
(renvoie toujours TRUE)SID
(identifiant de session généré automatiquement).SID
est une chaîne vide si les cookies sont activéssession_start()
doit être appel avant tout affichage dans le navigateur (utilise l'en-tête de requête HTTP).
$_SESSION
$_SESSION['nomVariable'] = valeur
bool isset(nom)
permet de vérifier si une variable est définie.
void unset(nom)
détruit une variable.void session_unset(void)
détruit toutes les variables de
session.unset($_SESSION['nomvariable']
<?php
session_start();
$nom = &$_SESSION['nom'];
?>
<?php
if (!isset($_SESSION['heure'])) {
$date = getdate();
$heure = $date['hours'].":".$date['minutes'];
$_SESSION['heure']= $heure;
}
echo $_SESSION['heure']; ?>
<?php
if (isset($_SESSION['heure']))
echo $_SESSION['heure']; ?>
session_destroy()
détruit toutes les données associées à la session courante.
<?php
session_destroy();
if (isset($_SESSION['heure']))
echo 'Les variables de session restent accessibles malgré le session_destroy ! '. $_SESSION['heure'];
else
echo "Plus de date en mémoire après le reload.";
?>
session_id()
ou parfois avec la constante SID
session_id(string $identifiant)
remplace l'identifiant généré automatiquement par l'argument. session_start()
session_cache_expire ( [int new_cache_expire]): int
session_cache_limiter(string cache_policy): string
pour identifier le limiteur de cache utilisé par le navigateur (none, nocache, private, private_no_expire, public)session_get_cookie_params(void): array
: retourne un tableau contenant
les données associé au cookie de la session courante
session_set_cookie_params(int lifetime [,string path [,string domain [,bool secure]]]): void
session_start()
session_write_close()
: mémorise les données de session et termine la session courante.session_encode()
: retourne une chaine linéarisée des variables de sessionsession_decode()
: décode une chaine et alimente $_SESSIONsession_abort()
/**
* Classe Carte
* @author Dominique Fournier
* @date 2017-2022
* @version 1.3 compatible php7.4
*/
class Carte
{
/**
* $hauteur : entier ∈ [0;12] indiquant la hauteur de la carte
* @access private
* @var int
*/
private int $hauteur;
/**
* $couleur : entier ∈ [0,3] indiquant la couleur de la carte
* @access private
* @var int
*/
private int $couleur;
/**
* @access public
* @param int $couleur l'entier associé à la couleur
* @param int $hauteur l'entier associé à la hauteur
*/
public function __construct(int $couleur, int $hauteur)
{
$this->couleur = $couleur;
$this->hauteur = $hauteur;
}
/**
* @access public
* @return int $this->hauteur
*/
public function getHauteur(): int
{
return $this->hauteur;
}
/**
* @access public
* @param int $hauteur
*/
public function setHauteur(int $hauteur): void
{
$this->hauteur = $hauteur;
}
/**
* @access public
* @return int $this->couleur
*/
public function getCouleur(): int
{
return $this->couleur;
}
/**
* @access public
* @param int $couleur
*/
public function setCouleur(int $couleur): void
{
$this->couleur = $couleur;
}
/**
* @access public
* @param $c une instance de Carte à comparer avec $this
* @return la différence de hauteur entre la carte appelante et la carte appelée
*/
public function compareCarte(Carte $c) : int {
return $this->hauteur - $c->hauteur;
}
/**
* @access public
* @return string une chaîne de caractères résumant $this
*/
public function __toString(): string
{
return "(couleur:".$this->couleur.", hauteur:".$this->hauteur.")";
}
}
/**
* Classe BoiteCarte
*
* Classe permettant de représenter une carte dans un span HTML
* @author Dominique Fournier
* @date 2017-2022
* @version 1.3 compatible PHP7.4
*/
require_once 'Carte.php';
class BoiteCarte {
/**
* Classe destinée à produire un composant HTML pour afficher une carte
* @static
* @access public
* @var array (string[])
*/
public static $COULEURS = array("spades","hearts","clubs","diams");
/**
@static
@access public
@var array (string[])
*/
public static $HAUTEURS= array("2","3","4","5","6","7","8","9","10","J","Q","K","A");
/**
* $carte une instance de Carte
* @access protected
* @var Carte
*/
protected Carte $carte;
/**
* @access public
* @param Carte $c la carte à afficher dans la boite html
*/
public function __construct(Carte $c) {
$this->carte = $c;
}
/**
* @access public
* @return Carte $this->carte
*/
public function getCarte() : Carte {
return $this->carte;
}
/**
* @access public
* @return string un span html représentant $this->carte
*/
public function __toString(): string {
$nomCouleur = self::$COULEURS[$this->carte->getCouleur()];
switch ($nomCouleur) {
case "spades":
case "clubs":
$col = "black";
break;
case "hearts":
case "diams":
$col = "red";
break;
default:
$col="green";
}
$resultat = "";
if ($this->carte->getHauteur() != 8) {
$resultat .= " ";
}
$resultat .= self::$HAUTEURS[$this->carte->getHauteur()].":&".$nomCouleur.";";
$resultat .= " ";
return $resultat;
}
}
/**
* Classe JeuCartes
*
* Représentation d'un jeu de 52 cartes
* @author Dominique Fournier
* @date 2017-2022
* @version 1.3 compatible PHP7.4
*/
require_once("Carte.php");
class JeuCartes {
/**
* $paquet : un tableau de Cartes
* @access private
* @var array (Carte[])
*/
private array $paquet;
/**
* $taille : le nombre de cartes contenu dans le paquet
* @access private
* @var int
*/
private int $taille;
/**
* self::NB_COULEURS : le nombre de couleurs (catégories de Cartes)
* @access public
* @const int
*/
const NB_COULEURS = 4; //array("spades","hearts","clubs","diams");
/**
* @access public
* @param int $t le nombre de cartes du paquet (multiple de 4)
*/
public function __construct($t = 52) {
$this->taille = $t;
$this->paquet = array();
for($i=0;$i<self::NB_COULEURS;$i++) {
for($j=0;$j<$t/self::NB_COULEURS;$j++) {
$c = new Carte($i,$j);
$this->addCarte($c);
}
}
}
/**
* ajoute une carte au paquet uniquement lors de l'initialisation (private?)
* @access public
* @param Carte $c la Carte à ajouter
*/
public function addCarte(Carte $c): void {
$this->paquet[sizeof($this->getPaquet())] = $c;
}
/**
* @access public
* @param int $pos la position de la carte dans le paquet
* @return Carte $this->paquet[$pos];
*/
public function getCarte($pos): Carte {
if ($pos >=0 && $pos < sizeof($this->paquet))
return $this->paquet[$pos];
else
exit("Indice de carte non valide :". $pos);
}
/**
* @access public
* @return array $this->paquet;
*/
public function getPaquet(): array {
return $this->paquet;
}
/**
* @access public
* @param int $pos
* @param Carte $c
*/
public function setCarte($pos, Carte $c): void {
if ($pos >=0 && $pos < $this->getTaille()) {
$this->paquet[$pos] = $c;
}
else {
exit("Indice de carte non valide :". $pos);
}
}
/**
* @access public
* @return int $this->taille;
*/
public function getTaille(): int {
return $this->taille;
}
/**
* @access public
* @param int $t
*/
public function setTaille(int $t): void {
$this->taille = $t;
}
/**
* @return void
* @access public
*/
public function melangePaquet(): void {
for ($i=0;$i<max(3,$this->getTaille()/10);$i++) {
$this->coupePaquet();
$this->riffleShuffe();
}
}
/**
* simulation de mélange type 'riffle-shuffle'
* @access private
* @return void
*/
private function riffleShuffe(): void {
$tabTmp1 = array();
$tabTmp2 = array();
for ($i=0;$i<$this->getTaille()/2;$i++) {
$tabTmp1[$i] = $this->getCarte($i);
$tabTmp2[$i] = $this->getCarte($i+$this->getTaille()/2);
}
for ($i=0;$i<$this->getTaille()/2;$i++) {
$this->setCarte($i*2,$tabTmp1[$i]);
$this->setCarte(($i*2)+1,$tabTmp2[$i]);
}
}
/**
* @access public
* @return void
*/
public function coupePaquet(): void {
$coupe = rand(1,$this->getTaille()-2);
$tabTmp = array();
for ($i=$coupe,$j=0;$i<$this->getTaille();$i++,$j++) {
$tabTmp[$j] = $this->getCarte($i);
}
for ($i=$coupe-1,$j=1;$i>=0;$i--,$j++) {
$this->setCarte($this->getTaille()-$j,$this->getCarte($i));
}
for ($i=0;$i<sizeof($tabTmp);$i++) {
$this->setCarte($i,$tabTmp[$i]);
}
}
/**
* @access public
* @return string une chaîne résumant le paquet
*/
public function __toString(): string {
$resultat = "[";
for($j=0;$j<$this->getTaille();$j++) {
$c = $this->getCarte($j);
$resultat .= $c->__toString();
}
return $resultat .= "]";
}
/**
* @access public
* @return Carte
*/
public function distribueCarte(): Carte {
$c = array_pop($this->paquet);
$this->setTaille($this->getTaille()-1);
return $c;
}
/**
* @access public
* @param Carte
*/
public function remiseCarte(Carte $c) {
array_unshift($this->paquet,$c);
$this->setTaille($this->getTaille()+1);
}
}
/**
* Classe Main
*
* @author Dominique Fournier
* @date 2017-2022
* @version 1.3 compatible PHP7.4
*/
require_once("Carte.php");
class Main {
/**
* $cardList : un tableau de 5 Cartes
* @access private
* @var array (Carte[])
*/
private array $cardList;
/**
* @access public
* initialise $this->cardList avec un tableau vide
*/
public function __construct() {
$this->cardList = array();
}
/**
* méthode ajoutant une carte à la main
* @access public
* @param Carte $c une instance de Carte
* @return void
*/
public function addCarte(Carte $c) {
$this->cardList[sizeof($this->cardList)] = $c;
}
/**
* @access public
* @param int $i la position de la carte dans $this->cardList
* @return Carte $this->cardList[$index];
*/
public function getCarte(int $index): Carte {
if ($index >=0 && $index < sizeof($this->cardList)) {
return $this->cardList[$index];
}
exit("Indice de carte non valide :". $index);
}
/**
* @access public
* @return int sizeof($this->cardList);
*/
public function getTailleMain(): int {
return sizeof($this->cardList);
}
/**
* @access public
* @return string une chaine résumant la main
*/
public function __toString(): string {
$resultat = "[";
for($j=0;$j<$this->getTailleMain();$j++) {
$c = $this->getCarte($j);
$resultat .= $c->__toString();
}
return $resultat .= "]";
}
/**
* @access public
* @param int $index
* @param Carte $c
* @return void
*/
public function setCarte($index, Carte $c):void {
if ($index<sizeof($this->cardList)) {
$this->cardList[$index] = $c;
}
}
/**
* méthode triant la main selon la hauteur des cartes
* @access public
* @return void
*/
public function triMain(): void {
sort($this->cardList);
}
/**
* méthode calculant la signature d'une main
* la signature correspond à la séquence constitutée des différences
* de hauteurs des cartes consécutives
* @access public
* @return string la chaîne représentant la signature
*/
public function signature(): string {
$signature = "";
for ($i=1; $i<sizeof($this->cardList); $i++) {
$diff = $this->getCarte($i)->getHauteur()-$this->getCarte($i-1)->getHauteur();
switch ($diff) {
case 12 :
$signature.= "C"; break;
case 11 :
$signature.= "B"; break;
case 10 :
$signature.= "A"; break;
default :
$signature.= $diff;
}
}
return $signature;
}
/**
* @access private
* @return array le résumé les effectifs de chaque couleur
*/
private function couleurs(): array {
$couleurs = array(0,0,0,0,0);
for ($i=0; $i<sizeof($this->cardList); $i++) {
$couleurs[$this->getCarte($i)->getCouleur()] +=1;
}
return $couleurs;
}
/**
* @access public
* @return int le niveau de la combinaison
*/
public function combinaison(): int {
$signature = $this->signature();
$nbZeros = substr_count($signature,"0");
$nbUns = substr_count($signature,"1");
if ($nbUns == 4 || $signature == "1119") {
if (in_array(5,$this->couleurs())) {
return 8; // Quinte flush
}
return 4; // Quinte;
}
else {
if (in_array(5,$this->couleurs())) {
return 5; // Couleur;
}
else {
if ($nbZeros == 3) {
if ($signature[0] != '0' || $signature[3] != '0') {
return 7; // Carre;
}
else {
return 6; // Full;
}
}
else {
if ($nbZeros == 2) {
if (substr_count($signature,"00")) {
return 3; //"Brelan";
}
else {
return 2; //"Double Paire";
}
}
else {
if ($nbZeros == 1) {
return 1; //"Paire";
}
}
}
}
return 0; //"Carte isolee";
}
}
/**
* @access public
* @return string le nom de la combinaison
*/
public function nomCombinaison(): string {
switch ($this->combinaison()) {
case 8 :
return "Quinte flush";
case 7 :
return "Carre";
case 6 :
return "Full";
case 5 :
return "Couleur";
case 4 :
return "Suite";
case 3 :
return "Brelan";
case 2 :
return "Double Paire";
case 1 :
return "Paire";
default :
return "Carte Isolee";
}
}
/**
* @access public
* @return array
*/
public function evalueMain(): array {
$signature = $this->signature();
$combinaison = $this->combinaison();
$evaluation = array();
$indEval = 0;
$evaluation[$indEval++] = $combinaison;
switch($combinaison) {
case 8 : // Quinte Flush
case 5 : // Couleur
case 4 : // Suite
case 0 : // Carte Isolee
for($i=4;$i>=0;$i--)
$evaluation[$indEval++]=$this->cardList[$i]->getHauteur();
break;
case 6 : // Full
$pos = strpos($signature,"00");
$evaluation[$indEval++]=$this->cardList[$pos]->getHauteur();
// AAABB 0 AABBB 2
break;
case 7: // Carre
$pos = strpos($signature,"000");
$evaluation[$indEval++]=$this->cardList[$pos]->getHauteur();
break;
case 3: // Brelan
$pos = strpos($signature,"00");
$evaluation[$indEval++]=$this->cardList[$pos]->getHauteur();
break;
case 2: // Double Paire
$pos1 = strrpos($signature,"0");
$evaluation[$indEval++]=$this->cardList[$pos1]->getHauteur();
$pos2 = strpos($signature,"0");
$evaluation[$indEval++]=$this->cardList[$pos2]->getHauteur();
for($i=4;$i>=0;$i--)
if ($i != $pos1 && $i != ($pos1+1) && $i != $pos2 && $i != ($pos2+1))
$evaluation[$indEval++]=$this->cardList[$i]->getHauteur();
break;
case 1: // Paire
$pos = strpos($signature,"0");
$evaluation[$indEval++]=$this->cardList[$pos]->getHauteur();
for($i=4;$i>=0;$i--)
if ($i != $pos && $i != ($pos+1))
$evaluation[$indEval++]=$this->cardList[$i]->getHauteur();
break;
default:
echo "BUG";
}
return $evaluation;
}
/**
* @param Main $m
* @access public
* @return int un entier comparant la valeur de $this et $m
*/
public function compareMain(Main $m): int {
$evalThis = $this->evalueMain();
$evalM = $m->evalueMain();
$parcours = 0;
while($evalThis[$parcours] == $evalM[$parcours] && $parcours < sizeof($evalThis)) {
$parcours++;
}
return $evalThis[$parcours] - $evalM[$parcours];
}
}
?>
/**
* Classe PokerUI
*
* @author Dominique Fournier
* @date 2017-2022
* @version 1.3 compatible PHP7.4
*/
include_once "JeuCartes.php";
include_once "Main.php";
class PokerUI {
/**
* @access private
* @var JeuCartes
*/
private JeuCartes $paquetCartes;
/**
* @access private
* @var Main
*/
private $main;
/**
* @access public
*/
public function __construct(){
$this->paquetCartes = new JeuCartes();
$this->paquetCartes->melangePaquet();
$this->main = new Main();
$this->main->addCarte($this->paquetCartes->distribueCarte());
$this->main->addCarte($this->paquetCartes->distribueCarte());
$this->main->addCarte($this->paquetCartes->distribueCarte());
$this->main->addCarte($this->paquetCartes->distribueCarte());
$this->main->addCarte($this->paquetCartes->distribueCarte());
$this->main->triMain();
}
/**
* @access public
* @param array les indices des cartes à renouveler
* @return void
*/
public function renouvelleMain(array $indexes): void {
foreach ($indexes as $index) {
$c = $this->main->getCarte($index);
$this->main->setCarte($index,$this->paquetCartes->distribueCarte());
$this->paquetCartes->remiseCarte($c);
}
$this->main->triMain();
}
/**
* @access public
* @return void
*/
public function affichePaquetCarte():void {
$bjc = new BoiteJeuCartes($this->paquetCartes);
echo $bjc;
}
/**
* @access public
* @return void
*/
public function afficheMain(): void {
echo new BoiteMain($this->main);
}
/**
* @access public
* @return void
*/
public function afficheFormulaireMain(): void {
echo "<form action='".$_SERVER['PHP_SELF']."' method='get'>
<div class='main'><fieldset><legend>Formulaire de changement de cartes</legend>
<input type='checkbox' name='main[]' value='0' />";
echo new BoiteCarte($this->main->getCarte(0));
echo "<input type='checkbox' name='main[]' value='1' />\n";
echo new BoiteCarte($this->main->getCarte(1));
echo "<input type='checkbox' name='main[]' value='2' />\n";
echo new BoiteCarte($this->main->getCarte(2));
echo "<input type='checkbox' name='main[]' value='3' />\n";
echo new BoiteCarte($this->main->getCarte(3));
echo "<input type='checkbox' name='main[]' value='4' />\n";
echo new BoiteCarte($this->main->getCarte(4));
echo "<input type='submit' value='changer' name='action' />\n";
echo "<input type='submit' value='finir' name='action' />\n";
echo "<input type='submit' value='raz' name='action' />\n";
echo "</div></form>\n";
}
/**
* @static
* @access public
* @retrun void
*/
public static function afficheMessage($message): void {
echo "<h3>$message</h3>\n";
}
/**
* @access public
* @return void
*/
public static function afficheLienRecommencer(): void {
echo "<p><a href='".$_SERVER['PHP_SELF']."'>Recommencer ?</a></p>\n";
}
}
include_once "BoiteJeuCartes.php";
include_once "BoiteMain.php";
include_once "PokerUI.php";
function afficheDebutHTML() {
echo "<!doctype html>
<html lang='fr'>
<head>
<meta charset='UTF-8'>
<title>Poker</title>
<link rel='stylesheet' type='text/css' href='poker.css' />
</head>
<body>
<div class='poker'>
<h1>Manipuler une main de 5 cartes au Poker</h1>\n";
}
function afficheFinHTML() {
echo "</div>
</body>
</html>\n";
}
// initialisation ou entretien de la session
session_start();
// on peut commencer à afficher le début de la page web
afficheDebutHTML();
// Vérification de la situation de la session
// premier passage l'objet gerant l'UI dans la session n'existe pas
if (!isset($_SESSION['UI'])) {
$_SESSION['UI' ] = new PokerUI();
$UI = &$_SESSION['UI'];
PokerUI::afficheMessage("Première main");
// on affiche la main et propose de changer des cartes
$UI->afficheMain();
$UI->afficheFormulaireMain();
}
else {
// l'application est déjà lancée
// création d'une variable locale au script
$UI = &$_SESSION['UI'];
// une partie est déjà commencée
if (isset($_GET) && isset($_GET['action'])) {
switch ($_GET['action']) {
case 'changer':
PokerUI::afficheMessage("Ancienne main");
$UI->afficheMain();
if (isset($_GET['main']) && is_array($_GET['main'])) {
// on procède au changement de cartes
$UI->renouvelleMain($_GET['main']);
}
PokerUI::afficheMessage("Nouvelle main");
$UI->afficheMain();
$UI->afficheFormulaireMain();
break;
case 'finir':
$UI->afficheMain();
session_unset();
session_destroy();
PokerUI::afficheLienRecommencer();
break;
case 'raz':
// cas de la remise à zéro : une variable de formulaire transmet l'ordre
session_unset();
session_destroy();
PokerUI::afficheLienRecommencer();
break;
default :
echo "Une action imprévue a été demandée";
}
}
}
// Quoi qu'il arrive on affiche l'état du jeu
// fonctionne meme quand action=raz !
PokerUI::afficheMessage("État du paquet");
$UI->affichePaquetCarte();
// on termine l'affichage de la page html
afficheFinHTML();
?>
GET /fichier.html
et qui retourne exclusivement une page HTMLGET
demande une représentation de la ressource spécifiée. Les requêtes GET doivent uniquement être utilisées afin de récupérer des données.POST
est utilisée pour envoyer une entité vers la ressource indiquée. Cela entraîne généralement un changement d'état ou des effets de bord sur le serveur.HEAD
demande une réponse identique à une requête GET pour laquelle on aura omis le corps de la réponse (on a uniquement l'en-tête).PUT
remplace toutes les représentations actuelles de la ressource visée par le contenu de la requête.DELETE
supprime la ressource indiquée.CONNECT, OPTIONS, TRACE, PATCH
... nomEntete: valeur
Host: tools.ietf.org User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Referer: http://localhost/~dominique/PHP/coursHTTP.php?page=3 Connection: keep-alive Upgrade-Insecure-Requests: 1
Date: Sun, 18 Feb 2018 20:17:01 GMT Server: Apache/2.2.22 (Debian) Content-Location: rfc4229.html Vary: negotiate,Accept-Encoding TCN: choice Last-Modified: Sun, 11 Feb 2018 09:01:19 GMT ETag: "3ca206-220af-564ec013715c0;56582426c5330" Accept-Ranges: bytes Cache-Control: max-age=604800 Expires: Sun, 25 Feb 2018 20:17:01 GMT Content-Encoding: gzip Strict-Transport-Security: max-age=3600 X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Clacks-Overhead: GNU Terry Pratchett Content-Length: 16953 Content-Type: text/html; charset=UTF-8
$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD /~dominique/PHP/index.php HTTP/1.0
Accept-language: fr,en
Accept-Encoding: utf-8
HTTP/1.1 200 OK Date: Sun, 18 Feb 2018 21:19:05 GMT Server: Apache/2.4.18 (Ubuntu) Connection: close Content-Type: text/html; charset=UTF-8 Connection closed by foreign host.
404
...100 Continue
Jusqu'ici tout va bien...101 Switching Protocol
200 OK
201 Created
204 No Content
301 Moved Permanently
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
418 I'm a teapot
Voir RFC 2324500 Internal Server Error
502 Bad Gateway
503 Service Unavailable
void header ( string $header [, bool $replace = TRUE [, int $http_response_code ]] )
if (isset($_GET['coffee'])) {
header("HTTP/1.1 418 I'm a teapot");
echo "\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
<html><head>
<title>418 I'm a teapot</title>
</head><body>
<h1>I'm a teapot</h1>
<p>The requested URL <b>". $_SERVER['SCRIPT_NAME']."?". $_SERVER['QUERY_STRING']. "</b> tried to brew coffee with a teapot !</p>
</body></html>";
}
else {
header("HTTP/1.1 301 Moved Permanently");
if (isset($_GET['page'])) {
header('Location: /~dominique/PHP/coursHTTP.php?page=' . $_GET['page']);
}
else
header('Location: /~dominique/PHP/coursHTTP.php?page=0');
}
try {
if (isset($_GET['action'])) {
$dbs1 = $dbh->prepare('INSERT INTO nom(nom) VALUES(:nom)');
$dbs1->bindValue(':nom', $_GET['nom']);
$dbs1->execute();
}
else {
$dbs1 = $dbh->query('DROP TABLE IF EXISTS nom ');
$dbs1 = $dbh->query('CREATE TABLE nom (id integer auto_increment PRIMARY KEY,
nom varchar(25))');
$dbs1 = $dbh->query("INSERT INTO nom(nom) VALUES ('nom 1'),('nom 2'),('nom 3')");
}
$dbs1 = $dbh->query('SELECT * from nom');
$tab = $dbs1->fetchAll($fetch_style = PDO::FETCH_ASSOC);
echo "<h1>Contenu de la table nom</h1>";
echo "<table border='1'>\n";
$colnames = array_keys($tab[0]);
echo "<tr>\n";
foreach ($colnames as $col) {
echo "<th>" . $col . "</th>";
}
echo "</tr>\n";
foreach ($tab as $ligne) {
echo "<tr>\n";
foreach ($ligne as $col) {
echo "<td>" . $col . "</td>";
}
echo "</tr>\n";
}
echo "</table>\n";
} catch (PDOException $e) {
print "<br />Erreur !: " . $e->getMessage() . "<br/>";
die();
}
echo "<h2>Formulaire utilisant la méthode GET</h2>";
echo "<form action='' method=get>
<input type='text' name='nom' />
<input type='submit' name='action' value='inserer'/>
</form>";
switch($_POST['action']) {
case 'action1' :
// TODO réalisation des éditions nécessaires à l'action 1...
$_SESSION['état'] = 'etat1';
break;
case 'action2' :
// TODO réalisation des éditions nécessaires à l'action 2...
break;
default :
// ...
}
switch($_SESSION['état']) {
case 'état1':
// TODO affichage de la page correspondante...
break;
case 'état2':
// TODO affichage de la page correspondante...
break;
default:
// ...
}
header('HTTP/1.1 303 See Other');
header('Location: fichierTraitantLesEtats.php');
exit;
try {
echo file_get_contents('util/debutSkelHtml5.html');
if (!isset($_GET['message'])) {
$dbs1 = $dbh->query('DROP TABLE IF EXISTS nom ');
$dbs1 = $dbh->query('CREATE TABLE nom (id integer auto_increment PRIMARY KEY, nom varchar(25))');
$dbs1 = $dbh->query("INSERT INTO nom(nom) VALUES ('nom 1'),('nom 2'),('nom 3')");
}
$dbs1 = $dbh->query('SELECT * from nom');
$tab = $dbs1->fetchAll($fetch_style = PDO::FETCH_ASSOC);
echo '<h1>Contenu de la table nom</h1>';
echo '<table border='1'>';
$colnames = array_keys($tab[0]);
echo '<tr>';
foreach ($colnames as $col) {
echo '<th>' . $col . '</th>';
}
echo '</tr>';
foreach ($tab as $ligne) {
echo '<tr>';
foreach ($ligne as $col) {
echo "<td> $col </td>";
}
echo '</tr>';
}
echo '</table>\n';
} catch (PDOException $e) {
print '<br />Erreur !: ' . $e->getMessage() . '<br/>';
die();
}
echo '<h2>Formulaire utilisant la méthode POST et traitement par script tiers</h2>';
echo "<form action='actionInserer.php' method='post'>
<input type='text' name='nom' />
<input type='submit' name='action' value='inserer'/>
</form>";
echo file_get_contents('util/finSkelHtml5.html');
try {
if (isset($_POST['action'])) {
$dbs1 = $dbh->prepare('INSERT INTO nom(nom) VALUES(:nom)');
$dbs1->bindValue(':nom', $_POST['nom']);
$dbs1->execute();
}
header('HTTP/1.1 303 See Other');
header('Location: insererForm_v3.php?message=Coucou de la part de actionInserer.php');
exit;
} catch (PDOException $e) {
print '<br />Erreur !: ' . $e->getMessage() . '<br/>';
die();
}
public __construct ( string $dsn [, string $username [, string $password [, array $options ]]] )
dsn
(Data Source Name) précise les informations nécessaire à la connexion à la base de donnée. Il peut s'agir
$dbh = new PDO('mysql:host=localhost;dbname=dbtest', $user, $pass); $dbh = new PDO('pgsql:host=localhost dbname=dbtest user=usertest password=test');
$dbh = new PDO('uri:http://proba.univ-lehavre.fr/~dominique/PHP/test/dsn.txt');
$dbh = new PDO('pgdb
');
PDO::query(string $statement [, ...])
exécute une requête SQL et retourne l'éventuel résultat dans un objet PDOStatement.PDO::exec(string $statement)
exécute une requête SQL et retourne le nombre de lignes affectées (utile pour des requêtes de type DML).PDO::prepare(string $statement [, ...]
initialise une requête qui sera exécutée via l'objet PDOStatement retourné. On peut ainsi exécuter plusieurs fois la même requête avec des paramètres différents.$dbs1 = $dbh->query('SELECT * from Commune');
$nb = $dbh->exec('UPDATE Commune set com_nbvotants = 832
WHERE com_nom=\'Moustan\'');
$dbs2 = $dbh->prepare('SELECT * FROM Commune
WHERE com_nom = ? OR com_dept =?');
$dbs3 = $dbh->prepare('SELECT * FROM Commune
WHERE com_nom = :commune OR com_dept = :dept');
bool PDO::beginTransaction()
: initie une transaction en désactivant l'auto-commit du SGBD.bool PDO::commit()
: valide une transaction et rend effectives les requêtes associées.bool PDO::rollBack()
: annule la transaction courante.bool PDO::inTransaction()
: véfifie si une transaction est active dans le pilote.mixed PDO::errorCode()
:
PDO::exec
array PDO::errorInfo()
: retourne les informations associées à la dernière erreur. Le tableau contient trois informations :
PDO::lastInsertId([ string $nomSeq=NULL ])
: permet de récupérer l'identifiant du dernier enregistrement inséré ou la dernière valeur générée par la séquence nomSeq.PDO::quote(string $string [, int $parameter_type=PDO::PARAM_STR])
: formate une chaîne destinée à être incorporée dans une requête.PDO::getAttribute(int $attribute)
: accède à un attribut de connexion parmi PDO::ATTR_AUTOCOMMIT, PDO::ATTR_CASE, PDO::ATTR_CLIENT_VERSION, ...PDO::setAttribute(int $attribute, mixed $value)
: modifieur correspondant.PDO::getAvailableDrivers()
: renvoie un tableau listant les pilotes disponibles (méthode statique).PDO::prepare
). bool PDOStatement::execute ([array $input_parameters=array() ])
$dbs2 = $dbh->prepare('SELECT * FROM Commune
WHERE com_nom = ? OR com_dept =?');
$dbs2->execute(array("Grobourg","76"));
$dbs3 = $dbh->prepare('SELECT * FROM Commune
WHERE com_nom = :commune OR com_dept = :dept');
$dbs3->execute(array(':commune' => 'Grobourg', ':dept' => 'GRD'));
mixed PDOStatement::fetch ([ int $fetch_style=PDO::FETCH_BOTH [, int $cursor_orientation=PDO::FETCH_ORI_NEXT [, int $cursor_offset=0 ]]])
fetch_style
à choisir parmi les constantes : PDO::FETCH_ASSOC, PDO::FETCH_NUM , PDO::FETCH_BOTH (mode par défaut), PDO::FETCH_OBJ, ... $tabAssocNum = $dbs1->fetch();
$tabNum = $dbs1->fetch(PDO::FETCH_NUM);
$tabAssoc = $dbs1->fetch(PDO::FETCH_ASSOC);
$obj = $dbs1->fetch(PDO::FETCH_OBJ);
array PDOStatement::fetchAll (...)
: retourne dans un tableau toutes les lignes d'un résultat (pratique mais augmente la charge du serveur). mixed PDOStatement::fetchObject ([ string $class_name [, array $ctor_args ]])
: retourne un objet de la classe spécifiée en paramètre.mixed PDOStatement::fetchColumn ([ int $column_number=0 ])
: permet d'accéder colonne par colonne aux différentes valeurs d'un enregistrement.bool PDOStatement::setFetchMode ( ... )
: définit le comportement par défaut de fetch.int PDOStatement::rowCount ()
: retourne le nombre de lignes obtenues lors du dernier appel de la méthode execute()
.int PDOStatement::columnCount ()
: retourne le nombre de colonnes du dernier résultat.string PDOStatement::errorCode()
: similaire à la méthode homonyme de PDOarray PDOStatement::errorInfo()
: idembool PDOStatement::bindParam (mixed $parameter, mixed &$variable [, int $data_type [, int $length [, mixed $driver_options]]])
:bool PDOStatement::bindValue (mixed $parameter, mixed $value [, int $data_type])
:bool PDOStatement::bindColumn (mixed $column, mixed &$param [, int $type[, int $maxlen [, mixed $driverdata]]])
:bool PDOStatement::debugDumpParams ( )
: détaille une commande préparée directement sur la sortie standardtry {
$dbh = new PDO('pgdb');
$dbs1 = $dbh->query('SELECT * from Commune');
...
} catch (PDOException $e) {
print "Erreur !: " . $e->getMessage() . "<br/>";
die();
}
PDOStatement::getColumnMeta
: uniquement avec Postgres, Mysql, Sqlite et DBlibPDOStatement::getAttribute
: spécifique Firebird et ODBCPDOStatement::setAttribute
: spécifique Firebird et ODBCSELECT
if (isset($_POST['table'])) {
$table = $_POST['table'];
try {
include('dsn.php'); // établi une connexion et instancie une variable $dbh
$dbs1 = $dbh->query('SELECT * from '. $table);
// récupère un tableau associatif avec le résultat de la requête
$tab = $dbs1->fetchAll($fetch_style = PDO::FETCH_ASSOC );
echo "<table border='1'>\n";
$colnames = array_keys($tab[0]); // pour le nom des colonnes
echo "<tr>\n";
foreach ($colnames as $col) {
echo "<th>".$col."</th>";
}
echo "</tr>\n";
foreach ($tab as $ligne) {
echo "<tr>\n";
foreach ($ligne as $col) {
echo "<td>".$col."</td>";
}
echo "</tr>\n";
}
echo "</table>\n";
} catch (PDOException $e) {
print "<br />Erreur !: " . $e->getMessage() . "<br/>";
die();
}
}
try {
include('dsn.php');
$dbh->exec('INSERT INTO livre VALUES(117, \'La lune disparue\', NULL)');
$dbh->exec('INSERT INTO livre VALUES(118, \'Retour à jamais\', NULL)');
$dbh->exec('INSERT INTO livre VALUES(119, \'Le visage du démon\', NULL)');
$dbh->exec('INSERT INTO livre VALUES(120, \'Programme spécial\', NULL)');
$nb = $dbh->exec('DELETE FROM livre WHERE liv_num > 116');
echo "<p>$nb enregistrements ont été supprimés.</p>\n";
} catch (PDOException $e) {
print "<br />Erreur !: " . $e->getMessage() . "<br/>";
die();
}
try {
include 'dsn.php';
$dbs3 = $dbh->prepare('SELECT * from livre WHERE liv_num = ? OR liv_titre LIKE ?');
$dbs3->execute(array(100, "Le %"));
$tab = $dbs3->fetchAll($fetch_style = PDO::FETCH_ASSOC );
afficheTable($tab);
} catch (PDOException $e) {
print "<br />Erreur !: " . $e->getMessage() . "<br/>";
die();
}
?>
<form action="<?php echo($PHP_SELF);?>" method="post">
<fieldset>
<legend>Affichage des livres écrits par un auteur :</legend>
<select name="nom">
<?php
include 'dsn.php';
$dbs = $dbh->query('SELECT aut_nom, aut_prenom FROM auteur ORDER BY aut_nom, aut_prenom');
while ($auteur = $dbs->fetch()) {
echo "<option value='".$auteur['aut_nom']."'>".$auteur['aut_nom']." ".$auteur['aut_prenom']."</option>\n";
}
?>
</select>
<input type="submit" name="Afficher" value="afficher" /></fieldset>
</form>
<?php
if (isset($_POST['nom'])) {
$nom = $_POST['nom'];
try {
include 'dsn.php';
$dbs2 = $dbh->prepare('SELECT * from titreauteurs t
WHERE aut_nom1 like :auteur OR aut_nom2 = :auteur');
$dbs2->execute(array("auteur" => $nom));
$tab = $dbs2->fetchAll($fetch_style = PDO::FETCH_ASSOC );
afficheTable($tab);
} catch (PDOException $e) {
print "<br />Erreur !: " . $e->getMessage() . "<br/>";
die();
}
}
?>
try {
include 'dsn.php';
$dbs2 = $dbh->prepare('SELECT * from auteur WHERE aut_nom = :auteur');
$auteur = "Dick";
$dbs2->bindParam(':auteur', $auteur, PDO::PARAM_STR);
$dbs2->execute();
$tab = $dbs2->fetchAll($fetch_style = PDO::FETCH_ASSOC );
afficheTable($tab);
$dbs3 = $dbh->prepare('SELECT * from auteur WHERE aut_nom LIKE ? OR aut_prenom LIKE ?');
$dbs3->bindValue(1, "Dick", PDO::PARAM_STR);
$dbs3->bindValue(2, "Fr%", PDO::PARAM_STR);
$dbs3->execute();
$tab = $dbs3->fetchAll($fetch_style = PDO::FETCH_ASSOC );
afficheTable($tab);
$dbs4 = $dbh->prepare('SELECT * from auteur WHERE aut_id < 5');
$dbs4->execute();
$dbs4->bindColumn(1, $id);
$dbs4->bindColumn(2, $nom);
$dbs4->bindColumn('aut_prenom', $prenom);
while ($ligne = $dbs4->fetch(PDO::FETCH_BOUND)) {
echo "<p>". $id . " " . $nom . " " . $prenom . "</p>\n";
}
} catch (PDOException $e) {
print "<br />Erreur !: " . $e->getMessage() . "<br/>";
die();
}
try {
include 'dsn.php';
$dbs = $dbh->query('SELECT aut_ville from auteur WHERE aut_nom = :auteur');
if ($dbs) {
$tab = $dbs->fetchAll($fetch_style = PDO::FETCH_ASSOC );
afficheTable($tab);
}
else {
echo "<p><b>". $dbh->errorCode() . "</b> : ";
foreach($dbh->errorInfo() as $cle => $val)
echo $cle."=".$val." ";
echo "</p>";
}
} catch (PDOException $e) {
print "<br />Erreur !: " . $e->getMessage() . "<br/>";
die();
}
Associer dans une classe les besoins d'une application et les contraintes provenant du SI. Dans le cas de la base LMSF, on dispose de 4 tables qui dispersent des informations souvent manipulées ensemble, par exemple, la notice d'un livre. Un objet métier devra permettre de faire la synthèse entre la problématique du SI et de l'application, typiquement :
$_ENV['host'] = "ust-infoserv.univlehavre.lan";
$_ENV['user'] = "xy123456";
$_ENV['db'] = "xy123456";
$_ENV['passwdt'] = "***********";
class LivreMetier {
/**
* gestion statique des accès SGBD
* @var PDO
*/
private static $_pdo;
/**
* gestion statique de la requête préparée de selection
* @var PDOStatement
*/
private static $_pdos_select;
/**
* gestion statique de la requête préparée de mise à jour
* @var PDOStatement
*/
private static $_pdos_update;
/**
* gestion statique de la requête préparée de d'insertion
* @var PDOStatement
*/
private static $_pdos_insert;
/**
* gestion statique de la requête préparée de suppression
* @var PDOStatement
*/
private static $_pdos_delete;
/**
* PreparedStatement associé à un SELECT, calcule le nombre de livres de la table
* @var PDOStatement;
*/
private static $_pdos_count;
/**
* PreparedStatement associé à un SELECT, récupère tous les livres
* @var PDOStatement;
*/
private static $_pdos_selectAll;
/**
* Initialisation de la connexion et mémorisation de l'instance PDO dans LivreMetier::$_pdo
*/
public static function initPDO() {
self::$_pdo = new PDO("mysql:host=".$_ENV['host'].";dbname=".$_ENV['db'],$_ENV['user'],$_ENV['passwd']);
// pour récupérer aussi les exceptions provenant de PDOStatement
self::$_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
/**
* préparation de la requête SELECT * FROM livre
* instantiation de self::$_pdos_selectAll
*/
public static function initPDOS_selectAll() {
self::$_pdos_selectAll = self::$_pdo->prepare('SELECT * FROM livre');
}
/**
* méthode statique instanciant LivreMetier::$_pdo_select
*/
public static function initPDOS_select() {
self::$_pdos_select = self::$_pdo->prepare('SELECT * FROM livre WHERE liv_num= :numero');
}
/**
* méthode statique instanciant LivreMetier::$_pdo_update
*/
public static function initPDOS_update() {
self::$_pdos_update = self::$_pdo->prepare('UPDATE livre SET liv_titre=:titre, liv_depotlegal=:depotLegal WHERE liv_num=:numero');
}
/**
* méthode statique instanciant LivreMetier::$_pdo_insert
*/
public static function initPDOS_insert() {
self::$_pdos_insert = self::$_pdo->prepare('INSERT INTO livre VALUES(:numero,:titre,:depotLegal)');
}
/**
* méthode statique instanciant LivreMetier::$_pdo_delete
*/
public static function initPDOS_delete() {
self::$_pdos_delete = self::$_pdo->prepare('DELETE FROM livre WHERE liv_num=:numero');
}
/**
* préparation de la requête SELECT COUNT(*) FROM livre
* instantiation de self::$_pdos_count
*/
public static function initPDOS_count() {
if (!isset(self::$_pdo))
self::initPDO();
self::$_pdos_count = self::$_pdo->prepare('SELECT COUNT(*) FROM livre');
}
/**
* numéro du livre (identifiant dans la table Livre)
* @var int
*/
protected $liv_num;
/**
* titre du livre
* @var string
*/
protected $liv_titre;
/**
* dépot légal du livre
* @var string
*/
protected $liv_depotlegal;
/**
* attribut interne pour différencier les nouveaux objets des objets créés côté applicatif de ceux issus du SGBD
* @var bool
*/
private $nouveau = TRUE;
/**
* @return $this->liv_num
*/
public function getLiv_num() : int {
return $this->liv_num;
}
/**
* @param $liv_num
*/
public function setLiv_num($liv_num): void {
$this->liv_num=$liv_num;
}
/**
* @return $this->liv_titre
*/
public function getLiv_titre() : string {
return $this->liv_titre;
}
/**
* @param $liv_titre
*/
public function setLiv_titre($liv_titre): void {
$this->liv_titre=$liv_titre;
}
/**
* @return $this->liv_depotlegal
*/
public function getLiv_depotlegal() : string {
return $this->liv_depotlegal;
}
/**
* @param $liv_depotlegal
*/
public function setLiv_depotlegal($liv_depotlegal): void {
$this->liv_depotlegal=$liv_depotlegal;
}
/**
* @return $this->nouveau
*/
public function getNouveau() : bool {
return $this->nouveau;
}
/**
* @param $nouveau
*/
public function setNouveau($nouveau): void {
$this->nouveau=$nouveau;
}
/**
* @return un tableau de tous les LivreMetier
*/
public static function getAll(): array {
try {
if (!isset(self::$_pdo))
self::initPDO();
if (!isset(self::$_pdos_selectAll))
self::initPDOS_selectAll();
self::$_pdos_selectAll->execute();
// résultat du fetch dans une instance de LivreMetier
$lesLivres = self::$_pdos_selectAll->fetchAll(PDO::FETCH_CLASS,'LivreMetier');
return $lesLivres;
}
catch (PDOException $e) {
print($e);
}
}
/**
* initialisation d'un objet métier à partir d'un enregistrement de livre
* @param $liv_num un identifiant de livre
* @return l'instance de LivreMetier associée à $liv_num
*/
public static function initLivreMetier($liv_num) : LivreMetier {
try {
if (!isset(self::$_pdo))
self::initPDO();
if (!isset(self::$_pdos_select))
self::initPDOS_select();
self::$_pdos_select->bindValue(':numero',$liv_num);
self::$_pdos_select->execute();
// résultat du fetch dans une instance de LivreMetier
$lm = self::$_pdos_select->fetchObject('LivreMetier');
if (isset($lm) && ! empty($lm))
$lm->setNouveau(FALSE);
if (empty($lm))
throw new Exception("Livre $liv_num inexistant dans la table Livre.\n");
return $lm;
}
catch (PDOException $e) {
print($e);
}
}
/**
* sauvegarde d'un objet métier
* soit on insère un nouvel objet
* soit on le met à jour
*/
public function save() : void {
if (!isset(self::$_pdo))
self::initPDO();
if ($this->nouveau) {
if (!isset(self::$_pdos_insert)) {
self::initPDOS_insert();
}
self::$_pdos_insert->bindParam(':numero', $this->liv_num);
self::$_pdos_insert->bindParam(':titre', $this->liv_titre);
self::$_pdos_insert->bindParam(':depotLegal', $this->liv_depotlegal);
self::$_pdos_insert->execute();
$this->setNouveau(FALSE);
}
else {
if (!isset(self::$_pdos_update))
self::initPDOS_update();
self::$_pdos_update->bindParam(':numero', $this->liv_num);
self::$_pdos_update->bindParam(':titre', $this->liv_titre);
self::$_pdos_update->bindParam(':depotLegal', $this->liv_depotlegal);
self::$_pdos_update->execute();
}
}
/**
* suppression d'un objet métier
*/
public function delete() :void {
if (!isset(self::$_pdo))
self::initPDO();
if (!$this->nouveau) {
if (!isset(self::$_pdos_delete)) {
self::initPDOS_delete();
}
self::$_pdos_delete->bindParam(':numero', $this->liv_num);
self::$_pdos_delete->execute();
}
$this->setNouveau(TRUE);
}
/**
* nombre d'objets metier disponible dans la table
*/
public static function getNbLivres() : int {
if (!isset(self::$_pdos_count)) {
self::initPDOS_count();
}
self::$_pdos_count->execute();
$resu = self::$_pdos_count->fetch();
return $resu[0];
}
/**
* affichage élémentaire
*/
public function __toString() : string {
$ch = "<table border='1'><tr><th>liv_num</th><th>liv_titre</th><th>liv_depot_legal</th><th>nouveau</th></tr><tr>";
$ch.= "<td>".$this->liv_num."</td>";
$ch.= "<td>".$this->liv_titre."</td>";
$ch.= "<td>".$this->liv_depotlegal."</td>";
$ch.= "<td>".$this->nouveau."</td>";
$ch.= "</tr></table>";
return $ch;
}
}
$lmsf1 = LivreMetier::initLivreMetier(1);
echo "<p>Livre numéro 1</p>";
echo $lmsf1;
Livre numéro 1";
echo $lmsf1."$lmsf117 = new LivreMetier();
$lmsf117->setLiv_num(117);
$lmsf117->setLiv_titre('La lune disparue');
$lmsf117->setNouveau(TRUE);
echo $lmsf117;
setLiv_num(117);
$lmsf117->setLiv_titre('La lune disparue');
$lmsf117->setNouveau(TRUE);
echo $lmsf117."$lmsf117->save();
echo $lmsf117;
save();
echo "
try {
$lmsf117bis = LivreMetier::initLivreMetier(117);
echo "<p>Livre numéro 117 après sauvegarde et récupération</p>";
echo( $lmsf117bis);
$lmsf117bis->delete();
echo "<p>Livre numéro 117 après suppression</p>";
echo( $lmsf117bis);
echo "<p>L'instance existe toujours mais n'a plus de pendant coté SGBD</p>";
echo "<p>Tentative d'initialisation d'un livre inexistant</p>";
$lmsf117ter = LivreMetier::initLivreMetier(117);
if (isset($lmsf117))
echo( $lmsf117ter);
} catch (Exception $e) {
print $e;
}
Livre numéro 117 après sauvegarde et récupération";
echo( $lmsf117bis)."Livre numéro 117 après suppression
"; echo( $lmsf117bis)."L'instance existe toujours mais n'a plus de pendant coté SGBD
"; echo "Tentative d'initialisation d'un livre inexistant
"; $lmsf117ter = LivreMetier::initLivreMetier(117); if (isset($lmsf117)) echo( $lmsf117ter)."$lmsf1 = LivreMetier::initLivreMetier(1);
echo "<p>Livre numéro 1 avant édition</p>";
echo $lmsf1;
$lmsf1->setLiv_depotlegal('1974-01-01');
$lmsf1->save();
echo "<p>Livre numéro 1 après édition et sauvegarde</p>";
echo $lmsf1;
Livre numéro 1 avant édition";
echo( $lmsf1);
$lmsf1->setLiv_depotlegal('1974-01-01');
$lmsf1->save();
echo "Livre numéro 1 après édition et sauvegarde
"; echo( $lmsf1); $lmsf1->setLiv_depotlegal(NULL); $lmsf1->save(); echo "interface Iterator extends Traversable {
/* Méthodes */
abstract public mixed current ( void )
abstract public scalar key ( void )
abstract public void next ( void )
abstract public void rewind ( void )
abstract public boolean valid ( void )
}
class LivreIterator implements Iterator, Countable {
protected $idLivre = 1;
public function count() {
return LivreMetier::getNbLivres();
}
public function current() {
return LivreMetier::initLivreMetier($this->idLivre);
}
public function key() {
return $this->idLivre;
}
public function next() {
$this->idLivre = $this->idLivre+1;
}
public function rewind ( ) {
$this->idLivre = 1;
}
public function valid () {
return $this->idLivre>0 && $this->idLivre<=$this->count();
}
}
$literator = new LivreIterator();
echo "<h2/>Liste complète</h2>";
echo "<ul>";
foreach ($literator as $livre)
echo "<li>".$literator->key()." = ".$livre->getLiv_num()." ".$literator->current()."</li>";
echo "</ul>";
echo "<br/><br/>";
echo "<h2/>Décalage de 100 livres</h2>";
$literator->rewind();
for ($i=0;$i<100;$i++)
$literator->next();
echo "<ol>";
while ($literator->valid()) {
echo "<li>".$literator->key()." ".$literator->current()."</li>";
$literator->next();
}
echo "</ol>";
echo "<h2/>Focus sur les 7 premiers</h2>";
$pageCourante = new LimitIterator($literator,0,7);
echo "<ul>";
foreach ($pageCourante as $val) {
echo "<li>";
echo $pageCourante->key()." ".$val;
echo "</li> ";
}
echo "</ul>";
interface IteratorAggregate extends Traversable {
/* Méthodes */
abstract public Traversable getIterator ( void )
}
class LivreMetierAggregate implements IteratorAggregate {
public function getIterator() {
return new LivreIterator();
}
}
$lma = new LivreMetierAggregate();
$literator = $lma->getIterator();
echo "<ul>";
foreach ($literator as $livre)
echo "<li>".$literator->current()."</li>";
echo "</ul>";
[]
sur les objetsinterface ArrayAccess {
/* Méthodes */
abstract public boolean offsetExists ( mixed $offset )
abstract public mixed offsetGet ( mixed $offset )
abstract public void offsetSet ( mixed $offset , mixed $value )
abstract public void offsetUnset ( mixed $offset )
}
ArrayAccess::offsetExists
— Indique si une position existe dans un tableauArrayAccess::offsetGet
— Position à lireArrayAccess::offsetSet
— Position à assignerArrayAccess::offsetUnset
— Position à supprimerclass LivreIterator2 extends LivreIterator implements ArrayAccess {
public function offsetExists($offset) {
$testOffset = LivreMetier::initLivreMetier($offset);
if (isset($testOffset))
return true ;
else
return false;
}
public function offsetGet($offset) {
return LivreMetier::initLivreMetier($offset);
}
public function offsetSet($offset, $value) {
// pas possible avec LivreIterator : update de livre ?
}
public function offsetUnset($offset) {
// pas possible avec LivreIterator : suppression de livre ?
}
}
$li2 = new LivreIterator2();
echo $li2[5];
echo $li2[117];
echo $li2[5];
Serializable
: interface permettant de personnaliser la sérialisation à l'aide des méthodes serialize
et unserialize
(doc PHP)Closure
: classe pour gérer les fonctions anonymes (fonctions de rappel ou autres)Generator
: itérateurs simplifiés à l'aide de yield
...count()
sur les objets
interface Countable {
/* Méthodes */
abstract public int count ( void )
}
/* Méthode supplémentaire*/
abstract public void seek ( int $position )
class LivreIterator3 extends LivreIterator implements SeekableIterator {
public function seek($position) {
if ($position<0 || $position>$this->count()) {
throw new OutOfBoundsException("position invalide ($position)");
}
$this->idLivre = $position;
}
}
try {
$literator3 = new LivreIterator3();
$literator3->seek(100);
echo $literator3->current();
$literator3->seek(200);
} catch (OutOfBoundsException $e) {
echo $e->getMessage();
}
/* Méthodes supplémentaires */
public RecursiveIterator getChildren ( void )
public bool hasChildren ( void )
<?php
//require_once("LivreIterator.php");
function __autoload($classname) {
$filename = "./". $classname .".php";
require($filename);
}
session_start();
if (isset($_GET['reset'])) {
session_unset();
session_destroy();
}
if (! isset($_SESSION['collection'])) {
$_SESSION['collection'] = new LivreIterator();
$_SESSION['debut'] = 1;
$_SESSION['taillePage'] = 10;
}
if(isset($_GET['suivant'])) {
$_SESSION['debut'] += $_GET['suivant']*$_SESSION['taillePage'];
}
echo "<p>**** ".count($_SESSION['collection'])." ****</p>";
echo "<p>**** ".$_SESSION['debut']." -- ".$_SESSION['taillePage']." ****</p>";
$pageCourante = new LimitIterator($_SESSION['collection'],$_SESSION['debut']-1,$_SESSION['taillePage']);
echo "<ul>";
foreach ($pageCourante as $val) {
echo "<li>";
echo $pageCourante->key()." ".$val;
echo "</li> ";
}
echo "</ul>";
$decalageFirst = (int)(-1 *$_SESSION['debut']/$_SESSION['taillePage']);
$decalageLast = (int)((count($_SESSION['collection'])-$_SESSION['debut'])/$_SESSION['taillePage']);
$decalagePrev = ($_SESSION['debut']==1)?0:-1;
$decalageNext = ($_SESSION['debut']+$_SESSION['taillePage']>count($_SESSION['collection']))?0:1;
$urlFirst = $_SERVER['PHP_SELF']."?suivant=".$decalageFirst;
$urlPrev = $_SERVER['PHP_SELF']."?suivant=".$decalagePrev;
$urlNext = $_SERVER['PHP_SELF']."?suivant=".$decalageNext;
$urlLast = $_SERVER['PHP_SELF']."?suivant=".$decalageLast;
echo "<p><a href='$urlFirst'>First</a> <a href='$urlPrev'>prev</a> <a href='$urlNext'>next</a> <a href='$urlLast'>Last</a></p>";
$urlReset = $_SERVER['PHP_SELF']."?reset";
echo "<p><a href='$urlReset'>Reset</a></p>";
?>
spl_autoload()
, spl_autoload_extensions()
, spl_autoload_register()
, ...
Résultat :
$persistant
class EntiteLivre extends AbstractEntite
{
const TABLENAME = 'livre';
static $COLNAMES = array('liv_num', 'liv_titre', 'liv_depotlegal');
static $COLTYPES = array('number', 'text', 'date'); // par facilité, les types des formulaires => à améliorer
static $PK = array('liv_num'); // tableau pour une éventuelle clé composite
static $AUTOID = FALSE; // booléen indiquant si le renseignement de la clé est automatisé
static $FK = array(); // tableau pour les éventuelles clés étrangères
protected $liv_num;
protected $liv_titre;
protected $liv_depotlegal;
// pas dechangement pour la suite ../..
// initialisation des variables $contenu et $message pour alimenter <body>
$contenu = "";
$message = "";
// initialisation du connecteur myPDO pour la connexion
// (sans nom de Table à renseigner selon le contexte)
$myPDO = new MyPDO('mysql', $_ENV['host'], $_ENV['db'], $_ENV['user'], $_ENV['password']);
if (!isset($_GET['action']))
$_GET['action'] = "initialiser";
switch ($_GET['action']) {
case 'initialiser':
$_SESSION['état'] = 'Accueil';
break;
case 'selectionnerTable':
$myPDO->setNomTable($_GET['table_name']);
$_SESSION['état'] = 'afficheTable';
$_SESSION['table_name'] = $_GET['table_name'];
break;
case 'supprimerEntité':
$myPDO->setNomTable($_SESSION['table_name']);
// récupération du nom de colonne dans le GET
$keyName = array_keys(array_diff_key($_GET, array('action'=>TRUE)))[0];
$myPDO->delete(array($keyName => $_GET[$keyName]));
$message .= "<p>Entité ". $_GET[$keyName]." supprimée</p>\n";
$_SESSION['etat'] = 'afficheTable';
break;
case 'créerEntité': // construction du formulaire de création de l'entité
$myPDO->setNomTable($_SESSION['table_name']);
// Réflection pour récupérer la structure de l'entité
$classeEntite = new ReflectionClass("lmsf\Entite".ucfirst($_SESSION['table_name']));
$colNames = $classeEntite->getStaticPropertyValue("COLNAMES");
$colTypes = $classeEntite->getStaticPropertyValue("COLTYPES");
$paramForm = array_combine($colNames,$colTypes);
if ($classeEntite->getStaticPropertyValue("AUTOID"))
$paramForm = array_diff_key($paramForm,
array($classeEntite->getStaticPropertyValue(("PK"))[0] => TRUE));
// $paramForm est un tableau associatif destiné à configurer le formulaire
var_dump($paramForm);
// Réflection pour récupérer la bonne vue
$classeVue = new ReflectionClass("lmsf\Vue" . ucfirst($_SESSION['table_name']));
$vue = $classeVue->newInstance();
$contenu .= $vue->getForm4Entity($paramForm, "insérerEntité");
// valeur par défaut non géré ci-dessus
//$contenu .= $vue->getForm4Entity(array('liv_num' => array('type' => 'number',
// 'default' => $nbEntites + 1),
// 'liv_titre' => 'text',
// 'liv_depotlegal' => 'date'), "insérerEntité");
$_SESSION['état'] = 'formulaireTable';
break;
case 'modifierEntité': // construction du formulaire de modification de l'entité
// ../..
$_SESSION['état'] = 'formulaireTable';
break;
case 'insérerEntité': // validation du formulaire de création d'une entité
$myPDO->setNomTable($_SESSION['table_name']);
// Réflection pour récupérer la structure de l'entité
$classeEntite = new ReflectionClass("lmsf\Entite".ucfirst($_SESSION['table_name']));
$colNames = $classeEntite->getStaticPropertyValue("COLNAMES");
$colTypes = $classeEntite->getStaticPropertyValue("COLTYPES");
$paramInsert = array_diff_key($_GET, array('action'=>'insérerEntité'));
if ($classeEntite->getStaticPropertyValue("AUTOID"))
$paramInsert = array_merge([$classeEntite->getStaticPropertyValue(("PK"))[0] => null],
$paramInsert);
var_dump($paramInsert);
$myPDO->insert($paramInsert);
// avant : $myPDO->insert(['liv_num' => $_GET['liv_num'],
// 'liv_titre' => $_GET['liv_titre'],
// 'liv_depotlegal' => $_GET['liv_depotlegal'] ] );
$entite = "?"; //$myPDO->get('liv_num',$_GET['liv_num']);
$message .= "<p>Entité $entite crée</p>\n";
$_SESSION['état'] = 'afficheTable';
break;
case 'sauverEntité': // validation du formulaire de modification de l'entité
// ../..
$_SESSION['état'] = 'afficheTable';
break;
default:
$message .= "<p>Action " . $_GET['action'] . " non implémentée.</p>\n";
$_SESSION['etat'] = 'Accueil';
}
switch ($_SESSION['état']) {
case 'Accueil':
$contenu .= getListeTables();
break;
case 'afficheTable' :
$classeVue = new ReflectionClass("lmsf\Vue".ucfirst($_SESSION['table_name']));
$vue = $classeVue->newInstance();
$lesEntites = $myPDO->getAll();
$contenu .= $vue->getAllEntities($lesEntites);
break;
case 'formulaireTable':
//rien à faire, tout est fait dans la gestion des Actions ?
break;
default:
$message .= "<p>état ".$_SESSION['etat']." inconnu</p>\n";
$_SESSION['etat'] = 'Accueil';
}
// ajout d'un lien vers la page d'accueil
$contenu .= "<p><a href='index.php?action=initialiser'>Accueil</a></p>\n";
// ../..
Démo (visible uniquement lors du CM, screencast sur eureka)
Verbe HTTP | GET | PUT | POST | DELETE |
---|---|---|---|---|
URI de collection (ex : http://www.site.net/livres/ ) | Récupère les URI et éventuellement d'autres détails des éléments de la collection. | Remplace la collection complète. | Crée un nouvel élément dans la collection, l'URI du nouvel élément est attribué automatiquement. | Supprime la collection |
URI d'élément (ex : http://www.site.net/livre/100/ ) | Récupère une représentation de l'élément sélectionné de la collection, exprimée dans un type de médias approprié. | Remplace l'élément sélectionné de la collection, ou s'il n'existe pas, le crée. | rarement utilisé | Supprime l'élément sélectionné de la collection |
<?php
namespace coursPHP;
const NBDIAPOS=200;
class coursPHP { /* ... */ }
?>
<?php
namespace coursPHP\Lic2;
const NBDIAPOS=123;
class coursPHP { /* ... */ }
on distingue ainsi les constantes coursPHP\NBDIAPOS et coursPHP\Lic2\NBDIAPOS, les classes coursPHP\coursPHP et coursPHP\Lic2\coursPHP définies dans les 2 namespaces.<?php
namespace coursPHP;
class ClasseExemplePHP
{
public function method(): string {
return "Methode de ClasseExemplePHP situé dans le namespace : ". __NAMESPACE__;
}
}
?>
require "ClasseExemplePHP.php";
use coursPHP\ClasseExemplePHP;
$objet = new ClasseExemplePHP;
echo $objet->method();
__NAMESPACE__
contient le nom du namespace courant.use
permet également d'établir des alias de classe :
use coursPHP\ClasseExemple as ClassExemple; // alias par défaut
use
ne dispense pas de charger en mémoire le code source des classes avec require ou include.spl_autoload_register()
spl_autoload_register()
enregistre une fonction pour implémenter __autoload()
spl_autoload_register ( callable $autoload_function = ? , bool $throw = true , bool $prepend = false ) : bool
function monChargeurDeClasse_V1(string $className) {
include './'.$className.'.php';
}
spl_autoload_register('monChargeurDeClasse_V1');
spl_autoload_register(function (string $className) {
include './'.$className.'.php';
}
);
spl_autoload_register(function (string $className) {
// il faut échapper le backslash d'où le '\\'
$className = str_replace('\\','/',$className);
include './'.$className.'.php';
}
);
spl_autoload_register(function (string $className) {
$className = str_replace(__NAMESPACE__.'\\','',$className);
$className = str_replace('\\','/',$className);
include './'.$className.'.php';
}
);
// la session doit avoit été activée avant son appel !
function isAuthorizedAccess(): bool {
return isset($_SESSION['access']) && $_SESSION['access'] === 'verified';
}
if (! isAuthorizedAccess()) {
// mémorisation de la destination dans une variable de session
$_SESSION['destination'] = $_SERVER['PHP_SELF'];
header('HTTP/1.1 303 See Other');
header('Location: login.php'); // renvoi au portail de connexion
}
else {
/* accès à la ressource demandée */
}
function getFormAccess(): string {
$form = '<h2>Contrôle d\'accès</h2>
<form action="'.$_SERVER['PHP_SELF'].'" method="post">
<fieldset><legend>login</legend>
<input type="text" name="login" /></fieldset>
<fieldset><legend>password</legend>
<input type="password" name="password" /></fieldset>
<button name="action" value="controler">Valider</button></form>';
return $form;
}
if (isset($_REQUEST['login']) && isset($_REQUEST['password'])
&& isValid($_REQUEST['login'],$_REQUEST['password'])) {
$_SESSION['access'] = 'verified';
// mémorisation du login dans la session si nécessaire
// redirection vers la source ayant demandé le contrôle d'accès
header('HTTP/1.1 303 See Other');
header('Location: '.$_SESSION['destination']);
}
else {
echo getDebutHTML().getFormAcces().getFinHTML();
}
httpd
: nom du processus UNIX (d pour daemon)$ sudo apt install apache2
apache2-bin ...
(accepter l'installation des éventuels packages dépendants)$ sudo service apache2 status
● apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2023-02-27 17:03:33 CET; 1 week 0 days ago
Docs: https://httpd.apache.org/docs/2.4/
Main PID: 897 (apache2)
Tasks: 11 (limit: 38054)
Memory: 49.5M
CPU: 20.138s
CGroup: /system.slice/apache2.service
├─ 897 /usr/sbin/apache2 -k start
├─205694 /usr/sbin/apache2 -k start
├─205696 /usr/sbin/apache2 -k start
├─205697 /usr/sbin/apache2 -k start
├─205698 /usr/sbin/apache2 -k start
├─205699 /usr/sbin/apache2 -k start
├─209570 /usr/sbin/apache2 -k start
├─209571 /usr/sbin/apache2 -k start
├─210307 /usr/sbin/apache2 -k start
├─210308 /usr/sbin/apache2 -k start
└─210314 /usr/sbin/apache2 -k start
/var/www/html
et correspond à cette URL : http://localhost/ qui accède au fichier
index.html/etc/apache2
$ sudo a2ensite
default-ssl
$ sudo a2enmod ssl
$ sudo service apache2 restart
a2enmod
, a2ensite
et a2enconf
génèrent simplement les liens symboliques dans les
dossiers mods-enabled/
, sites-enabled/
et conf-enabled/
a2dismod
, a2dissite
et a2disconf
suppriment ces liensphp, php-common
$ sudo apt install
libapache2-mod-php
/etc/apache2/mods-available/php8.x.conf
si on a activé les
dossiers /home/*/public_html
via le module userdir/etc/php/8.x/apache2/php.ini
à l'aide des versions de bases disponibles
dans /usr/lib/php/8.1/
$ sudo a2enmod php8.x
$ sudo service apache2 restart
/var/www/html
qui contient une fichier index.html
/var/www/html/index.html
index.html
(ou index.php
s'il
existe)root
, il n'est pas utilisable par un
utilisateur lambda
lambda
de publier des ressources sur le serveur web il faut lui
donner le droit d'écriture sur un dossier accessible par le serveur HTTP/var/www/html
: sudo mkdir www-lambda
lambda
la propriété
de www-lambda
: sudo chown lambda.lambda
www-lambda
lambda
est libre de publier sur http://localhost/www-lambda/home/lambda/public_html
/etc/apache2/mods-available/userdir.conf
permet de configurer plus finement le dossier associé aux pages
perso~/snap/firefox/common/.mozilla/firefox/i5wa0smb.default
cookies.sqlite
Cookie: _hjSessionUser_3827639=eyJpZCI6IjE4Y2RkMGJhLTdkYWEtNTRhMi04YTU3LWMxY2VlOTc3M2JjNiIsImNyZWF0ZWQiOjE3MTAxODgxNjE5NDYsImV4aXN0aW5nIjpmYWxzZX0=; _hjSession_3827639=eyJpZCI6ImRkNmNlMDYxLTVjNjItNGRhMS04NzRhLWMxZjNlMDY5ZDU5OCIsImMiOjE3MTAxODgxNjE5NDcsInMiOjAsInIiOjAsInNiIjowLCJzciI6MCwic2UiOjAsImZzIjoxLCJzcCI6MH0=
Cookie: cookieyes-consent=consentid:aXZ6WGd2YmRNZk5sb2tIbVhpUUN3V0RZZUF3YVVNNWw,consent:yes,action:yes,necessary:yes,functional:no,analytics:no,performance:no,advertisement:no; _hjSessionUser_3827639=eyJpZCI6IjE4Y2RkMGJhLTdkYWEtNTRhMi04YTU3LWMxY2VlOTc3M2JjNiIsImNyZWF0ZWQiOjE3MTAxODgxNjE5NDYsImV4aXN0aW5nIjp0cnVlfQ==; _hjSession_3827639=eyJpZCI6ImRkNmNlMDYxLTVjNjItNGRhMS04NzRhLWMxZjNlMDY5ZDU5OCIsImMiOjE3MTAxODgxNjE5NDcsInMiOjAsInIiOjAsInNiIjowLCJzciI6MCwic2UiOjAsImZzIjoxLCJzcCI6MH0=; wp-wpml_current_language=fr
header('Set-Cookie: monCookie=chocolat-noisette; path=/~dominique/PHP/; domain=localhost');
setCookie('monCookie2', 'chocolat-amande', time()+24*3600, '/~dominique/PHP/', 'localhost');
$_COOKIE
echo $_COOKIE['monCookie'];
affiche
chocolat-noisette
print_r($_COOKIE);
affiche
Array ( [monCookie] => chocolat-noisette [monCookie2] => chocolat-amande [PHPSESSID] => 3aipgbdk654kbj9ukvq70m4gk7 )
http://localhost/~dominique/PHP/
This work by Yoann Pigné is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
A few needed technologies
Application-level protocol for distributed systems. Generic and stateless.
generic-message = start-line
*(message-header CRLF)
CRLF
[ message-body ]
start-line = Request-Line | Status-Line
message-header = field-name ":" [ field-value ]
field-name = token
field-value = *( field-content | LWS )
field-content = <the OCTETs making up the field-value
and consisting of either *TEXT or combinations
of token, separators, and quoted-string>
Host: tools.ietf.org
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: UTF-8,*;q=0.5
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17
From the client to the server
Request = Request-Line
\*(( general-header
| request-header
| entity-header ) CRLF)
CRLF
[ message-body ]
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
"OPTIONS"
: information about the communication options available"GET"
: retrieve whatever information is identified by the Request-URI
"HEAD"
: same as "GET"
message-body
in the response"POST"
: append entity to the existing Request-URI
"PUT"
: store entity as the new Request-URI
"DELETE"
: delete existing Request-URI
"TRACE"
: see what is being received at the other end of the request chain"CONNECT"
: for use with a proxy that can dynamically switch to being a tunnel (e.g. SSL tunneling).Response = Status-Line
*(( general-header
| response-header
| entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
1xx
: Informational - Request received, continuing process2xx
: Success - The action was received,
understood, and accepted3xx
: Redirection - Further action must be taken to
complete the request4xx
: Client Error - The request contains bad syntax or cannot
be fulfilled5xx
: Server Error - The server failed to fulfill an apparently
valid request REST
is a style of software architecture for distributed systems on top of HTTP.
RESTful API and HTTP methods
Resource | GET | PUT | POST | DELETE |
---|---|---|---|---|
Collection URI | List elements | Replace entire collection | Create new element in collection | Delete collection |
Element URI | Retrieve one element | Replace existing element | *Generally not used* | Delete one element |
http://example.com/emails/
http://example.com/email/17/
https://api.twitter.com/1.1/statuses/home_timeline.json
Too many vulnerabilities exist... But developers are responsible for their code!
Reduce vulnerability... Use frameworks!
The Model–View–Controller Design Pattern
Show news and allow comments on them.
<?php
$connect = mysql_connect('myserver', 'mylogin', 'mypassword');
mysql_select_db('myDB');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$news_id = $_POST['news_id'];
mysql_query("INSERT INTO commentaires SET news_id='$news_id',
auteur='".mysql_escape_string($_POST['auteur'])."',
texte='".mysql_escape_string($_POST['texte'])."',
date=NOW()"
);
header("location: ".$_SERVER['SCRIPT_NAME']."?news_id=$news_id");
exit;
} else {
$news_id = $_GET['news_id'];
}
?>
<!-- [...] -->
<!-- [...] -->
<html><head><title>Les news</title></head>
<body>
<h1>Les news</h1>
<div id="news">
<?php
$news_req = mysql_query("SELECT * FROM news WHERE id='$news_id'");
$news = mysql_fetch_array($news_req);
?>
<h2><?php echo $news['titre'] ?> postée le <?php echo $news['date'] ?></h2>
<p><?php echo $news['texte_nouvelle'] ?> </p>
<?php
$comment_req = mysql_query("SELECT * FROM commentaires
WHERE news_id='$news_id'");
$nbre_comment = mysql_num_rows($comment_req);
?>
<h3><?php echo $nbre_comment ?> commentaires relatifs à cette nouvelle</h3>
<?php while ($comment = mysql_fetch_array($comment_req)) {?>
<h3><?php echo $comment['auteur'] ?>
a écrit le <?php echo $comment['date'] ?></h3>
<p><?php echo $comment['texte'] ?></p>
<?php } ?>
<!-- [...] -->
<!-- [...] -->
<form method="POST" action="<?php echo $_SERVER['SCRIPT_NAME'] ?>"
name="ajoutcomment">
<input type="hidden" name="news_id" value="<?php echo $news_id?>">
<input type="text" name="auteur" value="Votre nom"><br />
<textarea name="texte" rows="5" cols="10">
Saisissez votre commentaire
</textarea><br />
<input type="submit" name="submit" value="Envoyer">
</form>
</div>
</body>
</html>
Various actions are mixed up in this file:
<?php
function dbconnect() {
static $connect = null;
if ($connect === null) {
try {
$connect = new PDO("mysql:dbname=simplemvc;host=127.0.0.1", 'pigne', 'n2EfCJYFx6CExzSX' );
$connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) { echo 'Connection failed :( : ' . $e->getMessage(); exit;}
}
return $connect;
}
/* [...] */
/* [...] */
function get_news($id) {
try{
$sql = "SELECT * FROM news WHERE id= :id";
$sth = dbconnect()->prepare($sql);
$sth->execute(array(':id' => $id));
if($sth->errorCode() == 0) {
return $sth->fetch();
}
else {
return array();
}
}catch (PDOException $e) { echo 'Select comments failed: ' . $e->getMessage(); exit;}
}
function get_comments($news_id) {
try {
$sql = "SELECT * FROM commentaires WHERE news_id= :news_id";
$sth = dbconnect()->prepare($sql);
$sth->execute(array(':news_id' => $news_id));
if($sth->errorCode() == 0) {
return $sth->fetchAll();
}
else {
return array();
}
}catch (PDOException $e) { echo 'Select comments failed: ' . $e->getMessage(); exit;}
}
/* [...] */
/* [...] */
function insert_comment($comment) {
$connect = dbconnect();
try{
$sql = "INSERT INTO commentaires SET news_id= :news_id , " .
"auteur= :auteur , " .
"texte= :texte , " .
"date=NOW()";
$sth = $connect->prepare($sql);
$sth->execute(array(':news_id' => (int)$comment['news_id'],
':auteur' => $connect->quote($comment['auteur']),
':texte' => $connect->quote($comment['texte']),
)
);
} catch(PDOException $e) { echo 'Insert failed: ' . $e->getMessage(); exit;}
}
<html><head><title>Les news</title></head>
<body>
<h1>Les News</h1>
<div id="news">
<h2><?php echo $news['titre'] ?> postée le <?php echo $news['date'] ?></h2>
<p><?php echo $news['texte_nouvelle'] ?> </p>
<h3><?php echo $nbre_comment ?> commentaires relatifs à cette nouvelle</h3>
<dl>
<?php foreach ($comments AS $comment) {?>
<dt><?php echo $comment['auteur'] ?>, le <?php echo $comment['date']?>:</dt>
<dd><?php echo $comment['texte'] ?></dd>
<?php } ?>
</dl>
<h3>Un commentaire ?</h3>
<form method="POST" action="<?php echo $_SERVER['SCRIPT_NAME'] ?>" name="ajoutcomment">
<input type="hidden" name="news_id" value="<?php echo $news['id']?>">
<input type="text" name="auteur" placeholder="Votre nom"><br>
<textarea name="texte" placeholder="Saisissez votre commentaire"></textarea><br>
<input type="submit" name="submit" value="Envoyer">
</form>
</div>
</body></html>
require ('simpleModel.php');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
insert_comment($_POST);
header("HTTP/1.1 301 Moved Permanently");
header("location: {$_SERVER['SCRIPT_NAME']}?news_id={$_POST['news_id']}");
exit;
} else {
$news = get_news($_GET['news_id']);
$comments = get_comments($_GET['news_id']);
$nbre_comment = sizeof($comments);
require ('simpleView.php');
}
Many of them
src/
et templates
ou app/Resources
seront nos principales zones d'actionconfig/
pour configurer l'environnement (routes, services, packages)src/
pour le code PHPtemplates/
pour les twigbin/
les executables nécessaires dont bin/console qui permet notamment de génerer un environnement d'exécution completvar/
pour les fichiers générés automatiquement : logs, sessions , cachevendor/
bibliothèques tierspublic/
tout ce qui doit être accessible : css, js, images...tests
pour les tests unitairestranslations
pour configurer les sites multilinguesconfig/
pour configurer l'environnement (routes, services, packages) (avant dans app/config)src/
pour le code PHPtemplates/
pour les twig (avant dans app/Resources)bin/
les executables nécessaires dont bin/console qui permet notamment de génerer un environnement d'exécution completvar/
pour les fichiers générés automatiquement : logs, sessions , cachevendor/
bibliothèques tierspublic/
tout ce qui doit être accessible : css, js, images...tests
pour les tests unitairesapp/
pour configurer l'environnement et les templates (dont les vues) src/
pour le code PHP bin/
les executables nécessaires dont bin/console qui permet notamment de génerer un environnement d'exécution complettests/
pour les tests unitairesvar/
pour les fichiers générés automatiquement : logs, sessions , cachevendor/
bibliothèques tiersweb/
tout ce qui doit être accessible : css, js, images...src/AppBundle/Controller/
on peut ajouter des controleurs gérant des routes associées à des actions.namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HelloWorldController
{
/**
* @Route("/HelloWorld")
*/
public function helloWorldAction()
{
return new Response(
'<html><body><h1>Hello World !</h1></body></html>'
);
}
}
@Route
définit une URLnamespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloWorldController extends Controller
{
/**
* @Route("/HelloWorld")
*/
public function HelloWorldAction()
{
return new Response(
'<html><body><h1>Hello World !</h1></body></html>'
);
}
/**
* @Route("HelloWorld/mister")
*/
public function HelloMisterAction()
{
$mister = "Fournier";
return $this->render('HelloWorld/mister.html.twig', array('lastName' => $mister));
}
}
app/Resources/views/HelloWorld/mister.html.twig
:{# app/Resources/views/HelloWorld/mister.html.twig #}
<h1>Hello Mister {{ lastName }} !</h1>
/HelloWorld/mister/*
les routes peuvent être dynamiques :
/**
* @Route("HelloMister/{lastName}")
*/
public function HelloMisterLastNameAction($lastName) {
return $this->render('HelloWorld/mister.html.twig', array('mister' => $lastName));
}
/**
* @Route("HelloMister/{lastName}", name="bonjour")
*/
/**
* @Route("HelloMister")
*/
public function HelloMistersAction() {
$misters = array ("Amanton", "Arfi", "Balev", "Bertelle", "Duvallet", "Fournier", "Jay", "Mermet", "Pigne", "Ponty");
$urls = array();
foreach ($misters as $mister) {
$urls[] = $this->generateUrl('bonjour', array('lastName' => $mister));
}
return $this->render('HelloWorld/misters.html.twig', array( 'urls' => $urls, 'misters' => $misters));
}
twig
de les afficher :{# app/Resources/views/HelloWorld/misters.html.twig #}
<h1>Liste des misters !</h1>
<ul>
{% for mister in misters %}
<li><a href="{{ path('bonjour', {'lastName': mister}) }}"> {{ mister }} </a></li>
{% endfor %}
</ul>
<ul>
{% for url in urls %}
<li><a href="{{ url }}"> {{ url }}</a></li>
{% endfor %}
</ul>
/**
* @Route("HelloTitle/{title}/{lastName}")
*/
public function HelloTitleAction($title, $lastName) {
return $this->render('HelloWorld/title.html.twig',
array('title' => $title, 'lastName' => $lastName));
}
{# app/Resources/views/HelloWorld/title.html.twig #}
<h1>Hello {{ title }} {{ lastName }} !</h1>
/**
* @Route("Route/{number}")
*/
public function routeNumberAction($number) {
$number *= 5;
return new Response("Route $number !
");
}
/**
* @Route("Route/{name}")
*/
public function routeNameAction($name) {
return new Response("Route $name !
");
}
Par défaut c'est la première route qui est appariée, donc les routes associées à une chaîne de caractères vont produire une erreur.
/**
* @Route("Route/{number}", requirements={"number"="\d+"})
*/
public function routeNumberAction($number) {
$number *= 5;
return new Response("Route $number !
");
}
Ici un seul requirements suffit, la route associée à un nom sera exécutée si aucune route numérotée n'est visée.
{# commentaires ici #}
{{ variable ici }}
{% structure de contrôle ici %}
{% extends %}
permet d'hériter d'un template de base{% block bloc_name %}
et {% endblock %}
définissent un bloc surchargeable{{ parent() }}
permet de récupérer le contenu du bloc issu du template parentbaseHello.html.twig
:<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('logoULH.ico') }}" />
</head>
<body>
<h1>{% block h1 %}{% endblock %}</h1>
{% block article %}
<p>Ce texte est hérité du template parent !</p>
{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
title.html.twig
:{# app/Resources/views/HelloWorld/title.html.twig #}
{% extends 'HelloWorld/baseHello.html.twig' %}
{% block title %}Hello {{ title }} {{ lastName }} ! {% endblock %}
{% block h1 %}Hello {{ title }} {{ lastName }} ! {% endblock %}
{% block article %}
{{ parent() }}
<p>Et j'y ajoute un complément...</p>
{% endblock %}
config/
pour configurer l'environnement (routes, services, packages) src/
pour le code PHP templates/
pour les twig bin/
les executables nécessaires dont bin/console qui permet notamment de génerer un environnement d'exécution completvar/
pour les fichiers générés automatiquement : logs, sessions , cachevendor/
bibliothèques tierspublic/
tout ce qui doit être accessible : css, js, images...Symfony\Bundle\FrameworkBundle\Controller\Controller
mais dans la classe Symfony\Bundle\FrameworkBundle\Controller\AbstractController
,
il faut en faire hériter nos controleurs pour hériter de ses fonctionnalités, exemple :
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class HelloWorldController extends AbstractController
{
/**
* @Route("/HelloWorld")
*/
public function HelloWorldAction()
{
return new Response(
'<html><body><h1>Hello World !</h1></body></html>'
);
}
}
app/config/parameters.yml
parameters:
database_host: ust-infoserv.univlehavre.lan
database_port: null
database_name: xy123456
database_user: xy123456
database_password: *****
(...)
$ php bin/console doctrine:generate:entity
Welcome to the Doctrine2 entity generator
This command helps you generate Doctrine2 entities.
First, you need to give the entity name you want to generate.
You must use the shortcut notation like AcmeBlogBundle:Post.
The Entity shortcut name: AppBundle:Collectivite
Determine the format to use for the mapping information.
Configuration format (yml, xml, php, or annotation) [annotation]:
Instead of starting with a blank entity, you can add some fields now.
Note that the primary key will be added automatically (named id).
Available types: array, simple_array, json_array, object,
boolean, integer, smallint, bigint, string, text, datetime, datetimetz,
date, time, decimal, float, binary, blob, guid.
New field name (press <return> to stop adding fields): col_code
Field type [string]: string
Field length [255]: 3
Is nullable [false]:
Unique [false]: true
New field name (press <return> to stop adding fields): col_nom
Field type [string]:
Field length [255]: 50
Is nullable [false]:
Unique [false]:
New field name (press <return> to stop adding fields): col_population
Field type [string]: integer
Is nullable [false]:
Unique [false]:
New field name (press <return> to stop adding fields): col_superficie
Field type [string]: integer
Is nullable [false]:
Unique [false]:
New field name (press <return> to stop adding fields): col_region
Field type [string]:
Field length [255]: 30
Is nullable [false]:
Unique [false]:
New field name (press <return> to stop adding fields):
Entity generation
created ./src/AppBundle/Entity/Collectivite.php
> Generating entity class src/AppBundle/Entity/Collectivite.php: OK!
> Generating repository class src/AppBundle/Repository/CollectiviteRepository.php: OK!
Everything is OK! Now get to work :).
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Collectivite
*
* @ORM\Table(name="collectivite")
* @ORM\Entity(repositoryClass="AppBundle\Repository\CollectiviteRepository")
*/
class Collectivite
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="col_code", type="string", length=3, unique=true)
*/
private $colCode;
/**
* @var string
*
* @ORM\Column(name="col_nom", type="string", length=50)
*/
private $colNom;
/**
* @var int
*
* @ORM\Column(name="col_population", type="integer")
*/
private $colPopulation;
/**
* @var int
*
* @ORM\Column(name="col_superficie", type="integer")
*/
private $colSuperficie;
/**
* @var string
*
* @ORM\Column(name="col_region", type="string", length=30)
*/
private $colRegion;
/**
* Get id
*
* @return int
*/
public function getId()
{
return $this->id;
}
/* (...) suite des getters et des setters */
$ php bin/console doctrine:schema:validate
$ php bin/console doctrine:schema:update [ --force | --dump-sql ]
Table produite sur le SGBD :
mysql> describe collectivite;
+----------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| col_code | varchar(3) | NO | UNI | NULL | |
| col_nom | varchar(50) | NO | | NULL | |
| col_population | int(11) | NO | | NULL | |
| col_superficie | int(11) | NO | | NULL | |
| col_region | varchar(30) | NO | | NULL | |
+----------------+-------------+------+-----+---------+----------------+
6 rows in set (0,00 sec)
mysql> describe livresf;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| liv_num | int(11) | NO | PRI | NULL | auto_increment |
| liv_titre | varchar(100) | NO | | NULL | |
| auteursf_id | int(11) | YES | MUL | NULL | |
+-------------+--------------+------+-----+---------+----------------+
3 rows in set (0,00 sec)
namespace AppBundle\Controller;
use AppBundle\Entity\LivreSF;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LivreSFController extends DefaultController {
/**
* @Route("livresf/create")
*/*/
public function createAction() {
// you can fetch the EntityManager via $this->getDoctrine()
// or you can add an argument to your action: createAction(EntityManagerInterface $em)
$entityManager = $this->getDoctrine()->getManager();
$livresf = new LivreSF();
$livresf->setLivTitre('Le cerveau solitaire');
// tells Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($livresf);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new livresf with liv_num '.$livresf->getLivNum());
}
/**
* @Route("livresf/{livresfId}")
*/
public function showAction($livresfId)
{
$livresf = $this->getDoctrine()
->getRepository(LivreSF::class)
->find($livresfId);
if (!$livresf) {
throw $this->createNotFoundException(
'No livresf found for id '.$livresfId
);
}
else {
return $this->render('livresf.html.twig', array(
'liv_num' => $livresf->getLivNum(), 'liv_titre' => $livresf->getLivTitre(),
));
}
}
/**
* @Route("livresf", name = "accueil")
*/
public function showAllAction()
{
$lesLivresf = $this->getDoctrine()
->getRepository(LivreSF::class)
->findAll();
return $this->render('leslivresf.html.twig', array(
'leslivressf' => $lesLivresf
));
}
/**
* @Route("livresf/update/{livresfId}")
*/
public function updateAction($livresfId)
{
$entityManager = $this->getDoctrine()->getManager();
$livresf = $entityManager->getRepository(LivreSF::class)->find($livresfId);
if (!$livresf) {
throw $this->createNotFoundException(
'No livresf found for id '.$livresfId
);
}
$titres = array('Le cerveau solitaire', 'Le voyageur de l\'inconnu', 'Les mutants', 'La galaxie noire');
if ($livresfId <= sizeof($titres))
$livresf->setLivTitre($titres[$livresfId - 1]);
else
$livresf->setLivTitre('titre inconnu');
$entityManager->flush();
return $this->redirectToRoute('accueil');
}
/**
* @Route("livresf/delete/{livresfId}")
*/
public function deleteAction($livresfId)
{
$entityManager = $this->getDoctrine()->getManager();
$livresf = $entityManager->getRepository(LivreSF::class)->find($livresfId);
if (!$livresf) {
throw $this->createNotFoundException(
'No livresf found for id '.$livresfId
);
}
else {
$entityManager->remove($livresf);
$entityManager->flush();
}
return $this->redirectToRoute('accueil');
}
/**
* @Route("livresf/select/{livresfTitre}")
*/
public function selectAction($livresfTitre)
{
$entityManager = $this->getDoctrine()->getManager();
$livresf = $entityManager->getRepository(LivreSF::class)->findOneBy(array('liv_titre' => $livresfTitre));
if (!$livresf) {
throw $this->createNotFoundException(
'No livresf found for titre '.$livresfTitre
);
}
else {
return $this->render('livresf.html.twig', array(
'liv_num' => $livresf->getLivNum(), 'liv_titre' => $livresf->getLivTitre()
));
}
}
/**
* @ORM\ManyToOne(targetEntity="AuteurSF", inversedBy="livres")
* @ORM\JoinColumn(name="auteursf_id", referencedColumnName="id")
*/
private $auteur;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @ORM\Entity
* @ORM\Table(name="auteursf")
*/
class AuteurSF
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=100)
*/
private $aut_nom;
/**
* @ORM\Column(type="string", length=100)
*/
private $aut_prenom;
/**
* @ORM\OneToMany(targetEntity="LivreSF", mappedBy="auteur")
*/
private $livres;
public function __construct()
{
$this->livres = new ArrayCollection();
}
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
// ...
Liste détaillée des types.FormBuilder
permet de construire un formulaire :
/**
* @Route("livresf/form1/")
*/
public function form1Action(Request $request) {
/* Dans ce controller, on génère un formulaire permettant de saisir les informations d'une instance de livreSF. */
$livresf = new LivreSF();
$livresf->setLivTitre("Le cerveau solitaire");
$livresf->setLivNum(90);
$form = $this->createFormBuilder($livresf);
$form->add('livtitre',TextType::class, array('attr' => array('maxlength' => 22)));
$form->add('livnum', IntegerType::class);
$form->add('save', SubmitType::class, array('label' => 'Create livresf'));
$form->setMethod("GET");
$form->getForm();
return $this->render("default/new.html.twig", array('form' => $form->createView()));
}
/**
* @Route("livresf/form1/")
*/
public function form1Action(Request $request) {
$livresf = new LivreSF();
$livresf->setLivTitre("Le cerveau solitaire");
$livresf->setLivNum(90);
$form = $this->createFormBuilder($livresf)
->add('livtitre',TextType::class, array('attr' => array('maxlength' => 22)))
->add('livnum', IntegerType::class)
->add('save', SubmitType::class, array('label' => 'Create livresf'))
->setMethod("GET")
->getForm();
return $this->render("default/new.html.twig", array('form' => $form->createView()));
}
{# app/Resources/views/default/new.html.twig #}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
action
et method
des formulaires
POST
, donc dans ce cas pas besoin de setMethod("POST")
!
/**
* @Route("livresf/form2/")
*/
public function form2Action(Request $request) {
$livresf = new LivreSF();
$form = $this->createFormBuilder($livresf)
->add('livtitre',TextType::class, array('attr' => array('maxlength' => 22)))
->add('livnum', IntegerType::class)
->add('save', SubmitType::class, array('label' => 'Create livresf'))
//->setMethod("POST") inutile car méthode par défaut
->getForm();
return $this->render("default/new.html.twig", array('form' => $form->createView()));
}
Response
:use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
Request
permet de configurer et réaliser une requête HTTP :use Symfony\Component\HttpFoundation\Request;
public function form3Action(Request $request) {
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$livresf = $form->getData();
/* on fait ce qu'on veut de $livresf*/
return $this->redirectToRoute('accueil');
}
/**
* @Route("livresf/form3/")
*/
public function form3Action(Request $request) {
$livresf = new LivreSF();
$form = $this->createFormBuilder($livresf)
->add('livtitre',TextType::class)
->add('livnum', IntegerType::class)
->add('save', SubmitType::class, array('label' => 'Create livresf'))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$livresf = $form->getData();
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($livresf);
$entityManager->flush();
return $this->redirectToRoute('accueil');
}
return $this->render('default/new.html.twig', array(
'form' => $form->createView(),
));
}
/* (...) *.
use Symfony\Component\Validator\Constraints as Assert;
/* (...) *.
class LivreSF
{
/**
* @ORM\Column(type="string", length=100)
* @Assert\NotBlank()
*/
private $liv_titre;
/* (...) *.
Bloque le traitement du formulaire et place un message dans la page : /**
* @Assert\IsTrue(message="Le titre du livre ne peut pas être 'Le Masque SF'")
*/
public function isLivTitreOk()
{
return $this->liv_titre != "Le Masque SF";
}
@JointTable
sont présentes, ici l'entité Auteur et l'attribut livNumArray :
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\ManyToMany(targetEntity="Livre", inversedBy="aut")
* @ORM\JoinTable(name="livaut",
* joinColumns={
* @ORM\JoinColumn(name="aut_id", referencedColumnName="aut_id")
* },
* inverseJoinColumns={
* @ORM\JoinColumn(name="liv_num", referencedColumnName="liv_num")
* }
* )
*/
private $livNumArray;
Auteur::class
avec une méthode permettant l'ajout d'un livre
/**
* @param Livre $livre
*/
public function addLivre(Livre $livre) {
$this->livNumArray->add($livre);
}
$livres = $this->getDoctrine()->getRepository(Livre::class)->findAll();
$dataChoice = array();
foreach ($livres as $livre) {
$livTitre = $livre->getLivTitre();
$dataChoice[$livTitre] = $livre->getLivNum();
}
$form = $this->createFormBuilder()
->add('liv_num', ChoiceType::class, array('choices' => $dataChoice))
->add('save', SubmitType::class, array('label' => 'Update livre'))
->getForm();
$livres = $this->getDoctrine()->getRepository(Livre::class)->findAll();
$form = $this->createFormBuilder()
->add('liv_num', EntityType::class, [
'class' => Livre::class,
'multiple' => false,
'expanded' => false,
'choice_label' => 'liv_titre',
])
->add('save', SubmitType::class, array('label' => 'Update livre'))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$livre = $this->getDoctrine()
->getRepository(Livre::class)
->find($form->getData()['liv_num']);
$auteur->addLivre($livre);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($livre);
$entityManager->persist($auteur);
$entityManager->flush();
return $this->redirectToRoute('detailAuteur', array('id'=> $autId));
}
/**
* Livre
*
* @ORM\Table(name="livre")
* @ORM\Entity
* @ORM\Entity(repositoryClass="AppBundle\Repository\LivreRepository")
*/
class Livre
{
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class LivreRepository extends EntityRepository
{
public function findAllOrderedByLivTitre()
{
return $this->getEntityManager()
->createQuery(
'SELECT l FROM AppBundle:Livre l ORDER BY l.livTitre ASC'
)
->getResult();
}
public function findAllOrderedBy(string $colname, $sort = 'ASC') {
return $this->getEntityManager()
->createQuery(
'SELECT l FROM AppBundle:Livre l ORDER BY l.'.$colname.' '.$sort
)
->getResult();
}
}
app/config/security.yml
on peut paramétrer le contrôle d'accès :
# app/config/security.yml
security:
providers:
in_memory:
memory: ~
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous : ~
dev
concerne l'accès aux outils de développement la route /_profiler
par exemple ;main
, ici tout est accessible en mode anonyme c'est à dire sans authentification.security:
# ...
firewalls:
# ...
main:
anonymous : ~
http_basic: ~
// src/AppBundle/Controller/DefaultController.php
// ...
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends Controller
{
/**
* @Route("/admin")
*/
public function adminAction()
{
return new Response('Admin page!');
}
}
# app/config/security.yml
security:
# ...
firewalls:
# ...
main:
# ...
access_control:
# require ROLE_ADMIN for /admin*
- { path: ^/admin, roles: ROLE_ADMIN }
# app/config/security.yml
security:
providers:
in_memory:
memory:
users:
lambda:
password: lambdapass
roles: 'ROLE_USER'
admin:
password: teapot
roles: 'ROLE_ADMIN'
# ...
# app/config/security.yml
security:
# ...
encoders:
Symfony\Component\Security\Core\User\User: plaintext
# ...
# app/config/security.yml
security:
# ...
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12
$ php bin/console security:encode-password
Symfony Password Encoder Utility
================================
Type in your password to be encoded:
>
------------------ ---------------------------------------------------------------
Key Value
------------------ ---------------------------------------------------------------
Encoder used Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder
Encoded password $2y$12$68BvHejD/f4RKrSnXP7xsuTYGXGmQfcD.O/xwG7q64hXbXmgluPd2
------------------ ---------------------------------------------------------------
! [NOTE] Self-salting encoder used: the encoder generated its own built-in salt.
[OK] Password encoding succeeded
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
in_memory:
memory:
users:
lambda:
password: $2y$12$68BvHejD/f4RKrSnXP7xsuTYGXGmQfcD.O/xwG7q64hXbXmgluPd2
roles: 'ROLE_USER'
admin:
password: $2y$12$RGQrY.D3VoeRVxDuL9lHE.Bf3Hj2z8RSFDNMinEk6xfT7SjPTeFJe
roles: 'ROLE_ADMIN'
#...
# app/config/security.yml
security:
# ...
role_hierarchy:
ROLE_ADMIN: ROLE_USER
Attention tous les noms de rôle doivent commencer par ROLE_
# app/config/security.yml
security:
# ...
access_control:
# require ROLE_ADMIN for /admin*
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/lambda, roles: ROLE_USER }
- { path: ^/lmsf, roles: ROLE_USER }
Attention filtre les routes dans l'ordre de déclaration et adopte la première politique rencontréepublic function helloAction($name)
{
// The second parameter is used to specify on what object the role is tested.
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
//...
}
ou bien à l'aide d'annotation
// ...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* @Security("has_role('ROLE_ADMIN')")
*/
public function helloAction($name)
{
// ...
}
Génère une Réponse HTTP 403 si l'authentification n'est pas validée{% if is_granted('ROLE_ADMIN') %}
<a href="...">Delete</a>
{% endif %}
La fonction interne is_granted()
fait le job !$ bin/console --help
Usage:
list [options] [--] [<namespace>]
Arguments:
namespace The namespace name
Options:
--raw To output raw command list
--format=FORMAT The output format (txt, xml, json, or md) [default: "txt"]
Help:
The list command lists all commands:
php bin/console list
You can also display the commands for a specific namespace:
php bin/console list test
You can also output the information in other formats by using the --format option:
php bin/console list --format=xml
It's also possible to get raw list of commands (useful for embedding command runner):
php bin/console list --raw
$ bin/console list
Symfony 3.4.4 (kernel: app, env: dev, debug: true)
Usage:
command [options] [arguments]
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-e, --env=ENV The Environment name. [default: "dev"]
--no-debug Switches off debug mode.
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
about Displays information about the current project
help Displays help for a command
list Lists commands
assets
assets:install Installs bundles web assets under a public directory
cache
cache:clear Clears the cache
cache:pool:clear Clears cache pools
cache:pool:prune Prune cache pools
cache:warmup Warms up an empty cache
config
config:dump-reference Dumps the default configuration for an extension
debug
debug:autowiring Lists classes/interfaces you can use for autowiring
debug:config Dumps the current configuration for an extension
debug:container Displays current services for an application
debug:event-dispatcher Displays configured listeners for an application
debug:form Displays form type information
debug:router Displays current routes for an application
debug:swiftmailer [swiftmailer:debug] Displays current mailers for an application
debug:twig Shows a list of twig functions, filters, globals and tests
doctrine
doctrine:cache:clear-collection-region Clear a second-level cache collection region.
doctrine:cache:clear-entity-region Clear a second-level cache entity region.
doctrine:cache:clear-metadata Clears all metadata cache for an entity manager
doctrine:cache:clear-query Clears all query cache for an entity manager
doctrine:cache:clear-query-region Clear a second-level cache query region.
doctrine:cache:clear-result Clears result cache for an entity manager
doctrine:cache:contains Check if a cache entry exists
doctrine:cache:delete Delete a cache entry
doctrine:cache:flush [doctrine:cache:clear] Flush a given cache
doctrine:cache:stats Get stats on a given cache provider
doctrine:database:create Creates the configured database
doctrine:database:drop Drops the configured database
doctrine:database:import Import SQL file(s) directly to Database.
doctrine:ensure-production-settings Verify that Doctrine is properly configured for a production environment.
doctrine:generate:crud [generate:doctrine:crud] Generates a CRUD based on a Doctrine entity
doctrine:generate:entities [generate:doctrine:entities] Generates entity classes and method stubs from your mapping information
doctrine:generate:entity [generate:doctrine:entity] Generates a new Doctrine entity inside a bundle
doctrine:generate:form [generate:doctrine:form] Generates a form type class based on a Doctrine entity
doctrine:mapping:convert [orm:convert:mapping] Convert mapping information between supported formats.
doctrine:mapping:import Imports mapping information from an existing database
doctrine:mapping:info
doctrine:query:dql Executes arbitrary DQL directly from the command line.
doctrine:query:sql Executes arbitrary SQL directly from the command line.
doctrine:schema:create Executes (or dumps) the SQL needed to generate the database schema
doctrine:schema:drop Executes (or dumps) the SQL needed to drop the current database schema
doctrine:schema:update Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.
doctrine:schema:validate Validate the mapping files.
generate
generate:bundle Generates a bundle
generate:command Generates a console command
generate:controller Generates a controller
lint
lint:twig Lints a template and outputs encountered errors
lint:xliff Lints a XLIFF file and outputs encountered errors
lint:yaml Lints a file and outputs encountered errors
router
router:match Helps debug routes by simulating a path info match
security
security:check Checks security issues in your project dependencies
security:encode-password Encodes a password.
server
server:log Starts a log server that displays logs in real time
server:run Runs a local web server
server:start Starts a local web server in the background
server:status Outputs the status of the local web server for the given address
server:stop Stops the local web server that was started with the server:start command
swiftmailer
swiftmailer:email:send Send simple email message
swiftmailer:spool:send Sends emails from the spool
$ php bin/console list server
Symfony 3.4.4 (kernel: app, env: dev, debug: true)
Usage:
command [options] [arguments]
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-e, --env=ENV The Environment name. [default: "dev"]
--no-debug Switches off debug mode.
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands for the "server" namespace:
server:log Starts a log server that displays logs in real time
server:run Runs a local web server
server:start Starts a local web server in the background
server:status Outputs the status of the local web server for the given address
server:stop Stops the local web server that was started with the server:start command