Une application MVC minimale

A présent, nous allons débuter avec l'application la plus simple possible. Commencez par créer la structure de répertoire :

c:\wamp\www\
 +-votreapp
   +-application
     +-controllers
     +-views
       +-scripts
   +-public

Le répertoire public

Le répertoire public correspondra à la racine de votre serveur Web sur votre serveur de production. Les feuilles de styles, les images et les scripts Javascript qui sont accessibles directement sans passer par PHP seront placés dans ce dossier. Il comprendra également le fichier index.php qui contiendra le code suivant :

/public/index.php


<?php
require '../application/bootstrap.php'
?>

C'est en fait dans le fichier bootstrap.php que l'on placera le code d'initialisation. Comme ce script est situé à l'extérieur de la racine du serveur Web de production, il est à l'abri d'un problème de configuration qui pourrait accidentellement en révéler le contenu. Nous reviendrons à ce fichier plus tard.

Toutes les requêtes vont passer par ce fichier. Pour cela, il faut configurer le serveur Web pour qu'il réoriente toutes les requêtes ne concernant pas un fichier ou un répertoire statique vers index.php. On utilise pour cela un fichier .htaccess pour Apache qu'on place dans le répertoire public.

/public/.htaccess

# Active la réécriture des adresses
RewriteEngine On
# Si la requête correspond à un fichier standard dont la taille est supérieure à 0 ...
RewriteCond %{REQUEST_FILENAME} -s [OR]
# ... ou à un lien symbolique
RewriteCond %{REQUEST_FILENAME} -l [OR]
# ... ou à un répertoire
RewriteCond %{REQUEST_FILENAME} -d
# ... on laisse la requête tel quelle (le tiret veut dire pas de substitution)
# Drapeau NC (No Case) : comparaison insensible à la casse
# Drapeau L (Last) : ne pas interpréter les règles suivantes
RewriteRule ^.*$ - [NC,L]
# Sinon, on redirige vers index.php 
RewriteRule ^.*$ index.php [NC,L]
Une requête vers une page qui n'existe pas sera également redirigée vers index.php. Vous devez gérer les erreur 404 avec votre contrôleur errorController.
Vous devez activer le module mod_rewrite d'Apache pour que cette règle fonctionne. Les fichiers .htaccess doivent être également pouvoir être analysés par Apache (directive AllowOverride all). Pour une performance maximale, il est préférable de placer les directives dans le fichier de configuration d'Apache.

Si vous utilisez Nginx à la place d'Apache, vous devrez utiliser la règle de réécriture suivante :

/etc/nginx/nginx.conf

server {
  [..]
  if (!-e $request_filename){
    rewrite ^(.*)$ /index.php break;
  }
}

Hôtes virtuels Apache

Pour faciliter le déploiement ultérieur sur votre serveur de production, il vaut mieux utiliser des urls similaires sur les deux environnements. Au lieu d'accéder à vos pages avec des urls commençant par http://localhost/, on peut utiliser les hôtes virtuels Apache (virtuals hosts) pour obtenir des urls du type : http://votreapp.tld.

Pour ce faire, ouvrez le fichier C:\wamp\bin\apache\ApacheX.X.XX\conf\extra\httpd-vhosts.conf et ajoutez lui les lignes suivantes :

<VirtualHost *:80>
    DocumentRoot "C:/wamp/www/votreapp/public"
    ServerName votreapp.tld
    ServerAlias votreapp.tld *.votreapp.tld
</VirtualHost>

Ce code crée un hôte virtuel dans Apache. Lorsque vous appelerez une URL du domaine votreapp.tld, Apache utilisera le chemin C:/wamp/www/votreapp/public comme racine.

Redémarrez Apache, puis éditez le fichier C:\WINDOWS\system32\drivers\etc\hosts. Ajoutez la ligne suivante :

127.0.0.1       votreapp.tld

Le fichier hosts est utilisé par Windows pour convertir les noms d'hôtes en adresses IP. Comme votre domaine votreapp.tld est factice, vous devez le spécifier dans ce fichier pour que Windows puisse associer le domaine à l'adresse IP locale (127.0.0.1). Si vous ne faites pas cette modification, votre navigateur vous indiquera que le site demandé est introuvable.

A quoi corresponds l'extension .tld ?

tld est l'abréviation de Top Level Domain. Le domaine de premier niveau .tld n'est pas utilisé sur Internet, vous pouvez donc l'utiliser en local sans qu'il y ait de confusion avec un domaine existant réellement sur Internet. Consultez Wikipédia pour en savoir plus sur les noms de domaines.

Le répertoire application

C'est dans ce répertoire que vous allez placer l'essentiel de votre code. Il y a d'abord le fameux fichier bootstrap.php dont nous avons déjà parlé ci-dessus.

/application/bootstrap.php


<?php
require "Zend/Loader.php"

// Installe le chargeur automatique de classes
Zend_Loader::registerAutoload(); 
 
// Récupère une instance du contrôleur frontal
$frontController Zend_Controller_Front::getInstance();

// On n'utilise pas le plugin ErrorController pour l'instant
$frontController->throwException(true);

// On indique l'endroit où sont placés les contrôleurs
$frontController->addControllerDirectory('../application/controllers');

// Demande au contrôleur de traiter la requête
$frontController->dispatch(); 
?>

On commence par mettre en place le Zend_Loader qui permet de charger automatiquement les classes, à la manière de la fonction __autoload de PHP. Une fois que cet objet est initialisé, vous n'aurez plus besoin d'utiliser des require ou des include pour charger vos classes. Zend_Loader va analyser le nom de la classe demandée et déterminer automatiquement le chemin menant au fichier. Pour la classe Zend_Db_Table par exemple, Zend_Loader va inclure le fichier Zend/Db/Table.php. Vous pouvez même utiliser cette fonctionnalité pour vos propres classes en respectant la convention de nommage et la structure des fichiers.

On récupère ensuite une instance du contrôleur frontal. Il n'existe qu'un seul contrôleur frontal pour l'application, ce qui explique qu'on crée l'objet par l'intermédiaire d'un patron de conception de type Singleton. Le contrôleur frontal est chargé de transmettre la requête aux autres contrôleurs. Pour qu'il puisse fonctionner, on lui indique le dossier dans lequel il pourra trouver ces derniers.

Par défaut, si on ne spécifie aucun paramètre particulier dans l'URL, le contrôleur frontal transmet la requête au contrôleur nommé IndexController et appelle son action nommée indexAction.

/application/controllers/IndexController.php


<?php
class IndexController extends Zend_Controller_Action 
{
  
// L'action par défaut
  
public function indexAction() 
  {
  }
}
?>

Dans notre application, on ne va pour l'instant traiter aucune donnée. On laisse donc le corps de la méthode vide et on place notre message dans la vue correspondante :

/application/views/scripts/index/index.phtml

<?php
Salut tout le monde 
!
?>

Si vous ouvrez alors un navigateur avec l'adresse http://localhost/votreapp/public/, vous verrez alors apparaitre la page suivante :

Le modèle

Voilà, vous avez une première application fonctionnelle. Elle ne vas pas changer le monde, mais c'est un début. Il nous reste un peu de travail, car pour l'instant, on a oublié le M du concept MVC : le modèle. Lancez donc PHPMyadmin, créez une base nommée votreapp et exécutez le code SQL suivant :

CREATE TABLE `contacts` (
  `id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  `prenom` CHAR( 24 ) NOT NULL ,
  `nom` CHAR( 24 ) NOT NULL ,
  `telephone` CHAR( 14 ) NOT NULL
) ENGINE = MYISAM ;
INSERT INTO `contacts` ( `id` , `prenom` , `nom` , `téléphone` )
  VALUES (
    NULL , 'Pierre', 'QUIMOUSSE', '01.02.03.04.05'
  ), (
    NULL , 'Eva', 'NAISSANCE', '01.23.45.67.89'
  );

Pour notre exemple, on va créer un petit carnet d'adresse. On va donc travailler sur une table nommée contacts avec 4 colonnes : une clé primaire, le prénom, le nom et le numéro de téléphone.

On insère également quelques données.

A présent, on va préparer une connexion à la base de données. J'utilise le terme préparer car la connexion réelle ne se fera que lorsqu'on interrogera réellement la base.

On ajoute donc le code suivant à bootstrap.php, juste derrière la mise en place du Zend_Loader :

/application/bootstrap.php

<?php
[..]

// On prépare une connexion avec le driver PDO MySQL
$db Zend_Db::factory('pdo_mysql', array(
    
'host' => 'localhost',
    
'username' => 'root',
    
'password' => '',
    
'dbname' => 'votreapp'
));

// On place l'instance de la connexion dans le registre
Zend_Registry::set('db'$db);

[..]
?>
On utilise ici le driver PDO MySQL. Son support doit être activé dans PHP.

Ensuite on ajoute le code suivant à la méthode indexAction du contrôleur :

/application/controllers/IndexController.php

<?php
[..]

// Récupération de l'instance de la connexion depuis le registre
$db Zend_Registry::get('db');
// Création d'un objet de classe Zend_Db_Select
$select $db->select()->from('contacts');
// Exécution de la requête et récupération des résultats dans un tableau associatif
$this->view->contacts $select->query()->fetchAll();

[..]
?>

Puis finalement dans la vue :

/application/views/scripts/index/index.phtml

<?php
<html>
<
head>
<
title>Contacts</title>
<
meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</
head>
<
body>
<?
php     
foreach ($this->contacts as $contact) {
    echo 
"<div><h4>{$contact['prenom']} {$contact['nom']}</h4>\n<p>Tél. : {$contact['telephone']}</p></div>";
}
?>
</body>
</html>
?>

Le résultat final :

Commentaires

petit bug je crois,
Fatal error: Uncaught exception 'Zend_Controller_Dispatcher_Exception' 
with message 'Invalid controller specified (error)' 
in D:\lamp\htdocs\lib\Zend\Controller\Dispatcher\Standard.php:241
Stack trace: 
#0 D:\lamp\htdocs\lib\Zend\Controller\Front.php(934): 
Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http),
Object(Zend_Controller_Response_Http))
#1 D:\lamp\htdocs\votreapp\application\bootstrap.php(10): Zend_Controller_Front->dispatch() 
#2 D:\lamp\htdocs\votreapp\public\index.php(2): require('D:\lamp\htdocs\...') 
#3 {main} thrown in D:\lamp\htdocs\lib\Zend\Controller\Dispatcher\Standard.php on line 241

J'ai pris mon temps pour répondre (8 mois quand même), mais mieux vaut tard que jamais. Tu as cette erreur car tu n'as pas de contrôleur de gestion d'erreur. Il faut ajouter $frontController->throwException(true); à ton bootstrap (j'ai mis à jour le tutoriel pour inclure cette information).

Merci pour ce tuto sympa pour les débutants.

Juste une précision concernant le Virtual Host.
Cela dépend peut-être des configs, mais pour ma part j'ai du faire un autre modif :
Décommenter la ligne suivante dans le fichier httpd.conf qui se trouve dans C:\wamp\bin\apache\Apache2.2.11\conf

-> #Include conf/extra/httpd-vhosts.conf
Enlever le # !

Autrement le domaine tld me renvoyait à la racine de Wamp (Wamp/www) et pas sur Wapmp/www/monapp/public comme souhaité.

Bon courage à tous

Oui, avec certaines versions de Wamp, la config des hôtes virtuels est déportée dans des fichiers séparés.

La page Index.phtml n'apparait pas.
J'ai remplacé "votreapp" par "monsite".
J'ai tracé les fichiers en mettant un print"Nom du fichier" dans tous les fichiers qui entrent en jeu. Donc quand j'ouvre la page "http://monsite.tld/public", je vois que les fichiers, index.php, bootstrap.php et loader.php sont bien lus. Apparement, le fichier "IndexController.php" n'est pas lu. Avez-vous une idée ?
Sinon merci pour ce tuto !

Normalement, tu devrais accéder à ton site par l'url http://monsite.tld. Le répertoire public doit être la racine de ton hôte virtuel Apache.

Oui, j'accède bien à mon site avec l'url http://monsite.tld, mais la page est vierge. Je ne vois pas apparaître "Salut tout le monde".
Aprés quelques recherches, j'ai modifié un paramètre de php.ini. J'ai mis short_open_tag à "on".
Dans mon fichier IndexController.php j'ai mis ceci :
<?php
print("c'est l index controller");
class IndexController extends Zend_Controller_Action
{
// L'action par défaut
public function indexAction()
{

}
}
Pourtant, j'ai toujours une page blanche !
Je ne sais plus dans quelle direction chercher !

Tu devrais utiliser un débogueur comme (NetBeans + XDebug) pour tracer l'exécution de ton script.

J'ai trouvé l'erreur : Quand j'ai installé wamp, j'avais déjà une version de php. Wamp pointait sur celui-là.
Ensuite, j'ai installé la version 1.7.8 de Zend. La version 1.8 ne marchait pas.

Par contre, il y a une petite erreur de frappe dans le tuto. Il faut rajouter un s à throwException comme ci-dessous:
$frontController->throwExceptions(true);

Je vais enfin pouvoir continuer le tuto !

Hello, Pour ceux qui comme moi ce retrouve avec un :

"Notice: Zend_Loader::Zend_Loader::registerAutoload is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead in C:\wamp\www\lib\library\Zend\Loader.php on line 258",

Il faut sur le Bootstrap.php, remplacer :

"require "Zend/Loader.php";
// Installe le chargeur automatique de classes
Zend_Loader::registerAutoload();
"

par :

"require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::autoload('Zend_Loader_Autoloader_Resource');
"

Thomas

Bonjour à tous !

Tout d'abord, merci pour ce tuto qui m'a fort aidé dans mes travaux. Cependant, j'ai une erreur récurrente qui ressemble à la 1ère qui vous a été soumise. J'ai bien suivi et re-suivi ce tuto, si quelqu'un a une idée sur l'origine de cette erreur, merci de m'en faire part =)

Fatal error: Uncaught exception 'Zend_Controller_Dispatcher_Exception' with message 'Invalid controller specified (index)' in C:\wamp\www\lib\Zend\Controller\Dispatcher\Standard.php:242 Stack trace: #0 C:\wamp\www\lib\Zend\Controller\Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http)) #1 C:\wamp\www\votreapp\application\bootstrap.php(27): Zend_Controller_Front->dispatch() #2 C:\wamp\www\votreapp\public\index.php(2): require('C:\wamp\www\vot...') #3 {main} thrown in C:\wamp\www\lib\Zend\Controller\Dispatcher\Standard.php on line 242

Bonjour,

mon application marche mais j'ai un petit problème d'affichage au niveau des caractères accentués. En effet, j'ai un caractère étrange ( ? ) à la place du "é" de "Tél"...

Savez-vous comment je peux arranger ça SVP ?

Merci d'avance à ceux qui répondront à mon appel !

Stéphane

Re bonjour, finalement après quelques recherches j'ai trouvé la solutions ! Il faut mettre la ligne suivant en haut de la vue :
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
à la place de :
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
J'espère que ça en aidera certains d'entre-vous ! Stéphane

Ajouter un commentaire