Afin de suivre ce tutoriel, vous devez avoir un environnement de développement adéquat : consultez la doc concernant l'installation et créez un nouveau projet nommé blog.
Le but de ce tutoriel est de créer un petit moteur de blog. Si vous avez correctement créé votre appli comme indiqué dans les préliminaires, vous devez avoir un dossier blog/ à la racine de votre serveur web. Nous supposerons que vous avez créé un alias blog plutôt qu'un virtualhost.
L'essentiel du code devra être placé dans le sous-dossier app. Comme vous pourrez le constater, il possède une arborescence bien précise et c'est ce qui permet à Stato de s'y retrouver (“convention, not configuration”).
Détaillons un peu le contenu de ce dossier app :
En premier lieu, il est important de comprendre comment fonctionnent les Controllers et quelles relations ils entretiennent avec les URLs. L'URL d'une requête correspond à une classe controller et à une méthode de cette classe. Etant donné que la finalité d'un blog est de poster des billets (en anglais posts), nous allons donc créer un nouveau fichier nommé posts_controller.php dans le dossier app/controllers avec le code suivant :
class PostsController extends ApplicationController { }
Si vous essayez dans votre navigateur l'url http://localhost/blog/ , vous obtiendrez l'erreur suivante :
No controller specified in this request
Effectivement, vous n'avez pas précisé de controller dans l'URL, essayez donc avec http://localhost/blog/posts :
Action index not found in PostsController
Ahah ! La partie posts de l'url a bien été mappée vers notre nouveau controller, mais apparemment le framework cherche une action index dans ce controller. Nous allons donc l'implémenter :
class PostsController extends ApplicationController { public function index() { $this->render_text('Hello World !'); } }
En rafraichissant la page, nous obtenons ceci :
Hello World !
Victoire ! Nous aurions bien sûr eu le même résultat avec l'url suivante : http://localhost/blog/posts/index. En effet, si aucune action n'est précisée, le dispatcher recherche une action index. Ajoutons maintenant une nouvelle action :
class PostsController extends ApplicationController { public function index() { $this->render_text('Hello World !'); } public function test() { $this->render_text('Another action !'); } }
En utilisant l'url http://localhost/blog/posts/test nous avons ceci :
Another action !
Vous devez maintenant avoir compris la relation entre l'URL et les controllers.
Par défaut, les URLs sont de la forme :
http://localhost/app_dir/:controller/:action/:id
Vous devez tout d'abord configurer Stato pour pouvoir accéder à votre base : ouvrez le fichier conf/database.php et modifiez les paramètres de façon à obtenir ceci : (bien sûr, si vous avez attribué un mot de passe au compte root de MySQL, n'oubliez pas de l'indiquer !)
$config = array ( 'dev' => array ( 'driver' => 'MySql', 'host' => 'localhost', 'user' => 'root', 'pass' => '', 'dbname' => 'blog' ), 'prod' => array ( 'driver' => 'MySql', 'host' => 'localhost', 'user' => 'root', 'pass' => '', 'dbname' => 'blog' ), 'test' => array ( 'driver' => 'MySql', 'host' => 'localhost', 'user' => 'root', 'pass' => '', 'dbname' => 'blog' ) ); return $config;
Stato vous permet d'utiliser différentes bases en fonction du mode de fonctionnement choisi : développement, test, production. Nous nous contenterons d'utiliser la même base pour les 3.
Utilisez maintenant phpMyAdmin (ou tout autre outil) pour créer une base blog.
Ouvrez une console, et placez vous dans votre répertoire blog/. Exécutez la commande suivante :
php.exe scripts/generate.php model post
Stato va allors créer 2 fichiers : app/models/post.php et db/migrate/1_create_posts.php. Ouvrez ce dernier et éditez le comme ceci :
class CreatePosts extends SMigration { public function up() { $t = new STable(); $t->add_primary_key('id'); $t->add_column('title', 'string'); $t->add_column('content', 'text'); $t->add_column('created_on', 'datetime'); $t->add_column('updated_on', 'datetime'); $this->create_table('posts', $t); } public function down() { $this->drop_table('posts'); } }
Enfin, exécutez la migration à l'aide de la commande suivante :
php.exe scripts/migrate.php Migrating to CreatePosts (1) => CreatePosts: migrating => CreatePosts: migrated (0.0521s)
Vous venez de créer votre table posts dans la base !
Quant à notre fichier app/models/post.php, vous remarquerez qu'il contient ceci :
class Post extends SActiveRecord { public static $objects; }
C'est tout ce qu'il faut pour commencer à créer de nouveaux enregistrements dans la base. Nous n'avons même pas à préciser le nom de la table à utiliser : le framework va le “deviner”, et utiliser le pluriel du nom de la classe (classe Post ⇒ table posts). Il va également créer automatiquement des propriétés dans cette classe permettant d'accéder aux champs de chaque enregistrement dans la table.
Si toutefois nous n'avions pas appellé notre table posts, mais monprojet_posts, nous aurions pu le préciser ainsi dans la définition de notre classe Post :
class Post extends SActiveRecord { public static $table_name = 'monprojet_posts'; public static $objects; }
Supprimez votre fichier app/controllers/posts_controller.php et exécutez la commande suivante :
php.exe scripts/scaffold.php post
Stato va créer automatiquement un contrôleur et des vues de base autour de notre classe métier Post. Le contrôleur contient des actions correspondant à toutes les opérations CRUD sur notre modèle, ce qui nous permet immédiatement de créer, éditer, lister et supprimer des posts dans la base !
Rendez-vous à l'url http://localhost/blog/posts/create :
Nous n'avons pas fait grand chose, et pourtant nous pouvons déjà intégrer de nouveaux posts dans la base ! Saisissez un premier post, cliquez sur Create et vous obtiendrez ceci :
Il y a toutefois un meilleur moyen de gérer les dates de création : modifiez votre classe Post de manière à avoir ceci :
class Post extends SActiveRecord { public static $objects; public $record_timestamps = true; }
Maintenant, créez un nouveau post par l'intermédiaire du formulaire de création (vous remarquerez que les champs de sélection de dates en sont absents) et allez voir le contenu de du nouvel enregistrement dans phpMyAdmin : vous constaterez que Stato a enregistré la date de création. En fait, par convention, si la propriété $record_timestamps est égale à True, et que les champs created_on et updated_on sont présents dans la table, Stato les enregistre automatiquement. Nous verrons par la suite comment les afficher.
Visuellement, notre embryon de blog reste assez spartiate, mais nous pouvons y remédier. En effet, les templates associés aux différentes actions doivent être rendus au sein d'un layout général à tout le site. Vous comprenez maintenant à quoi sert le sous-dossier layouts dans le dossier app/views. Créez donc un nouveau fichier basic.php dans app/views/layouts contenant le code suivant :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>Mon blog</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <?= stylesheet_link_tag('basic.css'); ?> </head> <body> <div id="conteneur"> <div id="header"> <h1>Mon blog</h1> </div> <div id="centre"> <?= $this->layout_content; ?> </div> </div> </body> </html>
Même s'il s'agit d'un layout, la syntaxe utilisée est la même que pour les templates. Nous pouvons insérer du code PHP dans le HTML en utilisant les tags <? ?> et <?= ?> si nous voulons générer du texte en sortie (l'équivalent d'un echo).
Vous pouvez constater que nous avons inséré le code :
<?= stylesheet_link_tag('blog.css'); ?>
Il s'agit de notre première utilisation d'un helper de Stato : ce helper est une simple fonction qui générera le tag <link> nécessaire à l'inclusion de notre feuille de style.
Nous avons également écrit ceci :
<?= $this->layout_content; ?>
En effet, lors du rendu du layout, Stato va fournir le contenu du template dans cette variable $this→layout_content. Cela permet donc d'intégrer le rendu du template dans le layout.
Créons maintenant une feuille de style basique. Dans le dossier public/styles, créez un nouveau fichier basic.css contenant les styles suivants :
body { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 0.8em; margin: 0; padding: 0; } h1 { margin: 0; font-size: 18px; } label { display:block; clear:right; } table { border: 1px solid #000; border-spacing: 0; margin: 0; background-color: #6C6C6C; text-align:center; } th { color:#fff; padding: 2px 10px; } td { border-top: 1px solid #ccc; padding: 10px; background-color: #fff; border-collapse: collapse; } #header { height: 60px; color: #fff; background-color: #9C1C20; } #header h1 { font-size: 20px; font-family: Georgia, Times New Roman, Times, serif; padding: 15px; } #conteneur { position: absolute; width: 100%; background-color:#fff; } #centre { background-color:#fff; margin:20px 50px; min-height:500px; }
Il ne vous reste plus qu'à modifier la propriété $layout dans votre PostsController :
class PostsController extends ApplicationController { public $models = array('post'); public $layout = 'basic'; ....... }
Rafraichissez votre navigateur, et voici ce que vous obtiendrez :
En utilisant le scaffolding, nous avons pu très rapidement mettre en place une interface de gestion complète de nos billets. Les actions index, create, update et delete sont prêtes à l'emploi. Mais bien sûr, ce n'est pas ce que nous souhaitons présenter comme interface à nos visiteurs. L'intérêt du scaffolding est de permettre la réalisation rapide de prototypes et de développer progressivement nos propres actions et views par dessus. Toutefois, il est également très utile pour créer sans effort une interface d'administration, et c'est à cela que nous allons nous employer.
Pour créer notre module admin, utilisez la commande suivante :
php.exe scripts/generate.php module admin
create controllers/admin
create models/admin
create views/admin
create helpers/admin
Cette commande crée des sous-dossiers admin dans chacun des dossiers de notre appli. Nous allons maintenant générer un scaffolding pour notre classe Post à l'intérieur de ce module admin, en ajoutant un peu d'AJAX :
php.exe scripts/scaffold.php admin/post --ajax
create controllers/admin/posts_controller.php
create views/admin/posts
create views/admin/posts/index.php
create views/admin/posts/view.php
create views/admin/posts/create.php
create views/admin/posts/update.php
create views/admin/posts/_objects_list.php
create views/layouts/scaffold_ajax.php
create helpers/admin/scaffold_ajax_helper.php
Il nous faut maintenant configurer le routing pour le module admin. Editez le fichier conf/routes.php de la façon suivante :
$map = new SRouteSet(); $map->connect('admin/:controller/:action/:id', array('subdirectory' => 'admin', 'controller' => 'posts')); $map->connect(':controller/:action/:id'); return $map;
En faisant cela, nous créons une nouvelle route pour pouvoir accéder à notre module, en définissant un contrôleur par défaut (posts).
Si vous essayez d'accéder à l'url http://localhost/blog/admin/posts, vous obtiendrez l'erreur suivante :
Missing model post
C'est normal ! Notre classe métier Post est en effet restée à la racine de models/, or le contrôleur cherche à la charger depuis le module admin. Modifiez donc le code de app/controllers/admin/posts_controller.php de cette façon :
class PostsController extends ApplicationController { public $models = array('/post'); ....
Rafraîchissez, et vous obtenez votre interface d'admin :
Dans cette interface, les entêtes du tableau ainsi que les liens de pagination utilisent AJAX pour rafrîchir le tableau de données sans recharcher la page.
Les commentaires sont bien entendus incontournables dans un blog. Avant de nous attaquer à l'interface publique, nous allons donc créer notre classe métier Comment :
php.exe scripts/generate.php model comment
Editons le fichier db/migrate/2_create_comments.php :
class CreateComments extends SMigration { public function up() { $t = new STable(); $t->add_primary_key('id'); $t->add_column('post_id', 'integer'); $t->add_column('author', 'string'); $t->add_column('content', 'text'); $t->add_column('created_on', 'datetime'); $t->add_column('updated_on', 'datetime'); $this->create_table('comments', $t); } public function down() { $this->drop_table('comments'); } }
Lançons la migration :
php.exe scripts/migrate.php Migrating to CreateComments (2) => CreateComments: migrating => CreateComments: migrated (0.0672s)
Et enfin, générons une interface d'admin :
php.exe scripts/scaffold.php admin/comment --ajax
N'oublions pas, là encore, de pointer notre contrôleur vers la classe métier à la racine :
class CommentsController extends ApplicationController { public $models = array('/comment'); ...
Tout est prêt maintenant pour entrer dans le vif du sujet.
Vous aurez sans doute remarqué que nous avons inclus une Foreign Key post_id dans notre table comments. Elle va nous servir à associer les commentaires à leurs billets respectifs, mais il nous faut le préciser dans nos classes métier :
class Post extends SActiveRecord { public static $objects; public static $relationships = array('comments' => 'has_many'); public $record_timestamps = true; } class Comment extends SActiveRecord { public static $objects; public static $relationships = array('post' => 'belongs_to'); public $record_timestamps = true; }
Chaque commentaire appartient à un billet (belongs_to) et chaque billet peut avoir plusieurs commentaires (has_many).
Nous allons maintenant nous occuper de la page d'accueil de notre blog. Supprimer les actions create et delete du PostsController à la racine du dossier app/controllers et modifiez la méthode index comme ceci :
class PostsController extends ApplicationController { public $models = array('post'); public $layout = 'basic'; public function index() { $this->posts = Post::$objects->limit(10)->order_by('-created_on'); } public function view() { $this->post = Post::$objects->get($this->params['id']); } }
Quelques détails techniques :
Quand un utilisateur navigue vers http://localhost/blog/posts/index, Stato va appeller la méthode index() que nous avons créée. Cette action a pour but de recueillir des données pour les passer au template. La propriété statique $objects de la classe Post contient une instance de la classe SQuerySet qui va nous permettre de faire des requêtes dans la base. Elle possède différentes méthodes qui permettent de générer les différentes parties d'une requête SQL. Afin de rendre accessible le résultat de la requête dans le template, nous devons l'assigner à une variable d'instance du controller (ici $this→posts). Précisons toutefois que cette requête sera exécutée uniquement lorsque nous itèrerons la variable $this→posts à l'aide d'une boucle foreach, ce qui nous renverra des instances de la classe Post. La méthode limit nous permet de ne renvoyer que les 10 derniers billets, et la méthode order_by de les ordonner par date de création décroissante (d'où le - devant created_on).
Il nous faut maintenant personnaliser le template. Profitez-en pour supprimer les fichiers create.php et update.php du dossier app/views/posts qui sont désormais inutiles, et remplacez le contenu de index.php par le code suivant :
<div id="content">
<h2>Mes récents billets</h2>
<? foreach($this->posts as $post) : ?>
<h3><?= $post->title; ?></h3>
<p><?= $post->content; ?></p>
<strong><?= $post->created_on->locale(); ?></strong>
|
<?= link_to($post->comments->count().' commentaire(s)',
array('action' => 'view', 'id' => $post->id, 'anchor' => 'comments')); ?>
<? endforeach; ?>
</div>
En effet, après avoir exécuté l'action demandé par l'utilisateur, Stato va aller chercher le template dans le sous-dossier de app/views correspondant au controller (en l'occurence app/views/posts) et utiliser le fichier portant le même nom que l'action (donc index.php).
Dans notre template, nous itérons $this→posts (fourni par le controller) à travers une boucle foreach et nous affichons pour chaque objet certains de ses attributs.
Vous remarquerez l'utilisation de la méthode locale() sur l'attribut created_on. En effet, ce champ est de type DATETIME dans notre base, et ses valeurs sont donc automatiquement typées en tant qu'objet SDateTime par le framework. Si l'on avait utilisé simplement $post→created_on, nous aurions obtenu la date sous la forme 2006-02-07 17:43:17. Avec la méthode locale(), nous obtenons une forme localisée : 07 février 2006 - 17:36:24 (si votre locale est fr_FR bien sûr).
Enfin, vous constaterez que nous utilisons le helper link_to qui permet comme son nom l'indique de générer un lien. Vu que nous avons déclaré une relation entre billets et commentaires, une propriété comments est accessible dans chaque instance de Post, et elle dispose de différentes méthodes, dont count qui va nous permettre d'afficher le nombre de commentaires. Vous remarquerez également que dans le tableau de paramètres que nous avons fourni au helper pour déterminer l'URL, nous n'avons pas précisé de contrôleur. Stato va donc utiliser le contrôleur courant.
Vous pouvez maintenant rafraîchir la page et contempler votre oeuvre :
Pour continuer votre incursion dans Stato, je vous invite à suivre la partie 2 du tutoriel.