Tutoriel (première partie)

Préliminaires

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.

Hello World !

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 :

  • controllers : c'est dans ce dossier que Stato ira chercher vos classes controller. Un controller répond à une requête faite par l'utilisateur.
  • views : ce dossier contiendra vos templates, qui génèreront le HTML retourné au navigateur.
  • models : ce dossier contiendra les classes qui modélisent et manipulent les données issues de votre base.
  • helpers : vous placerez ici les classes et fonctions assistant les autres composants (majoritairement les vues). On évite ainsi d'avoir trop de code procédural dans les templates ou les controllers.

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

Création de notre première classe métier

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
    (
        'adapter' => 'MySql',
        'host'    => 'localhost',
        'user'    => 'root',
        'pass'    => '',
        'dbname'  => 'blog'
    ),
    'prod' => array
    (
        'adapter' => 'MySql',
        'host'    => 'localhost',
        'user'    => 'root',
        'pass'    => '',
        'dbname'  => 'blog'
    ),
    'test' => array
    (
        'adapter' => '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/do.php generate 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/do.php migrate

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;
}

Let's CRUD !

Supprimez votre fichier app/controllers/posts_controller.php et exécutez la commande suivante :

php.exe scripts/do.php scaffold 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.

Un peu de mise en page

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 $layout = 'basic';
    .......
}

Rafraichissez votre navigateur, et voici ce que vous obtiendrez :

Création d'un module d'administration

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/do.php generate module admin
    create controllers/admin
    create models/admin
    create views/admin
    create helpers/admin
    create apis/admin
    create i18n/admin
    create controllers/admin/base_controller.php

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 :

php.exe scripts/do.php scaffold admin/post
    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/_form.php
    identical views/layouts/scaffold.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).

Vous pouvez maintenant accéder à votre interface d'admin à l'url http://localhost/blog/admin/posts.

Ajout des commentaires

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/do.php generate 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/do.php migrate

Migrating to CreateComments (2)
=> CreateComments: migrating
=> CreateComments: migrated (0.0672s)

Et enfin, générons une interface d'admin :

php.exe scripts/do.php scaffold admin/comment

Tout est prêt maintenant pour entrer dans le vif du sujet.

Commençons à coder

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 $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->format('%d %B %Y - %H:%M:%S'); ?></strong>
        &nbsp;|&nbsp;
        <?= 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 format('%d %B %Y - %H:%M:%S') 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 format(), nous obtenons une forme plus lisible : 07 february 2006 - 17:43:17. Il nous suffit juste d'ajouter un appel à setlocale() dans le fichier conf/environment.php pour avoir la date en français :

setlocale(LC_TIME, 'fr_FR.utf8', 'fr_FR', 'fr');

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.

 
0_9_1/fr/tutoriel.txt · Last modified: 2009/04/03 02:27 (external edit)
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki