VIII. Exemples d’application▲
VIII-A. Introduction▲
Les exemples qui suivent sont tous bâtis sur le même modèle, à savoir un site de tutoriels. Nous verrons plusieurs manières d’aborder cette thématique, les avantages et inconvénients de chaque méthode, et les conclusions qui s’imposent. Attention, l’ergonomie n’est pas au programme : il faudra utiliser vos propres dons ou vous inspirer d’une autre source.
VIII-B. Application simple▲
VIII-B-1. Introduction▲
Ce site est fait le plus simplement possible, chaque script étant indépendant des autres.
Arborescence sous « C:\Web\online\http\cours-php\1-simple » (DocumentRoot) :
- /
- /index.php
- /cours.php
- /auteur.php
VIII-B-2. Les scripts▲
<?
xml version=
"1.0"
encoding=
"utf-8"
?>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
xml:lang
=
"
fr-FR
"
lang
=
"
fr-FR
"
>
<
head>
<
title>
G. Rossolini - Tutoriels PHP<
/title
>
<
meta http-equiv
=
"
content-type
"
content
=
"
text/html; charset=utf-8
"
/
>
<
/head
>
<
body>
<
ul style
=
"
text-align: center
"
>
<
li style
=
"
display: inline
"
><a
href
=
"
./
"
title
=
"
G. Rossolini - Tutoriels PHP
"
>
Accueil<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
./articles.php
"
title
=
"
G. Rossolini - Liste des publications
"
>
Publications<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
./auteurs.php
"
title
=
"
G. Rossolini - Liste des auteurs
"
>
Auteurs<
/a
></li
>
<
/ul
>
<
h1 style
=
"
text-align: center
"
>
Guillaume Rossolini - Tutoriels PHP<
/h1
>
<
h2 style
=
"
text-decoration: underline
"
>
Présentation<
/h2
>
<
p>
J’ai suivi une formation dans plusieurs domaines, notamment la communication,
le développement de logiciels, la gestion de projet,
<
span style
=
"
font-style: italic
"
>
,etc<
/span
>
.<
/p
>
<
p>
Aujoud'hui, je suis un développeur PHP muni de la certification Zend :<
br /
>
<
a
href
=
"
http://www.zend.com/store/education/certification/authenticate.php?ClientCandidateID=ZEND005053&RegistrationID=221302133
"
title
=
"
Zend Certified Engineer Details
"
><img
src
=
"
http://www.zend.com/images/training/php5_zce_logo.gif
"
alt
=
"
Logo ZCE
"
style
=
"
border: none
"
/
></a
></p
>
<
p style
=
"
text-align: center; font-size: 0.7em;
"
>
Copyright
Guillaume Rossolini 2007-<?php
echo date('Y'
);
?>
<
br /
>
Contact : <
a
href
=
"
mailto:g-rossolini@developpez.com
"
title
=
"
E-mail
"
>
g-rossolini@developpez.com<
/a
></p
>
<
/body
>
<
/html
>
<?php
mysql_connect('localhost'
,
'utilisateur'
,
'motdepasse'
);
mysql_select_db('developpez'
);
function
html($string
)
{
return
utf8_encode(htmlspecialchars($string
,
ENT_QUOTES));
}
?>
<?
xml version=
"1.0"
encoding=
"utf-8"
?>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
xml:lang
=
"
fr-FR
"
lang
=
"
fr-FR
"
>
<
head>
<
title>
G. Rossolini - Articles<
/title
>
<
meta http-equiv
=
"
content-type
"
content
=
"
text/html; charset=utf-8
"
/
>
<
/head
>
<
body>
<
ul style
=
"
text-align: center
"
>
<
li style
=
"
display: inline
"
><a
href
=
"
./
"
title
=
"
G. Rossolini - Tutoriels PHP
"
>
Accueil<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
./articles.php
"
title
=
"
G. Rossolini - Liste des publications
"
>
Publications<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
./auteurs.php
"
title
=
"
G. Rossolini - Liste des auteurs
"
>
Auteurs<
/a
></li
>
<
/ul
>
<?php
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications<
/h1
>
<?php
$sql
=
'SELECT c.id, c.name, COUNT(*) AS nb_articles
FROM category AS c
INNER JOIN article AS a ON a.category_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name'
;
$db_categories
=
mysql_query($sql
);
while
($category
=
mysql_fetch_assoc($db_categories
))
{
?>
<
h2>
<?php
echo html($category
[
'name'
]
);
?>
<
/h2
>
<?php
$sql
=
'SELECT ar.id, ar.title,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.category_id = %d
ORDER BY ar.title'
;
$db_article
=
mysql_query(sprintf($sql
,
$category
[
'id'
]
));
?>
<
ul>
<?php
while
($article
=
mysql_fetch_assoc($db_article
))
{
?>
<
li><a href
=
"
./articles.php?id=
<?php
echo (int)
$article
[
'id'
];
?>
"
title
=
"
<?php
echo html($article
[
'title'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
>
, par <
a href
=
"
./auteurs.php?id=
<?php
echo (int)
$article
[
'author_id'
];
?>
"
title
=
""
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
}
else
{
$id
=
(int)
$_GET
[
'id'
];
$sql
=
'SELECT ar.title, ar.text,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.id = %d'
;
$db_article
=
mysql_query(sprintf($sql
,
$id
));
if
($article
=
mysql_fetch_assoc($db_article
))
{
$article
[
'text'
]
=
utf8_decode($article
[
'text'
]
);
?>
<
h1 style
=
"
text-align: center
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/h1
>
<
div style
=
"
text-align: center; font-style: italic
"
>
Par <
a
href
=
"
./auteurs.php?id=
<?php
echo html($article
[
'author_id'
]
);
?>
"
title
=
"
Publications de
<?php
echo html($article
[
'author_name'
]
);
?>
"
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></div
>
<?php
echo nl2br(html($article
[
'text'
]
));
}
else
{
echo "Cet article n’existe pas..."
;
}
}
?>
<
p style
=
"
text-align: center; font-size: 0.7em;
"
>
Copyright
Guillaume Rossolini 2007-<?php
echo date('Y'
);
?>
<
br /
>
Contact : <
a
href
=
"
mailto:g-rossolini@developpez.com
"
title
=
"
E-mail
"
>
g-rossolini@developpez.com<
/a
></p
>
<
/body
>
<
/html
>
<?php
mysql_connect('localhost'
,
'utilisateur'
,
'motdepasse'
);
mysql_select_db('developpez'
);
function
html($string
)
{
return
utf8_encode(htmlspecialchars($string
,
ENT_QUOTES));
}
?>
<?
xml version=
"1.0"
encoding=
"utf-8"
?>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
xml:lang
=
"
fr-FR
"
lang
=
"
fr-FR
"
>
<
head>
<
title>
G. Rossolini - Publications<
/title
>
<
meta http-equiv
=
"
content-type
"
content
=
"
text/html; charset=utf-8
"
/
>
<
/head
>
<
body>
<
ul style
=
"
text-align: center
"
>
<
li style
=
"
display: inline
"
><a
href
=
"
./
"
title
=
"
G. Rossolini - Tutoriels PHP
"
>
Accueil<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
./articles.php
"
title
=
"
G. Rossolini - Liste des publications
"
>
Publications<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
./auteurs.php
"
title
=
"
G. Rossolini - Liste des auteurs
"
>
Auteurs<
/a
></li
>
<
/ul
>
<?php
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Auteurs<
/h1
>
<
ul style
=
"
list-style-type: none
"
>
<?php
$sql
=
'SELECT au.id, au.name, COUNT(*) AS nb_articles
FROM author AS au
LEFT JOIN article AS ar ON ar.author_id = au.id
GROUP BY au.id, au.name
ORDER BY name'
;
$db_authors
=
mysql_query($sql
);
while
($author
=
mysql_fetch_assoc($db_authors
))
{
?>
<
li><a
href
=
"
./auteurs.php?id=
<?php
echo (int)
$author
[
'id'
];
?>
"
title
=
"
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($author
[
'name'
]
);
?>
<
/a
>
(<?php
echo (int)
$author
[
'nb_articles'
];
?>
articles)<
/li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
$id
=
(int)
$_GET
[
'id'
];
$sql
=
'SELECT name
FROM author
WHERE id = %d'
;
$db_author
=
mysql_query(sprintf($sql
,
$id
));
if
($author
=
mysql_fetch_assoc($db_author
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications de <?php
echo html($author
[
'name'
]
);
?>
<
/h1
>
<
ul>
<?php
$sql
=
'SELECT id, title
FROM article
WHERE author_id = %d
ORDER BY title'
;
$db_article
=
mysql_query(sprintf($sql
,
$id
));
while
($article
=
mysql_fetch_assoc($db_article
))
{
?>
<
li><a
href
=
"
./articles.php?id=
<?php
echo html($article
[
'id'
]
);
?>
"
title
=
"
Article par
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
echo "Cet auteur n’existe pas..."
;
}
}
?>
<
p style
=
"
text-align: center; font-size: 0.7em;
"
>
Copyright
Guillaume Rossolini 2007-<?php
echo date('Y'
);
?>
<
br /
>
Contact : <
a
href
=
"
mailto:g-rossolini@developpez.com
"
title
=
"
E-mail
"
>
g-rossolini@developpez.com<
/a
></p
>
<
/body
>
<
/html
>
Même si tout est parfaitement fonctionnel et que le code XHTML produit est parfaitement standard, voyons maintenant les inconvénients de cette approche, si souvent utilisée par les débutants avant qu’ils prennent de l’expérience.
VIII-B-3. Avantages▲
Ce code est très rapide à écrire.
Puisque le code de chaque script n’est pas très long, nous avons une bonne vision détaillée de leur comportement (en un clin d’œil).
VIII-B-4. Inconvénients▲
L’en-tête et le pied de page (code XHTML) sont répétés dans tous les documents, alors qu’ils sont toujours identiques. Nous pourrions définir des fonctions, mais le plus adéquat est d’inclure des fichiers.
Les paramètres d’accès et la connexion à la base de données sont eux aussi répétés dans plusieurs scripts. Une inclusion serait également bienvenue.
Dans le même ordre d’idée, la fonction html() est déclarée à l’identique dans tous les scripts.
Le dialogue avec la base de données se fait au moyen de l’API standard de MySQL, ce qui est très restrictif. Il serait préférable d’utiliser PDO.
À chaque retour de requête, nous sommes obligés de faire manuellement une conversion d’encodage. Utiliser une classe étendue de PDO nous permettra de nous affranchir de ce problème.
Un problème moins évident est que le terme « php » apparaît dans les URL. Par conséquent, ces URL sont éligibles pour toutes les recherches sur « php » dans les moteurs de recherche. Dans mon cas ce n’est pas grave puisqu’il s’agit justement de tutoriels PHP, mais imaginons un site sur l’alimentaire ou tout autre sujet en décalage complet avec PHP… En ce cas, l’extension de fichier dans l’URL n’est pas une bonne chose, il est préférable de s’en débarrasser. Cela peut même améliorer la sécurité de votre site…
Enfin, il y a bien trop de mélanges de PHP et de XHTML, le code du script est donc très complexe à lire. Imaginez que j’introduise du code CSS, JavaScript, etc., le code source de mon script deviendrait rapidement incompréhensible.
L’inconvénient majeur est donc la maintenabilité du code : nous n’avons adopté que très peu de règles d’écriture du code, et par conséquent il est difficile à relire.
VIII-C. Inclusions▲
VIII-C-1. Introduction▲
Dans un premier temps, nous allons corriger la répétition du code.
Arborescence sous « C:\Web\online\http\cours-php\2-includes » (DocumentRoot) :
- /
- /index.php
- /articles.php
- /auteurs.php
Arborescence sous « C:\Web\offline\sites\cours-php\2-includes » (hors de DocumentRoot) :
- /
- /footer.html
- /header.html
- /common.php
- /functions.php
Le moyen le plus efficace de rendre les fichiers à inclure accessibles aux scripts du site, est de modifier la configuration d’Apache via le httpd.conf ou .htaccess. Cela nous évitera de mettre tout le chemin vers les fichiers dans tous nos scripts. Voici comment cela se présente dans mon cas :
VIII-C-2. Les scripts▲
<Directory "C:/Web/online/http/cours-php/2-includes">
AllowOverride
None
php_value
include_path ".;C:/Web/offline/sites/cours-php/2-includes"
</Directory>
<?php
include 'header.tpl'
;
?>
<
h1 style
=
"
text-align: center
"
>
Guillaume Rossolini - Tutoriels PHP<
/h1
>
<
h2 style
=
"
text-decoration: underline
"
>
Présentation<
/h2
>
<
p>
J’ai suivi une formation dans plusieurs domaines, notamment la communication,
le développement de logiciels, la gestion de projet,
<
span style
=
"
font-style: italic
"
>
,etc<
/span
>
.<
/p
>
<
p>
Aujoud'hui, je suis un développeur PHP muni de la certification Zend :<
br /
>
<
a href
=
"
http://www.zend.com/store/education/certification/authenticate.php?ClientCandidateID=ZEND005053&RegistrationID=221302133
"
title
=
"
Zend Certified Engineer Details
"
><img
src
=
"
http://www.zend.com/images/training/php5_zce_logo.gif
"
alt
=
"
Logo ZCE
"
style
=
"
border: none
"
/
></a
></p
>
<?php
include 'footer.tpl'
;
<?php
require_once 'common.php'
;
require_once 'functions.php'
;
include 'header.tpl'
;
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications<
/h1
>
<?php
$sql
=
'SELECT c.id, c.name, COUNT(*) AS nb_articles
FROM category AS c
INNER JOIN article AS a ON a.category_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name'
;
$db_categories
=
mysql_query($sql
) or
die(mysql_error());
while
($category
=
mysql_fetch_assoc($db_categories
))
{
?>
<
h2>
<?php
echo html($category
[
'name'
]
);
?>
<
/h2
>
<?php
$sql
=
'SELECT ar.id, ar.title,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.category_id = %d
ORDER BY ar.title'
;
$db_article
=
mysql_query(sprintf($sql
,
$category
[
'id'
]
));
?>
<
ul>
<?php
while
($article
=
mysql_fetch_assoc($db_article
))
{
?>
<
li><a href
=
"
./articles.php?id=
<?php
echo (int)
$article
[
'id'
];
?>
"
title
=
"
<?php
echo html($article
[
'title'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
>
, par <
a href
=
"
./auteurs.php?id=
<?php
echo (int)
$article
[
'author_id'
];
?>
"
title
=
""
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
}
else
{
$id
=
(int)
$_GET
[
'id'
];
$sql
=
'SELECT ar.title, ar.text,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.id = %d'
;
$db_article
=
mysql_query(sprintf($sql
,
$id
));
if
($article
=
mysql_fetch_assoc($db_article
))
{
$article
[
'text'
]
=
utf8_decode($article
[
'text'
]
);
?>
<
h1 style
=
"
text-align: center
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/h1
>
<
div style
=
"
text-align: center; font-style: italic
"
>
Par <
a
href
=
"
./auteurs.php?id=
<?php
echo (int)
$article
[
'author_id'
];
?>
"
title
=
"
Publications de
<?php
echo html($article
[
'author_name'
]
);
?>
"
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></div
>
<?php
echo nl2br(html($article
[
'text'
]
));
}
else
{
echo "Cet article n’existe pas..."
;
}
}
include 'footer.tpl'
;
<?php
require_once 'common.php'
;
require_once 'functions.php'
;
include 'header.tpl'
;
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Auteurs<
/h1
>
<
ul style
=
"
list-style-type: none
"
>
<?php
$sql
=
'SELECT au.id, au.name, COUNT(*) AS nb_articles
FROM author AS au
LEFT JOIN article AS ar ON ar.author_id = au.id
GROUP BY au.id, au.name
ORDER BY name'
;
$db_authors
=
mysql_query($sql
);
while
($author
=
mysql_fetch_assoc($db_authors
))
{
?>
<
li><a
href
=
"
./auteurs.php?id=
<?php
echo (int)
$author
[
'id'
];
?>
"
title
=
"
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($author
[
'name'
]
);
?>
<
/a
>
(<?php
echo (int)
$author
[
'nb_articles'
];
?>
articles)<
/li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
$id
=
(int)
$_GET
[
'id'
];
$sql
=
'SELECT name
FROM author
WHERE id = %d'
;
$db_author
=
mysql_query(sprintf($sql
,
$id
));
if
($author
=
mysql_fetch_assoc($db_author
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications de <?php
echo html($author
[
'name'
]
);
?>
<
/h1
>
<
ul>
<?php
$sql
=
'SELECT id, title
FROM article
WHERE author_id = %d
ORDER BY title'
;
$db_article
=
mysql_query(sprintf($sql
,
$id
));
while
($article
=
mysql_fetch_assoc($db_article
))
{
?>
<
li><a
href
=
"
./articles.php?id=
<?php
echo (int)
$article
[
'id'
];
?>
"
title
=
"
Article par
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
echo "Cet auteur n’existe pas..."
;
}
}
include 'footer.tpl'
;
<?php
mysql_connect('localhost'
,
'utilisateur'
,
'motdepasse'
);
mysql_select_db('developpez'
);
<?php
function
html($string
)
{
return
utf8_encode(htmlspecialchars($string
,
ENT_QUOTES));
}
<?xml version
=
"1.0"
encoding
=
"utf-8"
?>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
>
<html xmlns
=
"http://www.w3.org/1999/xhtml"
xml
:
lang
=
"fr-FR"
lang
=
"fr-FR"
>
<head>
<title>G. Rossolini - Tutoriels PHP</title>
<meta http-equiv
=
"content-type"
content
=
"text/html; charset=utf-8"
/>
</head>
<body>
<ul style
=
"text-align: center"
>
<li style
=
"display: inline"
><a
href
=
"./"
title
=
"G. Rossolini - Tutoriels PHP"
>
Accueil</a>
-</li>
<li style
=
"display: inline"
><a
href
=
"./articles.php"
title
=
"G. Rossolini - Liste des publications"
>
Publications</a>
-</li>
<li style
=
"display: inline"
><a
href
=
"./auteurs.php"
title
=
"G. Rossolini - Liste des auteurs"
>
Auteurs</a></li>
</ul>
<p style
=
"text-align: center; font-size: 0.7em;"
>
Copyright
Guillaume Rossolini 2007-<?php echo date('Y'
); ?><br />
Contact : <a
href
=
"mailto:g-rossolini@developpez.com"
title
=
"E-mail"
>
g-rossolini@developpez.com</a></p>
</body>
</html>
VIII-C-3. Nouveaux avantages▲
Toute modification de l’en-tête ou du pied de page est immédiatement répercutée dans toutes les pages du site.
Puisqu’ils sont hors du DocumentRoot, les fichiers à inclure sont protégés de tout accès externe. Leur chemin d’accès étant dans l’include_path de PHP, ils sont également facilement accessibles.
VIII-C-4. Nouveaux inconvénients▲
Les fichiers à inclure ne sont plus intégralement copiés dans les scripts effectifs, mais l’on se rend compte que les instructions require* et include* se répètent d’un script à l’autre. Nous verrons plus loin que MVC nous permet d’éviter cette situation.
Par exemple, si je souhaite conserver des statistiques sur mes visiteurs, une solution serait de créer un script « stats.php » à inclure dans toutes mes pages. Je serais donc obligé de répéter ce code dans tous les scripts :
<?php
require_once 'common.php'
;
require_once 'functions.php'
;
include 'stats.php'
;
include 'header.tpl'
;
Un problème évident avec le code ci-dessus est que nous pourrions oublier d’inclure « stats.php » dans l’un des scripts… Un autre est que le bloc d’includes/requires est tout simplement du code répété, or il nous faut éviter ce genre de situations.
VIII-D. Sans l’extension « .php »▲
VIII-D-1. Introduction▲
Nous avons déjà établi que l’extension « .php » est un problème dans l’URL. Maintenant que notre menu d’en-tête est géré par des inclusions, nous pouvons plus facilement modifier les liens du site.
Le principe est d’utiliser des URL absolues à partir de la racine du site, donc commençant par « / » plutôt que « ./ », pour chacun des liens. Nous allons mettre chaque script dans un répertoire, ce qui nous permettra de l’appeler « index.php » et ainsi de nous passer totalement du nom du script dans l’URL. Un nouveau fichier de configuration permet de respecter DRY (don't repeat yourself).
Arborescence sous « C:\Web\online\http\cours-php\3-extensions » (DocumentRoot) :
- /
- /index.php
- /articles/index.php
- /auteurs/index.php
Arborescence sous « C:\Web\offline\sites\cours-php\3-extensions » (hors de DocumentRoot) :
- /
- /footer.html
- /header.html
- /common.php
- /functions.php
VIII-D-2. Les scripts modifiés▲
<Directory "C:/Web/online/http/cours-php/3-extensions">
AllowOverride
None
php_value
include_path ".;C:/Web/offline/sites/cours-php/3-extensions"
SetEnv
HTTP_ROOT /cours-php/3-extensions/
</Directory>
<?
xml version=
"1.0"
encoding=
"utf-8"
?>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
xml:lang
=
"
fr-FR
"
lang
=
"
fr-FR
"
>
<
head>
<
title>
G. Rossolini - Tutoriels PHP<
/title
>
<
meta http-equiv
=
"
content-type
"
content
=
"
text/html; charset=utf-8
"
/
>
<
/head
>
<
body>
<
ul style
=
"
text-align: center
"
>
<
li style
=
"
display: inline
"
><a
href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
"
title
=
"
G. Rossolini - Tutoriels PHP
"
>
Accueil<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
articles/
"
title
=
"
G. Rossolini - Liste des publications
"
>
Publications<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteurs/
"
title
=
"
G. Rossolini - Liste des auteurs
"
>
Auteurs<
/a
></li
>
<
/ul
>
<?php
require_once 'common.php'
;
require_once 'functions.php'
;
include 'header.tpl'
;
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications<
/h1
>
<?php
$sql
=
'SELECT c.id, c.name, COUNT(*) AS nb_articles
FROM category AS c
INNER JOIN article AS a ON a.category_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name'
;
$db_categories
=
mysql_query($sql
) or
die(mysql_error());
while
($category
=
mysql_fetch_assoc($db_categories
))
{
?>
<
h2>
<?php
echo html($category
[
'name'
]
);
?>
<
/h2
>
<?php
$sql
=
'SELECT ar.id, ar.title,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.category_id = %d
ORDER BY ar.title'
;
$db_article
=
mysql_query(sprintf($sql
,
$category
[
'id'
]
));
?>
<
ul>
<?php
while
($article
=
mysql_fetch_assoc($db_article
))
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
articles/?id=
<?php
echo (int)
$article
[
'id'
];
?>
"
title
=
"
<?php
echo html($article
[
'title'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
>
, par <
a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteurs/?id=
<?php
echo (int)
$article
[
'author_id'
];
?>
"
title
=
""
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
}
else
{
$id
=
(int)
$_GET
[
'id'
];
$sql
=
'SELECT ar.title, ar.text,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.id = %d'
;
$db_article
=
mysql_query(sprintf($sql
,
$id
));
if
($article
=
mysql_fetch_assoc($db_article
))
{
$article
[
'text'
]
=
utf8_decode($article
[
'text'
]
);
?>
<
h1 style
=
"
text-align: center
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/h1
>
<
div style
=
"
text-align: center; font-style: italic
"
>
Par <
a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteurs/?id=
<?php
echo (int)
$article
[
'author_id'
];
?>
"
title
=
"
Publications de
<?php
echo html($article
[
'author_name'
]
);
?>
"
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></div
>
<?php
echo nl2br(html($article
[
'text'
]
));
}
else
{
echo "Cet article n’existe pas..."
;
}
}
include 'footer.tpl'
;
<?php
require_once 'common.php'
;
require_once 'functions.php'
;
include 'header.tpl'
;
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Auteurs<
/h1
>
<
ul style
=
"
list-style-type: none
"
>
<?php
$sql
=
'SELECT au.id, au.name, COUNT(*) AS nb_articles
FROM author AS au
LEFT JOIN article AS ar ON ar.author_id = au.id
GROUP BY au.id, au.name
ORDER BY name'
;
$db_authors
=
mysql_query($sql
);
while
($author
=
mysql_fetch_assoc($db_authors
))
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteurs/?id=
<?php
echo (int)
$author
[
'id'
];
?>
"
title
=
"
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($author
[
'name'
]
);
?>
<
/a
>
(<?php
echo (int)
$author
[
'nb_articles'
];
?>
articles)<
/li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
$id
=
(int)
$_GET
[
'id'
];
$sql
=
'SELECT name
FROM author
WHERE id = %d'
;
$db_author
=
mysql_query(sprintf($sql
,
$id
));
if
($author
=
mysql_fetch_assoc($db_author
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications de <?php
echo html($author
[
'name'
]
);
?>
<
/h1
>
<
ul>
<?php
$sql
=
'SELECT id, title
FROM article
WHERE author_id = %d
ORDER BY title'
;
$db_article
=
mysql_query(sprintf($sql
,
$id
));
while
($article
=
mysql_fetch_assoc($db_article
))
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
articles/?id=
<?php
echo (int)
$article
[
'id'
];
?>
"
title
=
"
Article par
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
echo "Cet auteur n’existe pas..."
;
}
}
include 'footer.tpl'
;
VIII-D-3. Nouveaux avantages▲
L’extension « .php » n’apparaît plus dans l’URL, les moteurs de recherche peuvent donc référencer la totalité de nos URL sans trouver de mot inutile.
VIII-D-4. Nouveaux inconvénients▲
Nous avons maintenant autant de répertoires publics que de scripts, ce qui n’est pas nécessairement très pratique ou utile pour notre organisation interne.
VIII-E. Modèles (classes pour la BDD)▲
VIII-E-1. Introduction▲
Arborescence sous « C:\Web\online\http\cours-php\4-modeles » (DocumentRoot) :
- /
- /index.php
- /articles.php
- /auteurs.php
Arborescence sous « C:\Web\offline\sites\cours-php\4-modeles » (hors de DocumentRoot) :
- /
- /AbstractTable.php
- /Article.php
- /Author.php
- /Category.php
- /common.php
- /footer.html
- /functions.php
- /header.html
- /MyPDO.php
VIII-E-2. Les scripts▲
<Directory "C:/Web/online/http/cours-php/4-modeles">
AllowOverride
None
php_value
include_path ".;C:/Web/offline/sites/cours-php/4-modeles"
SetEnv
HTTP_ROOT /cours-php/4-modeles/
</Directory>
<?php
abstract
class
AbstractTable
{
protected
$db
;
protected
$selectAll
;
protected
$selectById
;
public
function
__construct
($db
)
{
$this
->
db =
$db
;
}
public
function
getAll()
{
$this
->
selectAll->
execute();
return
$this
->
selectAll->
fetchAll();
}
public
function
getById($id
)
{
$this
->
selectById->
execute(array
($id
));
$row
=
$this
->
selectById->
fetchAll();
if
(empty($row
[
0
]
))
{
return
array
();
}
else
{
return
$row
[
0
];
}
}
}
<?php
spl_autoload_register('generic_autoload'
);
$db
=
new
MyPDO(
'mysql:host=localhost;dbname=developpez'
,
'utilisateur'
,
'motdepasse'
);
<?php
function
html($string
)
{
return
utf8_encode(htmlspecialchars($string
,
ENT_QUOTES));
}
function
generic_autoload($class
)
{
require_once $class
.
'.php'
;
}
<?php
class
MyPDO extends
PDO
{
public
function
__construct
($dsn
,
$user
=
NULL
,
$password
=
NULL
)
{
parent
::
__construct
($dsn
,
$user
,
$password
);
$this
->
setAttribute(PDO::
ATTR_ERRMODE,
PDO::
ERRMODE_EXCEPTION);
}
public
function
prepare($sql
,
$options
=
NULL
)
{
$statement
=
parent
::
prepare($sql
);
if
(strpos(strtoupper($sql
),
'SELECT'
) ===
0
) //requête "SELECT"
{
$statement
->
setFetchMode(PDO::
FETCH_ASSOC
);
}
return
$statement
;
}
}
<?php
class
Article extends
AbstractTable
{
protected
$selectById
;
protected
$selectByAuthor
;
protected
$selectByCategory
;
public
function
__construct
($db
)
{
parent
::
__construct
($db
);
$sql
=
'SELECT ar.title, ar.text,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.id = ?'
;
$this
->
selectById =
$this
->
db->
prepare($sql
);
$sql
=
'SELECT id, title
FROM article
WHERE author_id = ?
ORDER BY title'
;
$this
->
selectByAuthor =
$this
->
db->
prepare($sql
);
$sql
=
'SELECT ar.id, ar.title,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.category_id = ?
ORDER BY ar.title'
;
$this
->
selectByCategory =
$this
->
db->
prepare($sql
);
}
public
function
getById($id
)
{
$this
->
selectById->
execute(array
($id
));
$articles
=
$this
->
selectById->
fetchAll();
if
(empty($articles
[
0
]
))
{
return
array
();
}
else
{
$articles
[
0
][
'text'
]
=
utf8_decode($articles
[
0
][
'text'
]
);
return
$articles
[
0
];
}
}
public
function
getByAuthor($id
)
{
$this
->
selectByAuthor->
execute(array
($id
));
return
$this
->
selectByAuthor->
fetchAll();
}
public
function
getByCategory($id
)
{
$this
->
selectByCategory->
execute(array
($id
));
return
$this
->
selectByCategory->
fetchAll();
}
}
<?php
class
Author extends
AbstractTable
{
public
function
__construct
($db
)
{
parent
::
__construct
($db
);
$sql
=
'SELECT au.id, au.name, COUNT(*) AS nb_articles
FROM author AS au
LEFT JOIN article AS ar ON ar.author_id = au.id
GROUP BY au.id, au.name
ORDER BY name'
;
$this
->
selectAll =
$this
->
db->
prepare($sql
);
$sql
=
'SELECT name
FROM author
WHERE id = ?'
;
$this
->
selectById =
$this
->
db->
prepare($sql
);
}
}
<?php
class
Category extends
AbstractTable
{
public
function
__construct
($db
)
{
parent
::
__construct
($db
);
$sql
=
'SELECT c.id, c.name, COUNT(*) AS nb_articles
FROM category AS c
INNER JOIN article AS a ON a.category_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name'
;
$this
->
selectAll =
$this
->
db->
prepare($sql
);
}
}
<?php
require_once 'functions.php'
;
include 'header.tpl'
;
?>
<
h1 style
=
"
text-align: center
"
>
Guillaume Rossolini - Tutoriels PHP<
/h1
>
<
h2 style
=
"
text-decoration: underline
"
>
Présentation<
/h2
>
<
p>
J’ai suivi une formation dans plusieurs domaines, notamment la communication,
le développement de logiciels, la gestion de projet,
<
span style
=
"
font-style: italic
"
>
,etc<
/span
>
.<
/p
>
<
p>
Aujoud'hui, je suis un développeur PHP muni de la certification Zend :<
br /
>
<
a href
=
"
http://www.zend.com/store/education/certification/authenticate.php?ClientCandidateID=ZEND005053&RegistrationID=221302133
"
title
=
"
Zend Certified Engineer Details
"
><img
src
=
"
http://www.zend.com/images/training/php5_zce_logo.gif
"
alt
=
"
Logo ZCE
"
style
=
"
border: none
"
/
></a
></p
>
<?php
include 'footer.tpl'
;
<?php
require_once 'functions.php'
;
require_once 'common.php'
;
include 'header.tpl'
;
$articles
=
new
Article($db
);
$categories
=
new
Category($db
);
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications<
/h1
>
<?php
foreach
($categories
->
getAll() as
$category
)
{
?>
<
h2>
<?php
echo html($category
[
'name'
]
);
?>
<
/h2
>
<?php
?>
<
ul>
<?php
foreach
($articles
->
getByCategory($category
[
'id'
]
) as
$article
)
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
articles/?id=
<?php
echo (int)
$article
[
'id'
];
?>
"
title
=
"
<?php
echo html($article
[
'title'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
>
, par <
a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteurs/?id=
<?php
echo (int)
$article
[
'author_id'
];
?>
"
title
=
""
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
}
else
{
$id
=
(int)
$_GET
[
'id'
];
if
($article
=
$articles
->
getById($id
))
{
?>
<
h1 style
=
"
text-align: center
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/h1
>
<
div style
=
"
text-align: center; font-style: italic
"
>
Par <
a
href
=
"
./auteurs.php?id=
<?php
echo (int)
$article
[
'author_id'
];
?>
"
title
=
"
Publications de
<?php
echo html($article
[
'author_name'
]
);
?>
"
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></div
>
<?php
echo nl2br(html($article
[
'text'
]
));
}
else
{
echo "Cet article n’existe pas..."
;
}
}
include 'footer.tpl'
;
<?php
require_once 'functions.php'
;
require_once 'common.php'
;
include 'header.tpl'
;
$authors
=
new
Author($db
);
$articles
=
new
Article($db
);
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Auteurs<
/h1
>
<
ul style
=
"
list-style-type: none
"
>
<?php
foreach
($authors
->
getAll() as
$author
)
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteurs/?id=
<?php
echo (int)
$author
[
'id'
];
?>
"
title
=
"
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($author
[
'name'
]
);
?>
<
/a
>
(<?php
echo (int)
$author
[
'nb_articles'
];
?>
articles)<
/li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
$id
=
(int)
$_GET
[
'id'
];
if
($author
=
$authors
->
getById($id
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications de <?php
echo html($author
[
'name'
]
);
?>
<
/h1
>
<
ul>
<?php
foreach
($articles
->
getByAuthor($id
) as
$article
)
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
articles/?id=
<?php
echo (int)
$article
[
'id'
];
?>
"
title
=
"
Article par
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
echo "Cet auteur n’existe pas..."
;
}
}
include 'footer.tpl'
;
VIII-E-3. Nouveaux avantages▲
Le code peut maintenant être porté sans aucun problème d’un SGBD à un autre. De plus, les requêtes sont facilement identifiables puisqu’elles sont regroupées dans des classes, et nous sommes totalement protégés contre les injections SQL.
VIII-E-4. Nouveaux inconvénients▲
Les scripts à inclure sont tous regroupés et mélangés, il n’y a aucune organisation. Cela peut convenir pour un site de quelques pages, mais, à mesure que le site grossit, il devient apparent qu’il faut structurer ces fichiers. Il est temps d’adopter une structure MVC.
Il reste encore beaucoup de mélange PHP/HTML. Heureusement, nous avons effectué de nombreuses mises en facteur du reste du code, et par conséquent ces mélanges PHP/HTML sont à présent tout à fait lisibles.
VIII-F. URL Rewriting, ou Routage▲
VIII-F-1. Introduction▲
Nous pouvons maintenant résoudre le problème des URL d’accès à nos pages. En effet, nous avons vu plus tôt dans ce cours que les URL à base de paramètres GET visibles sont peu efficaces, tant pour l’œil humain que pour les robots qui s’occupent de référencer nos sites.
Une solution à ces problèmes est la réécriture de liens, aussi appelée « URL Rewriting » ou encore « routage ».
Nous allons utiliser certains aspects avancés d’Apache, et en particulier le module mod_rewrite (qu’il faut donc activer dans httpd.conf). Il nous faudra aussi de bonnes connaissances en expressions régulières (aka « regex » ou « expressions rationnelles »).
Le principe de la réécriture de liens est d’utiliser une URL publique ne correspondant pas à un fichier sur le serveur. Le mod_rewrite nous permet de router ces URL vers les scripts de notre serveur, en interne, c’est-à-dire sans causer de redirection HTTP et sans que le client s’en rende compte. Nous pouvons ainsi choisir les paramètres GET les plus adaptés, sans pour autant encombrer le code de nos scripts PHP.
Arborescence sous « C:\Web\online\http\cours-php\5-routing » (DocumentRoot) :
- /
- /index.php
- /articles.php
- /auteurs.php
Arborescence sous « C:\Web\offline\sites\cours-php\5-routing » (hors de DocumentRoot) :
- /
- /AbstractTable.php
- /Article.php
- /Author.php
- /Category.php
- /common.php
- /footer.html
- /functions.php
- /header.html
- /MyPDO.php
Pour pouvoir utiliser la réécriture avec Apache dans un fichier .htaccess, vous aurez besoin d’activer l’option +FollowSymLinks :
Options
+FollowSymLinks
RewriteEngine
On
Sans cette option, utiliser le RewriteEngine dans un fichier .htaccess donnera lieu à une erreur HTTP 403 et à une entrée log de ce style :
Options FollowSymLinks or SymLinksIfOwnerMatch is off
which implies that RewriteRule directive is forbidden
VIII-F-2. Les scripts▲
Nous aurons peu de modifications. Les liens doivent maintenant être absolus afin de ne pas porter à confusion ; pour le reste, le httpd.conf est à peu près le seul fichier à modifier. Je vais indiquer ici uniquement les fichiers modifiés :
<Directory "C:/Web/online/http/cours-php/5-routing">
AllowOverride
None
php_value
include_path ".;C:/Web/offline/sites/cours-php/5-routing"
SetEnv
HTTP_ROOT /cours-php/5-routing/
RewriteEngine
on
RewriteBase
/cours-php/5-routing/
RewriteRule
^auteurs/$ auteurs.php [L]
RewriteRule
^auteur/([0-9])-?.*$ auteurs.php?id=$1 [L]
RewriteRule
^articles/$ articles.php [L]
RewriteRule
^article/([0-9])-?.*$ articles.php?id=$1 [L]
</Directory>
<?php
function
html($string
)
{
return
utf8_encode(htmlspecialchars($string
,
ENT_QUOTES));
}
function
url_format($string
)
{
$string
=
str_replace(' '
,
'-'
,
strtolower($string
));
return
html(preg_replace('#[^a-z0-9-]#'
,
''
,
$string
));
}
function
generic_autoload($class
)
{
require_once $class
.
'.php'
;
}
<?php
require_once 'functions.php'
;
require_once 'common.php'
;
include 'header.tpl'
;
$authors
=
new
Author($db
);
$articles
=
new
Article($db
);
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Auteurs<
/h1
>
<
ul style
=
"
list-style-type: none
"
>
<?php
foreach
($authors
->
getAll() as
$author
)
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteur/
<?php
echo url_format($author
[
'id'
].
'-'
.
$author
[
'name'
]
);
?>
"
title
=
"
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($author
[
'name'
]
);
?>
<
/a
>
(<?php
echo (int)
$author
[
'nb_articles'
];
?>
articles)<
/li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
$id
=
(int)
$_GET
[
'id'
];
if
($author
=
$authors
->
getById($id
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications de <?php
echo html($author
[
'name'
]
);
?>
<
/h1
>
<
ul>
<?php
foreach
($articles
->
getByAuthor($id
) as
$article
)
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
article/
<?php
echo url_format($article
[
'id'
].
'-'
.
$article
[
'title'
]
);
?>
"
title
=
"
Article par
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
else
{
echo "Cet auteur n’existe pas..."
;
}
}
include 'footer.tpl'
;
<?php
require_once 'functions.php'
;
require_once 'common.php'
;
include 'header.tpl'
;
$articles
=
new
Article($db
);
$categories
=
new
Category($db
);
if
(empty($_GET
[
'id'
]
))
{
?>
<
h1 style
=
"
text-align: center
"
>
Publications<
/h1
>
<?php
foreach
($categories
->
getAll() as
$category
)
{
?>
<
h2>
<?php
echo html($category
[
'name'
]
);
?>
<
/h2
>
<?php
?>
<
ul>
<?php
foreach
($articles
->
getByCategory($category
[
'id'
]
) as
$article
)
{
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
article/
<?php
echo url_format($article
[
'id'
].
'-'
.
$article
[
'title'
]
)
?>
"
title
=
"
<?php
echo html($article
[
'title'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
>
, par <
a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteur/
<?php
echo url_format(
$article
[
'author_id'
].
'-'
.
$article
[
'author_name'
]
);
?>
"
title
=
""
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></li
>
<?php
}
?>
<
/ul
>
<?php
}
}
else
{
$id
=
(int)
$_GET
[
'id'
];
if
($article
=
$articles
->
getById($id
))
{
?>
<
h1 style
=
"
text-align: center
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/h1
>
<
div style
=
"
text-align: center; font-style: italic
"
>
Par <
a
href
=
"
./auteurs.php?id=
<?php
echo html($article
[
'author_id'
]
);
?>
"
title
=
"
Publications de
<?php
echo html($article
[
'author_name'
]
);
?>
"
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></div
>
<?php
echo nl2br(html($article
[
'text'
]
));
}
else
{
echo "Cet article n’existe pas..."
;
}
}
include 'footer.tpl'
;
VIII-F-3. Nouveaux avantages▲
VIII-F-4. Nouveaux inconvénients▲
L’inconvénient majeur est que nous avons maintenant une infinité de points d’entrée pour chaque page. En effet, les anciennes URL fonctionnent encore : c’est inévitable puisque les scripts auteurs.php et articles.php sont encore accessibles depuis l’extérieur.
Par ailleurs, on peut maintenant mettre n’importe quelle chaîne à la suite des identifiants numériques : la présence de l’identifiant est suffisante pour charger la page. Cela pose de gros problèmes de duplicate content (plusieurs URL pour une seule ressource).
Ces deux URL chargent exactement le même document :
- http://localhost/cours-php/4-routing/article/6-dcouverte-des-principaux-moteurs-de-template-en-php
- http://localhost/cours-php/4-routing/article/6-alterner-entre-plusieurs-versions-dapache-et-de-php
Dans cet exemple, la 2° URL est fausse puisque le titre de l’article n° 5 est donné à l’URL conduisant à l’article n° 6. Cependant, nous n’avons mis en place aucun mécanisme pour indiquer à l’utilisateur qu’il n’utilise pas la bonne URL. Si nous envoyons encore une redirection HTTP, les navigateurs risquent de déceler une « redirection infinie » et de ne jamais afficher le document souhaité.
VIII-G. Design pattern MVC (modèle-vue-contrôleur)▲
VIII-G-1. Introduction▲
Un design pattern permet de résoudre la plupart des inconvénients énoncés ci-dessus : MVC.
L’objectif est d’utiliser des contrôleurs pour traiter les données, des modèles pour les récupérer et des vues pour les afficher. Un contrôleur principal, appelé front controller ou encore contrôleur frontal, est utilisé pour centraliser l’ensemble des requêtes du client. Ainsi, un seul script PHP est exposé en HTTP, ce qui limite les problèmes de duplicate content et améliore la sécurité du site. C’est le front controller qui répartit (dispatch) le travail aux autres contrôleurs.
Puisque les URL d’un site utilisant une architecture MVC sont fictives, cela nous impose d’utiliser une forme d’URL Rewriting. Ainsi, les URL peuvent rester identiques à celles définies depuis nos essais d’URL Rewriting. Tout dépend de notre découpage modules/actions.
Notre site actuel est trop simple pour être un exemple suffisamment explicite, mais nous pouvons facilement dégager des actions au sein de chaque contrôleur.
Les contrôleurs :
- Article : Permet de lister (et d’administrer) les articles ;
- Auteur : Permet de lister (et d’administrer) les auteurs ;
- Erreur : Dicte la conduite en cas d’erreur.
Actions dans le contrôleur « Auteur » :
- index : Liste les acteurs ;
- display : Affiche les articles d’un auteur ;
- (filter : Dans un site plus complet, cette action permet d’avoir une liste filtrée) ;
- (update : Dans un site plus complet, cette action met à jour un auteur) ;
- (delete : Dans un site plus complet, cette action supprime un auteur) ;
- etc.
Actions dans le contrôleur « Article » :
- index : Liste les articles ;
- display : Affiche un article ;
- (filter : Dans un site plus complet, cette action permet d’avoir une liste filtrée) ;
- (update : Dans un site plus complet, cette action met à jour un article) ;
- (delete : Dans un site plus complet, cette action supprime un article) ;
- etc.
Actions dans le contrôleur « Erreur » :
- index : Serveur indisponible ;
- http : Redirection, page inaccessible ;
- (sql : Erreur interne) ;
- etc.
Il est maintenant très clair que nous devrions avoir un contrôleur « Catégorie » et que, depuis le début de ce tutoriel, il manque un script pour afficher les catégories. Une bonne architecture peut aider à déceler les problèmes dans les applications.
Arborescence sous « C:\Web\online\http\cours-php\6-mvc » (DocumentRoot) :
- /
- /index.php
Arborescence sous « C:\Web\offline\sites\cours-php\6-mvc » (hors de DocumentRoot) :
- /
- /MyPDO.php
- /Controller
- /Controller/Article.php
- /Controller/Author.php
- /Controller/Error.php
- /Controller/Index.php
- /Controller/Template.php
- /Model
- /Model/Article.php
- /Model/Author.php
- /Model/Category.php
- /Model/Template.php
- /View
- /View/footer.tpl
- /View/header.tpl
- /View/article/
- /View/article/index.tpl
- /View/article/display.tpl
- /View/author/
- /View/author/index.tpl
- /View/author/display.tpl
- /View/error/
- /View/error/index.tpl
- /View/index/
- /View/index/index.tpl
VIII-G-2. Tous les scripts▲
<Directory "C:/Web/online/http/cours-php/6-mvc">
AllowOverride
None
php_value
include_path ".;C:/Web/offline/sites/cours-php/6-mvc"
SetEnv
HTTP_ROOT /cours-php/6-mvc/
RewriteEngine
on
RewriteCond
%{REQUEST_URI} !\.(js|css|jpg|png|gif)$
RewriteRule
.* index.php
</Directory>
La directive RewriteCond ci-dessus n’est pas utile dans notre exemple, mais elle vous montre comment on évite aux images ainsi qu’aux scripts CSS et JavaScript, etc. de passer par notre contrôleur frontal PHP. En effet, ce serait une très grande perte de ressources et cela n’aurait aucune valeur ajoutée.
<?php
require 'functions.php'
;
spl_autoload_register('generic_autoload'
);
Controller_Template::
$db
=
new
MyPDO(
'mysql:host=localhost;dbname=developpez'
,
'utilisateur'
,
'motdepasse'
);
preg_match(
'#^'
.
$_SERVER
[
'HTTP_ROOT'
].
'(?:([a-z]+)/)?#'
,
$_SERVER
[
'REQUEST_URI'
],
$match
);
if
(empty($match
[
1
]
))
{
$controller
=
Controller_Index::
getInstance();
$controller
->
index();
}
else
{
switch
($match
[
1
]
)
{
case
'articles'
:
$controller
=
Controller_Article::
getInstance();
$controller
->
index();
break
;
case
'auteurs'
:
$controller
=
Controller_Author::
getInstance();
$controller
->
index();
break
;
case
'article'
:
if
(preg_match(
'#^'
.
$_SERVER
[
'HTTP_ROOT'
].
'article/([0-9]+)-.*$#'
,
$_SERVER
[
'REQUEST_URI'
],
$match
))
{
$controller
=
Controller_Article::
getInstance();
$controller
->
display($match
[
1
]
);
}
else
{
Controller_Error::
documentNotFound(
"Page introuvable : URL incorrecte"
);
}
break
;
case
'auteur'
:
if
(preg_match(
'#^'
.
$_SERVER
[
'HTTP_ROOT'
].
'auteur/([0-9]+)-.*$#'
,
$_SERVER
[
'REQUEST_URI'
],
$match
))
{
$controller
=
Controller_Author::
getInstance();
$controller
->
display($match
[
1
]
);
}
else
{
Controller_Error::
documentNotFound(
"Page introuvable : URL incorrecte"
);
}
break
;
default
:
Controller_Error::
documentNotFound(
"Page introuvable : URL incorrecte"
);
}
}
<?php
function
html($string
)
{
return
utf8_encode(htmlspecialchars($string
,
ENT_QUOTES));
}
function
url_format($string
)
{
$string
=
str_replace(' '
,
'-'
,
strtolower($string
));
return
html(preg_replace('#[^a-z0-9-]#'
,
''
,
$string
));
}
function
generic_autoload($class
)
{
require_once str_replace('_'
,
'/'
,
$class
).
'.php'
;
}
<?php
class
MyPDO extends
PDO
{
public
function
__construct
($dsn
,
$user
=
NULL
,
$password
=
NULL
)
{
parent
::
__construct
($dsn
,
$user
,
$password
);
$this
->
setAttribute(PDO::
ATTR_ERRMODE,
PDO::
ERRMODE_EXCEPTION);
}
public
function
prepare($sql
,
$options
=
NULL
)
{
$statement
=
parent
::
prepare($sql
);
if
(strpos(strtoupper($sql
),
'SELECT'
) ===
0
) //requête "SELECT"
{
$statement
->
setFetchMode(PDO::
FETCH_ASSOC
);
}
return
$statement
;
}
}
<?php
class
Controller_Article extends
Controller_Template
{
protected
$categoriesModel
;
protected
function
__construct
()
{
parent
::
__construct
();
$this
->
selfModel =
new
Model_Article();
$this
->
categoriesModel =
NULL
;
}
public
function
index()
{
$title
=
"Liste des publications"
;
$this
->
categoriesModel =
new
Model_Category();
$categories
=
$this
->
categoriesModel->
getAll();
foreach
($categories
as
$i
=>
$category
)
{
$categories
[
$i
][
'articles'
]
=
$this
->
selfModel->
getByCategory(
$category
[
'id'
]
);
}
header('Content-Type: text/html; charset=utf-8'
);
require 'View/header.tpl'
;
require 'View/article/index.tpl'
;
require 'View/footer.tpl'
;
}
public
function
display($id
)
{
$article
=
$this
->
selfModel->
getById($id
);
if
(!
$article
)
{
Controller_Error::
documentNotFound();
}
else
{
$title
=
$article
[
'title'
];
header('Content-Type: text/html; charset=utf-8'
);
require 'View/header.tpl'
;
require 'View/article/display.tpl'
;
require 'View/footer.tpl'
;
}
}
}
<?php
class
Controller_Author extends
Controller_Template
{
protected
$articlesModel
;
protected
function
__construct
()
{
parent
::
__construct
();
$this
->
selfModel =
new
Model_Author();
$this
->
articlesModel =
NULL
;
}
public
function
index()
{
$title
=
"Liste des auteurs"
;
$authors
=
$this
->
selfModel->
getAll();
header('Content-Type: text/html; charset=utf-8'
);
require 'View/header.tpl'
;
require 'View/author/index.tpl'
;
require 'View/footer.tpl'
;
}
public
function
display($id
)
{
$author
=
$this
->
selfModel->
getById($id
);
if
(!
$author
)
{
Controller_Error::
documentNotFound("Cet auteur n’existe pas"
);
}
else
{
$title
=
"Publications de "
.
$author
[
'name'
];
$this
->
articlesModel =
new
Model_Article();
$articles
=
$this
->
articlesModel->
getByAuthor($id
);
header('Content-Type: text/html; charset=utf-8'
);
require 'View/header.tpl'
;
require 'View/author/display.tpl'
;
require 'View/footer.tpl'
;
}
}
}
<?php
abstract
class
Controller_Error extends
Controller_Template
{
public
static
function
documentNotFound($title
)
{
header('HTTP/1.1 404 Not Found'
);
header('Content-Type: text/html; charset=utf-8'
);
include 'View/header.tpl'
;
include 'View/error/404.tpl'
;
include 'View/footer.tpl'
;
}
}
<?php
class
Controller_Index extends
Controller_Template
{
public
function
index()
{
$title
=
"Guillaume Rossolini - Tutoriels PHP"
;
header('Content-Type: text/html; charset=utf-8'
);
require 'View/header.tpl'
;
require 'View/index/index.tpl'
;
require 'View/footer.tpl'
;
}
}
<?php
abstract
class
Controller_Template
{
protected
$selfModel
;
protected
static
$instance
;
public
static
$db
;
protected
function
__construct
()
{
}
public
static
function
getInstance()
{
$class
=
get_called_class();
if
(!
$class
::
$instance
)
{
$class
::
$instance
=
new
$class
();
return
$class
::
$instance
;
}
}
}
L’utilisation de la fonction get_called_class() nous oblige à utiliser au minimum la version 5.3 de PHP.
<?php
class
Model_Article extends
Model_Template
{
protected
$selectById
;
protected
$selectByAuthor
;
protected
$selectByCategory
;
public
function
__construct
()
{
parent
::
__construct
();
$sql
=
'SELECT ar.title, ar.text,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.id = ?'
;
$this
->
selectById =
Controller_Template::
$db
->
prepare($sql
);
$sql
=
'SELECT id, title
FROM article
WHERE author_id = ?
ORDER BY title'
;
$this
->
selectByAuthor =
Controller_Template::
$db
->
prepare($sql
);
$sql
=
'SELECT ar.id, ar.title,
au.id AS author_id, au.name AS author_name
FROM article AS ar
INNER JOIN author AS au ON ar.author_id = au.id
WHERE ar.category_id = ?
ORDER BY ar.title'
;
$this
->
selectByCategory =
Controller_Template::
$db
->
prepare($sql
);
}
public
function
getById($id
)
{
$this
->
selectById->
execute(array
($id
));
$articles
=
$this
->
selectById->
fetchAll();
if
(empty($articles
[
0
]
))
{
return
array
();
}
else
{
$articles
[
0
][
'text'
]
=
utf8_decode($articles
[
0
][
'text'
]
);
return
$articles
[
0
];
}
}
public
function
getByAuthor($id
)
{
$this
->
selectByAuthor->
execute(array
($id
));
return
$this
->
selectByAuthor->
fetchAll();
}
public
function
getByCategory($id
)
{
$this
->
selectByCategory->
execute(array
($id
));
return
$this
->
selectByCategory->
fetchAll();
}
}
<?php
class
Model_Author extends
Model_Template
{
public
function
__construct
()
{
parent
::
__construct
();
$sql
=
'SELECT au.id, au.name, COUNT(*) AS nb_articles
FROM author AS au
LEFT JOIN article AS ar ON ar.author_id = au.id
GROUP BY au.id, au.name
ORDER BY name'
;
$this
->
selectAll =
Controller_Template::
$db
->
prepare($sql
);
$sql
=
'SELECT name
FROM author
WHERE id = ?'
;
$this
->
selectById =
Controller_Template::
$db
->
prepare($sql
);
}
}
<?php
class
Model_Category extends
Model_Template
{
public
function
__construct
()
{
parent
::
__construct
();
$sql
=
'SELECT c.id, c.name, COUNT(*) AS nb_articles
FROM category AS c
INNER JOIN article AS a ON a.category_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name'
;
$this
->
selectAll =
Controller_Template::
$db
->
prepare($sql
);
}
}
<?php
abstract
class
Model_Template
{
protected
$selectAll
;
protected
$selectById
;
public
function
__construct
()
{
}
public
function
getAll()
{
$this
->
selectAll->
execute();
return
$this
->
selectAll->
fetchAll();
}
public
function
getById($id
)
{
$this
->
selectById->
execute(array
($id
));
$row
=
$this
->
selectById->
fetchAll();
if
(empty($row
[
0
]
))
{
return
array
();
}
else
{
return
$row
[
0
];
}
}
}
<?
xml version=
"1.0"
encoding=
"utf-8"
?>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
xml:lang
=
"
fr-FR
"
lang
=
"
fr-FR
"
>
<
head>
<
title>
<?php
echo html($title
);
?>
<
/title
>
<
meta http-equiv
=
"
content-type
"
content
=
"
text/html; charset=utf-8
"
/
>
<style type
=
"
text/css
"
>
h1 {
text-align:
center
;
}
</
style>
<
/head
>
<
body>
<
ul style
=
"
text-align: center
"
>
<
li style
=
"
display: inline
"
><a
href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
"
title
=
"
G. Rossolini - Tutoriels PHP
"
>
Accueil<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
articles/
"
title
=
"
G. Rossolini - Liste des publications
"
>
Publications<
/a
>
-<
/li
>
<
li style
=
"
display: inline
"
><a
href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteurs/
"
title
=
"
G. Rossolini - Liste des auteurs
"
>
Auteurs<
/a
></li
>
<
/ul
>
<
h1>
<?php
echo html($title
);
?>
<
/h1
>
<
p style
=
"
text-align: center; font-size: 0.7em;
"
>
Copyright
Guillaume Rossolini 2007-<?php
echo date('Y'
);
?>
<
br /
>
Contact : <
a
href
=
"
mailto:g-rossolini@developpez.com
"
title
=
"
E-mail
"
>
g-rossolini@developpez.com<
/a
></p
>
<
/body
>
<
/html
>
<
div style
=
"
text-align: center; font-style: italic
"
>
Par <
a
href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteur/
<?php
echo url_format($article
[
'author_id'
].
'-'
.
$article
[
'author_name'
]
);
?>
"
title
=
"
Publications de
<?php
echo html($article
[
'author_name'
]
);
?>
"
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></div
>
<?php
echo nl2br(html($article
[
'text'
]
));
<?php
foreach
($categories
as
$category
) :
?>
<
h2>
<?php
echo html($category
[
'name'
]
);
?>
<
/h2
>
<
ul>
<?php
foreach
($category
[
'articles'
]
as
$article
) :
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
article/
<?php
echo url_format($article
[
'id'
].
'-'
.
$article
[
'title'
]
)
?>
"
title
=
"
<?php
echo html($article
[
'title'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
>
, par <
a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteur/
<?php
echo url_format($article
[
'author_id'
].
'-'
.
$article
[
'author_name'
]
);
?>
"
title
=
""
>
<?php
echo html($article
[
'author_name'
]
);
?>
<
/a
></li
>
<?php
endforeach
;
?>
<
/ul
>
<?php
endforeach
;
?>
<
ul>
<?php
foreach
($articles
as
$article
) :
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
article/
<?php
echo url_format($article
[
'id'
].
'-'
.
$article
[
'title'
]
);
?>
"
title
=
"
Article par
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($article
[
'title'
]
);
?>
<
/a
></li
>
<?php
endforeach
;
?>
<
/ul
>
<
ul style
=
"
list-style-type: none
"
>
<?php
foreach
($authors
as
$author
) :
?>
<
li><a href
=
"
<?php
echo html($_SERVER
[
'HTTP_ROOT'
]
);
?>
auteur/
<?php
echo url_format($author
[
'id'
].
'-'
.
$author
[
'name'
]
);
?>
"
title
=
"
<?php
echo html($author
[
'name'
]
);
?>
"
>
<?php
echo html($author
[
'name'
]
);
?>
<
/a
>
(<?php
echo (int)
$author
[
'nb_articles'
];
?>
articles)<
/li
>
<?php
endforeach
;
?>
<
/ul
>
Le document <?php
echo html($_SERVER
[
'REQUEST_URI'
]
);
?>
n’existe pas.
<
h2 style
=
"
text-decoration: underline
"
>
Présentation<
/h2
>
<
p>
J’ai suivi une formation dans plusieurs domaines, notamment la communication,
le développement de logiciels, la gestion de projet,
<
span style
=
"
font-style: italic
"
>
,etc<
/span
>
.<
/p
>
<
p>
Aujoud'hui, je suis un développeur PHP muni de la certification Zend :<
br /
>
<
a href
=
"
http://www.zend.com/store/education/certification/authenticate.php?ClientCandidateID=ZEND005053&RegistrationID=221302133
"
title
=
"
Zend Certified Engineer Details
"
><img
src
=
"
http://www.zend.com/images/training/php5_zce_logo.gif
"
alt
=
"
Logo ZCE
"
style
=
"
border: none
"
/
></a
></p
>
VIII-G-3. Conclusion▲
Cette architecture est largement plus robuste et flexible que celle du premier exemple, mais elle n’est pas encore tout à fait parfaite. MVC a tendance à pousser à l’utilisation de nombreux Design patterns, notamment Singleton et Registry. Je n’ai pas mis en place un système trop complexe, car notre petit site d’exemple ne le justifie pas, mais par exemple la connexion à la BDD mériterait un pattern Registry plutôt que d’être mise dans un contrôleur ou dans un modèle, alors que ni l’un ni l’autre ne sont prévus pour cela.
Par ailleurs, la simplicité fonctionnelle du site actuel ne rend pas justice au front controller. Je vous recommande d’étudier le fonctionnement de frameworks MVC et de juger par vous-mêmes de l’approche la plus adaptée à vos projets.
Ne vous fiez pas à la taille des scripts présentés ici pour juger de l’utilité de ce que je viens de vous montrer. Ce site d’exemple est bien trop petit pour que MVC soit utilisé correctement. Pour mieux comprendre l’intérêt du pattern MVC, ajoutez des fonctionnalités à ce site dans chacune des approches vues ici. C’est un très bon exercice de style…