Catalyst

user_icon admin | icon2 Catalyst | icon4 26/11/2006 18h2| Type doc: article| Type File: txt| icon3 No Comment

1. Introduction

Perl est un language de programmation avec lequel je me débrouille tout juste alors pardonnez les erreurs qui apparaitront certainement au cours de l'écriture de ce document. N'hésitez pas à m'informer de toute remarque qui vous semblerai judicieuse.

Catalyst est un nouveau framework MVC en Perl destiné à faciliter considérablement la construction d'application Web. Il est aujourdhui en constante évolution.

Il utilise de nombreux modules Perl issu du CPAN ( search.cpan.org) et est au fait des dernières technos.

1.1. Modèle MVC

MVC signifie Model View Controler.

  • Les Models sont les définitions des espaces de stockages et elurs méthodes d'accès.

  • Les Views serviront dans la représentation des données.

  • Le rôle des controleurs est de gérer le flux de l'application.

Controller -> Prend les données dans Model

Controller -> Fourni les données à la View ou passe a un autre controleur, ...

Par exemple http://localhost:3000/Users/list est une action permettant de lister les utilisateurs de l'application. Dans le Controler 'Users' serai défini une méthode 'list' qui pourrai accéder aux données du Model 'Utilisateurs'. Les données retournées par les methodes du Model seraient stockées par le Controller qui les fournirai ensuite à la View.

Nous débuterons notre exploration à partir d'une installation vierge indépendante du système installé.

2. Installation de Catalyst

Prérequis:

  • Une Debian installée.

  • Accès au Net

Pour permettre l'utilisation de la dernière version de Catalyst nous allons créer une mini Debian qui contiendra tous les packages necessaires. Ainsi le système de base se sera pas affecté.

# 1 - Création du système Debian minimum
# Retrieve minimum packages for Debian Sid
debootstrap sid chroot_catalyst

# 2 - On prend pour racine ce nouveau système
chroot chroot_catalyst

# 3 - Montage du FS /proc
mount -t proc proc /proc


# 4 - Lecture des derniers packages
apt-get update

# 5 - Install minimum packages for catalyst
apt-get install locales gcc libc6-dev libperl-dev perl-modules dh-make-perl libdbi-perl libclass-dbi-perl libdbd-mysql-perl libclass-dbi-sqlite-perl libsql-abstract-limit-perl libclass-trigger-perl libdbix-contextualfetch-perl libhttp-server-simple-perl libwww-mechanize-perl libhttp-server-simple-perl libsqlite3-dev sqlite3 libtest-pod-perl libtest-pod-coverage-perl libclass-inspector-perl libperl6-slurp-perl subversion-tools libnet-ldap-perl shared-mime-info dnsutils libimage-imlib2-perl libgraphviz-perl librpc-xml-perl libtemplate-perl libgd-perl libclass-dbi-fromform-perl ftp mysql-client-4.1
dpkg-reconfigure locales
apt-get clean (=~ 300 Mo)

# 6 - Init .cpan
perl -MCPAN -e shell

# 7 - Install lasted packages for Catalyst
perl -MCPAN -e shell << __EOF__
force install Test::WWW::Mechanize::Catalyst
install Task::Catalyst
install Catalyst::Enzyme
install Catalyst::View::GraphViz
install Catalyst::View::TT::ControllerLocal
install Catalyst::Plugin::Authorization::Roles
install Catalyst::Plugin::Authentication::Store::DBIC
install Catalyst::Helper::Controller::Scaffold
install Catalyst::Plugin::Authentication::CDBI
install DBI::Shell
__EOF__       

Vous avez pu constater que Catalyst est dépendant d'un très grand nombre de modules perl.

Notre espace de travail étant à jour (=~ 330Mo), nous y resterons le reste du document.

3. Création d'une application

Nous crééons tout dabord le répertoire d'acceuil de notre application et nous nous y rendons.

                
mkdir -p /var/www/catalyst &amp;&amp; cd /var/www/catalyst

Ensuite création de l'application

                                
catalyst.pl MonAppli
created "MonAppli"

created "MonAppli/script"
created "MonAppli/lib"
...

Et enfin exécution de l'application.

localhost:/var/www/catalyst/MonAppli/# cd MonAppli
localhost:/var/www/catalyst/MonAppli/# perl script/monappli_server.pl
[] [catalyst] [debug] Debug messages enabled
[] [catalyst] [debug] Loaded plugins:
.------------------------------------------------------------------------------.
| Catalyst::Plugin::Static::Simple                                             |
'------------------------------------------------------------------------------'

[] [catalyst] [debug] Loaded dispatcher "Catalyst::Dispatcher"
[] [catalyst] [debug] Loaded engine "Catalyst::Engine::HTTP"

[] [catalyst] [debug] Found home "/var/www/catalyst/MonAppli"
[] [catalyst] [debug] Loaded Private actions:
.----------------------+----------------------------------------+--------------.
| Private              | Class                                  | Method       |
+----------------------+----------------------------------------+--------------+
| /default             | MonAppli                               | default      |
'----------------------+----------------------------------------+--------------'

[] [catalyst] [info] MonAppli powered by Catalyst 5.61
You can connect to your server at http://localhost:3000

Et voilà notre première application est maintenant créée, elle est en écoute du port 3000. Pour le vérifier il suffit de se connecter à http://localhost:3000/

Le fonctionnement de l'application MonAppli est définie dans ./lib/MonAppli.pm. On constate que celle-ci hérite de '-Debug' 'Static::Simple'

use Catalyst qw/-Debug Static::Simple/;

-Debug nous permet d'accéder au mode Débug de Catalyst et Static::Simple gérera pour nous les pages statiques.

On y remarque le code suivant:


sub default : Private {
      my ( $self, $c ) = @_;

      # Hello World
      $c->response->body( $c->welcome_message );
}

Il s'agit du code qui sera utilisé si aucune autre action n'est exécutée. Pour plus de détails sur les actions prédéfinies (default, begin, end, auto, index) voir Wiki FlowChart

Au début on s'y pert un peu... Pour débugger on peut ajouter une ligne de code comme ceci:

$c->log->debug("[>> MonAppDeTest.pm sub default: bla bla bla ]") if $c->req->params->{debug};

4. Ajout d'une vue

Nous allons modifier l'application de manière a ce qu'elle utilise une vue héritant des templates toolkit. Voir Catalyst::View::TT et www.template-toolkit.org

Pour créer notre vue :

localhost:/var/www/catalyst/MonAppli/# perl script/monappli_create.pl view TT TT
 exists "/var/www/catalyst/MonAppli/script/../lib/MonAppli/View"
 exists "/var/www/catalyst/MonAppli/script/../t/View"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/View/TT.pm"

created "/var/www/catalyst/MonAppli/script/../t/View/TT.t"

Nous pouvons maintenant utiliser cette vue.

Dans lib/MonAppli.pm nous modifions :

sub default : Private {
    my ( $self, $c ) = @_;

    # Hello World
    #$c->response->body( $c->welcome_message );

    $c->stash->{template}="mavue.tt";
    $c->stash->{mavariable}="MAVARIABLE";
}

sub end : Private {
    my ( $self, $c ) = @_;

    # Forward to View unless response body is already defined

    $c->forward('View::TT') unless $c->response->body;
}

Et nous créons le fichier root/mavue.tt

Ceci est le fichier mavue.tt
mavariable=[% mavariable %]

Nous redémarrons le serveur pour la prise en compte des modifications. Cette fois pour le redémmarrer nous utilsons l'option -r qui permet au serveur de redémarrer automatiquement si un fichier est modifié. Très utile lors du développement de l'application.

On indique à View::TT quel template utiliser en fournissant son nom a travers le stash. Pour plus d'infos sur le stash voir Manual Intro

Ensuite on transfert vers MonAppli::View::TT ($c->forward('MonAppli::View::TT');)

Et voilà on voit apparaitre "Ceci est le fichier mavue.tt" ainsi que [% mavariable %] subsititué.

5. Exemples d'utilisation des TT

Comme pour le template les variables seroint fournies a notre vue a travers le hachage $c->stash. Nous allons utiliser concretement les templates toolkit en leurs fournissants des donnes.

5.1. Cas du passage d'un tableau (au plutot d'une reference a un tableau)

Dans 'defaut' de lib/MonAppli.pm ajoutons:

$c->stash->{montableau} = [ 1, 3, 5, 7, 9 ]; # reference a un tableau

Et dans le template root/mavue.tt:

<br><b>Reference a un tableau</b><br>
montableau=[% montableau %]<br>
montableau[1]=[% montableau.1 %]<br>
montableau.first = [% montableau.first %]<br>

montableau.last = [% montableau.last %]<br>
montableau.size = [% montableau.size %]<br>

join(',',montableau) = [% montableau.join(', ') %]<br>


[% FOREACH donnee = montableau %]
        [% "Premier tour <br>" IF loop.first -%]	
	donnee=[% donnee %] loop.count=[% loop.count %]<br>

[% END %]

5.2. Cas du passage d'un hachage (au plutot d'une reference a un hachage)

Dans 'defaut' de lib/MonAppli.pm ajoutons:

$c->stash->{monhach} = {
     couleur => 'rouge',
     taille  => 'petit',
     forme   => 'rond',
    };

Et dans le template root/mavue.tt:


<br><b>Reference a un hachage</b><br>
monhach=[% monhach %]<br>

monhach.couleur = [% monhach.couleur %]<br>

Infos sur les variables TT => Variables TT

5.3. Résultat d'une requête SQL dans TT

A faire ...

6. Création d'un controleur

Nous souhaitons accéder a notre vue avec une URL spécifique par exemple '/mavue'. Encore une fois (comme pour le vue TT) nous allons utiliser le 'Helper' de Catalyst pour créer le squelette de notre controleur.


perl script/monappli_create.pl controller mavue

Ouvrons le fichier lib/MonAppli/Controller/mavue.pm. Comme pour l'application il y a encore une action 'default' commente cette fois ci. On la dcommente pour quelle devienne:

sub default : Private {
    my ( $self, $c ) = @_;
    $c->log->debug("[>> MonAppli/Controller/mavue.pm sub defaut ]") if $c->req->params->{debug};

    $c->stash->{mavariable}="MAVARIABLE";
    $c->stash->{montableau} = [ 1, 3, 5, 7, 9 ]; # reference a un tableau

    $c->stash->{monhach} = {
                     couleur => 'rouge',
                     taille  => 'petit',
                     forme   => 'rond',
                     };
    $c->stash->{template}="mavue.tt";
    $c->forward('MonAppli::View::TT');
}

Modifions a nouveau 'default' de lib/MonAppli.pm pour qu'il redevienne:


sub default : Private {
    my ( $self, $c ) = @_;

    # Hello World
    $c->response->body( $c->welcome_message );
}

Et voilà notre controler est prêt. http://localhost:3000/mavue

Donc jusqu'a maintenant nous avons:

  • Conserver la page par defaut (lib/MonAppli.pm sub default)

  • Ajouter le controleur 'mavue' qui 'forward' vers mavue.tt avec View::TT

Ces deux actions sont indiqués dans le log sous cette forme:

.----------------------+----------------------------------------+--------------.
| Private              | Class                                  | Method       |
+----------------------+----------------------------------------+--------------+
| /default             | MonAppli                               | default      |
| /mavue/default       | MonAppli::Controller::mavue            | default      |
'----------------------+----------------------------------------+--------------'

Nous pouvons ajouter des actions a notre controller.Dans notre exemple notre application devra retourne la date lorsque l'on accdera a /mavue/date.

Ajout a lib/MonAppli/Controller/mavue.pm de:

...
use Date::Calc qw(Localtime);
...
sub date : Global{
    my ( $self, $c ) = @_;

    my ($year,$month,$day, $hour,$min,$sec, $doy,$dow,$dst) = Localtime();
    my $date="Nous somme le $day/$month/$year il est $hour:$min:$sec";

    $c->stash->{date}=$date;
    $c->stash->{template}="date.tt";
    $c->forward('MonAppli::View::TT');
}

Et creation du template correspondant root/date.tt


Fichier date.tt<br>

[% date %]

Et voila on peut y accéder http://localhost:3000/date nous fourni la date. D'ailleurs on le vérifie dans le log:

.----------------------+----------------------------------------+--------------.
| Private              | Class                                  | Method       |
+----------------------+----------------------------------------+--------------+
| /default             | MonAppli                               | default      |
| /mavue/date          | MonAppli::Controller::mavue            | date         |
| /mavue/default       | MonAppli::Controller::mavue            | default      |
'----------------------+----------------------------------------+--------------'

[Mon Nov 21 13:47:15 2005] [catalyst] [debug] Loaded Path actions:
.--------------------------------------+---------------------------------------.
| Path                                 | Private                               |
+--------------------------------------+---------------------------------------+
| /date                                | /mavue/date                           |
'--------------------------------------+---------------------------------------

Pour rendre cette action accessible a partir de /mavue/date l'action ne doit plus etre Global mais Local. Pour les type d'actions possibles voir Manuel::Intro#Actions

[Mon Nov 21 14:34:51 2005] [catalyst] [debug] Loaded Path actions:
.--------------------------------------+---------------------------------------.
| Path                                 | Private                               |
+--------------------------------------+---------------------------------------+
| /mavue/date                          | /mavue/date                           |
'--------------------------------------+---------------------------------------'

7. Création d'un Model

Le Model défini les moyens d'accéder à une base de donnée. Dans cet exemple nous utiliserons une base sqlite.

Le schéma de notre base étant le suivant: ../exemple.sql

                

-- exempledb.sql
CREATE TABLE page (
   id_page INTEGER PRIMARY KEY,
   titre VARCHAR(40)
);

CREATE TABLE article(
   id_article INTEGER PRIMARY KEY,
   titre VARCHAR(40),
   contenu VARCHAR(2000)
);


CREATE TABLE page_article(
   id INTEGER PRIMARY KEY,
   page INTEGER REFERENCES page,
   article INTEGER REFERENCES article
);


INSERT INTO page VALUES(1, 'Titre de la premiere page');
INSERT INTO page values(2, 'Titre de la seconde page');


INSERT INTO article values(1, 'Titre Article 1', 'contenu article 1');
INSERT INTO article values(2, 'Titre Article 2', 'contenu article 2');

INSERT INTO article values(3, 'Titre Article 3', 'contenu article 3');
INSERT INTO article values(4, 'Titre Article 4', 'contenu article 4');


INSERT INTO page_article values(1,'1','1');
INSERT INTO page_article values(2,'1','2');

INSERT INTO page_article values(3,'2','3');
INSERT INTO page_article values(4,'2','4');

Pour créer la base sqlite, on utilise la commande suivante:

localhost:/var/www/catalyst/MonAppli/$ sqlite3 exemple.db < ../exemple.sql

Crééons le Model qui utilisera cette base:

localhost:/var/www/catalyst/MonAppli/$ perl script/monappli_create.pl model MonCDBI CDBI dbi:SQLite:/var/www/catalyst/MonAppli/exemple.db
exists "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model"
 exists "/var/www/catalyst/MonAppli/script/../t"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI.pm"

created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI/Article.pm"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI/Page.pm"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI/PageArticle.pm"
 exists "/var/www/catalyst/MonAppli/script/../t"
created "/var/www/catalyst/MonAppli/script/../t/model_MonCDBI-Article.t"

 exists "/var/www/catalyst/MonAppli/script/../t"
created "/var/www/catalyst/MonAppli/script/../t/model_MonCDBI-Page.t"
 exists "/var/www/catalyst/MonAppli/script/../t"
created "/var/www/catalyst/MonAppli/script/../t/model_MonCDBI-PageArticle.t"

Cool :) Notre Model 'MonCDBI' à découvert les tables de notre base. Mouai mais encore ...

Nous allons ensuite voir comment accéder aux données de nos tables.

Pour cela nous pouvons utiliser le controleur Scaffold de cette manière:

                

localhost:/var/www/catalyst/MonAppli/$ perl script/monappli_create.pl controller Page Scaffold MonCDBI::Page
exists "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI.pm"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI/Article.pm"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI/Page.pm"
created "/var/www/catalyst/MonAppli/script/../lib/MonAppli/Model/MonCDBI/PageArticle.pm"

 exists "/var/www/catalyst/MonAppli/script/../t/Model"
created "/var/www/catalyst/MonAppli/script/../t/Model/MonCDBI-Article.t"
 exists "/var/www/catalyst/MonAppli/script/../t/Model"
created "/var/www/catalyst/MonAppli/script/../t/Model/MonCDBI-Page.t"
 exists "/var/www/catalyst/MonAppli/script/../t/Model"

created "/var/www/catalyst/MonAppli/script/../t/Model/MonCDBI-PageArticle.t"

Mais il existe depuis peu un module beaucoup plus simple d'utilisation et qui va nous faciliter grandement la tâche :)

8. Catalyst::Enzyme pour plus de simplicité

Catalyst::Enzyme nous facilite encore plus la tâche :)

Testons le immédiatement. Pour cela nous réutiliserons le schema de la base SQLite exemple.sql

Lors de la création automatique de vue,controleur ou model nous avons utilisé des 'Helpers'. Ils sont fournis pour nous aider à construire le squelette de scripts. Catalyst::Enzyme fourni aussi ses propres Helpers

Il va nous aider dans la création des scripts d'accès aux données d'une table.

localhost:/var/www/catalyst/$ catalyst.pl TestEnzyme &amp;&amp; cd TestEnzyme
localhost:/var/www/catalyst/TestEnzyme/$ vi lib/TestEnzyme.pm

On remplace
use Catalyst qw/-Debug Static::Simple/;

par
use Catalyst qw/-Debug Static::Simple DefaultEnd FormValidator/;

et
$c->response->body( $c->welcome_message );

par
$c->res->redirect("/page");


localhost:/var/www/catalyst/TestEnzyme/$ perl script/testenzyme_create.pl view TT Enzyme::TT
exists "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/View"
exists "/var/www/catalyst/TestEnzyme/script/../t"
created "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/View/TT.pm"
created "/var/www/catalyst/TestEnzyme/script/../root/base/add.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/base/edit.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/base/footer.tt"

created "/var/www/catalyst/TestEnzyme/script/../root/base/form_macros.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/base/header.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/base/list.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/base/list_macros.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/base/pager.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/base/pager_macros.tt"

created "/var/www/catalyst/TestEnzyme/script/../root/base/view.tt"
created "/var/www/catalyst/TestEnzyme/script/../root/static/css/testenzyme.css"
created "/var/www/catalyst/TestEnzyme/script/../t/view_TT.t"

localhost:/var/www/catalyst/TestEnzyme/$ mkdir db &amp;&amp; dbish dbi:SQLite:dbname=db/exemple.db < ../exemple.sql
localhost:/var/www/catalyst/TestEnzyme/$ perl script/testenzyme_create.pl model ExempleDB Enzyme::CDBI dbi:SQLite:dbname=db/exemple.db
localhost:/var/www/catalyst/TestEnzyme/$ perl script/testenzyme_create.pl controller Page Enzyme::CRUD ExempleDB::Page
localhost:/var/www/catalyst/TestEnzyme/$ perl script/testenzyme_create.pl controller Article Enzyme::CRUD ExempleDB::Article

Cool :)

Avec une page de style, un pager ... ça a une autre gueule non ?

En fait pour profiter du 'pager' (permet d'afficher qu'un nombre limité de données ) il nous faut tout dabord modifier notre Modele Page lib/TestEnzyme/Model/ExempleDB/Page.pm.

# Remplacer
__PACKAGE__->config(
    crud => {
        
    }
);

# par
__PACKAGE__->config(

        crud => {
            moniker => "Page",
            column_monikers => { __PACKAGE__->default_column_monikers, titre => "Le titre" },
            rows_per_page => 5,
            data_form_validator => {
                optional => [ __PACKAGE__->columns ],
                required => [ qw/ titre /],
                constraint_methods => {
                    titre => { name => 'contraintetitre', constraint => qr/^[A-Z]/ },
                },
                missing_optional_valid => 0,
                msgs => {
                    format => '%s',
                    constraints => {
                        contraintetitre => "Le titre doit d~buter par une majuscule !",
                    },
                },
            },
        },
);

  • column_monikers : permet de renommer une colonne a l'affichage

  • rows_per_page: comme son nom l'indique, nombre de rangée de données par page (utilisé par le 'pager')

  • data_form_validator: Nous permet de valider les donnée fourni par l'utilisateur. Dans notre exemple si aucune donnée n'est fournie au champ 'titre' (required) alors les données ne sont pas validées et donc non enregistrées dans par l'action 'do_add'.

  • constraint_methods: Contrainte sur les données. Notre titre doit commencer par une majuscule

Il nous est aussi possible de modifier les champs/colonnes à afficher. Pour notre Model 'Article' remarquez que seul les champs 'contenu' et 'titre' sont affichés, 'id_article' ne l'est pas. Pour forcer son affichage lors du listing ' /list' nous modifions la ligne suivante du Model 'Article' ( lib/TestEnzyme/Model/ExempleDB/Article.pm):


__PACKAGE__->columns(list_columns => qw/ contenu titre /);

par
__PACKAGE__->columns(list_columns => qw/ id_article contenu titre /);

Il est aussi possible de modifier les champs à afficher lors de l'ajout et de la vue d'article.

__PACKAGE__->columns(view_columns => qw/ contenu titre /);

9. Authentification

Nous n'allons pas laisser n'importe qui accéder en écriture à notre base de données, il nous faut donc un mécanisme d'authentification. Encore une fois Catalyst est là pour nous aider :)

Pour assurer l'authentication des utilisateurs nous utiliserons le module Catalyst::Plugin::Authentication::CDBI. Celui-ci ne se contente pas seulement de gérer les utilisateurs mais aussi le 'role' des utilisateurs. Nous déciderons par exemple que l'utilisateur 'toto' a le 'role' admin.

Les utilisateurs ainsi que les roles seront stockés en base. Une fois encore nous utiliserons SQLite. ( ../auth.sql )

                
-- Users
CREATE TABLE user (
        id INTEGER AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(30) NOT NULL,
        password VARCHAR(40) NOT NULL,
        firstname VARCHAR(40),
        lastname VARCHAR(40)
);

-- Roles

CREATE TABLE role (
        id INTEGER AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(30)
);

-- Mapping
CREATE TABLE user_role (
        id INTEGER AUTO_INCREMENT PRIMARY KEY,
        user INTEGER REFERENCES user,
        role INTEGER REFERENCES role

);

-- Users (pass: 12345)
REPLACE INTO user VALUES (1, 'admin', '12345','Robert','Dupont');

REPLACE INTO user VALUES (2, 'user', '12345','gaston','lagaffe');

REPLACE INTO user VALUES (3, 'toto', '12345','gaston','lagaffe');

-- Roles

REPLACE INTO role VALUES (1, 'admin');
REPLACE INTO role VALUES (2, 'writer');

REPLACE INTO role VALUES (3, 'reader');

-- User Roles
REPLACE INTO user_role VALUES (1, 1, 1);

REPLACE INTO user_role VALUES (2, 1, 2);
REPLACE INTO user_role VALUES (3, 1, 3);
REPLACE INTO user_role VALUES (4, 2, 2);

REPLACE INTO user_role VALUES (5, 2, 3);
REPLACE INTO user_role VALUES (6, 3, 3);

localhost:/var/www/catalyst/TestEnzyme/# dbish dbi:SQLite:dbname=db/auth.db < ../auth.sql

Notre application doit tout dabord hériter du module 'Catalyst::Plugin::Authentication::CDBI' et de 'Session::FastMmap'. Ajoutons les simplement dans le fichier lib/TestEnzyme.pm.


use Catalyst qw/-Debug Static::Simple DefaultEnd FormValidator Session::FastMmap Authentication::CDBI/;

...

# Authentication
__PACKAGE__->config->{authentication} = {
        user_class           => 'TestEnzyme::Model::Auth::User',
        user_field           => 'username',
        role_class           => 'TestEnzyme::Model::Auth::Role',
        role_field           => 'role',
        user_role_class      => 'TestEnzyme::Model::Auth::UserRole',
        user_role_user_field => 'user',
        user_role_role_field => 'role'

    };

...

sub begin : Private {
    my ( $self, $c ) = @_;
 
    $c->res->headers->content_type( 'text/html; charset=iso-8859-1' );

    my $result=$c->session_login('admin', '8cb2237d0679ca88db6464eac60da96345513964');
    $c->log->debug("result=$result");
}

...

sub end : Private {
    my ( $self, $c ) = @_;

    die "Debug forc~" if $c->req->params->{die};
}

'begin' est la première action exécutée, nous vérifions simplement que le login fonctionne. Dans le log du serveur devrait apparaitre '[catalyst] [debug] result=1'. ( Créer tout dabord le Model 'Auth' ci-dessous). $c->res->headers->content_type nous permet la prise en compte des accents en français.

'end' est la dernière 'action' exécutée. Elle nous permettra le debuguage des scripts, il suffira alors ajouter '?die=1' à l'url pour accéder à la page de debug.

9.1. Création du Model 'Auth'

Notre module d'authentification fait appel au Model 'Auth' qui n'a pas encore été créé. Ce que nous faisons tout de suite.

                    

localhost:/var/www/catalyst/TestEnzyme/$ perl script/testenzyme_create.pl model Auth CDBI dbi:SQLite:dbname=db/auth.db
 exists "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/Model"
 exists "/var/www/catalyst/TestEnzyme/script/../t"
created "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/Model/Auth.pm"
created "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/Model/Auth"

created "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/Model/Auth/Role.pm"
created "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/Model/Auth/User.pm"
created "/var/www/catalyst/TestEnzyme/script/../lib/TestEnzyme/Model/Auth/UserRole.pm"
 exists "/var/www/catalyst/TestEnzyme/script/../t"
created "/var/www/catalyst/TestEnzyme/script/../t/model_Auth-Role.t"
 exists "/var/www/catalyst/TestEnzyme/script/../t"

created "/var/www/catalyst/TestEnzyme/script/../t/model_Auth-User.t"
 exists "/var/www/catalyst/TestEnzyme/script/../t"
created "/var/www/catalyst/TestEnzyme/script/../t/model_Auth-UserRole.t"

En redémarrant le serveur nous pouvons vérifier que les tables sont correctement chargées et que result est bien égal à '1' ce qui nous confirmera qur l'authentification s'est correctement déroulée. Un cookie à aussi été créé contenant le numéro de session.

[] [catalyst] [debug] Loaded tables "article page page_article"
[] [catalyst] [debug] Loaded tables "role user user_role"
...
[] [catalyst] [debug] result=1

Nous allons dans un premier temps imposer une authentification des utilisateurs quelle que soit la page accédée. Pour celà nous modifions l'action 'begin' du fichier principal de notre application lib/TestEnzyme.pm

sub begin : Private {
    my ( $self, $c ) = @_;

    # Pour les accents

    $c->res->headers->content_type( 'text/html; charset=iso-8859-1' );

    # force login for all pages
    unless ($c->req->{user}) {
      $c->req->action(undef);
      $c->forward('/login/login');
    }
}

'begin' étant éxécuté avant tout autre action, cela oblige les utilisateurs à se loguer.

Nous constatons que l'action 'begin' n'est pas exécutée. Hmmm ... Pourquoi ???. Rappelons nous que nous venons de tester le fonctionnement du 'login' et qu'un cookie a été créé. Supprimons le et là et bien ...

9.2. Création du controleur 'Login'

Il nous faut donc créer le controleur 'Login'.

localhost:/var/www/catalyst/TestEnzyme/$ perl script/testenzyme_create.pl controller Login

Et l'action login du controleur 'Login' ( lib/TestEnzyme/Controller/Login.pm

sub default : Private {
  my ($self, $c) = @_;
  $c->forward('login');
}


sub login : Path('/login') {
  my ( $self, $c ) = @_;

  if ($c->req->params->{username}) {
      my $login=$c->session_login(
                      $c->req->params->{username},
                      $c->req->params->{password}
                     );
     if ( $login ){
       $c->res->redirect( $c->session->{referer} || '/' );
     }
     else{
       $c->stash->{template} = "login.tt"

     }
  }
  else {
    # save the referring page so we can redirect back
    $c->session->{referer} = $c->req->path;
    $c->stash->{template} = "login.tt"
  }
}

Si nous ne sommes pas authentifié alors nous sommes redirigé vers la page de login. Le reste se passe de commentaire :)

9.3. Ajout du template 'login.tt'

Il nous faut encore créer le template 'login.tt'

localhost:/var/www/catalyst/TestEnzyme/$ cat root/login.tt
[% INCLUDE base/header.tt %]


<form action="/login" method="post">
<fieldset>
        <legend>Connexion.</legend>
        <label for="username"><span class="field">Login:</span></label>
        <input type="text" name="username" /><br />

        <label for="password"><span class="field">Password:</span></label>
        <input type="password" name="password" /><br />

        <label for="submit"><span class="field"></span></label>

        <input type="submit" value="Se connecter" /><br />
</fieldset>
</form>


[% IF ! c.req.user %]
<script language="javascript">
        document.forms[0].username.focus();
</script>

[% END %]

<fieldset>
        Se connecter en admin password=12345 ou user/12345 ou toto/12345
</fieldset>

Terminé. Notre système d'authentification est en place :)

Nous allons modifier maintenant modifier un peu l'authentification. L'utilisateur pourra voir divers éléments selon son 'role'. S'il n'est pas logué alors il ne dispose d'aucun 'role' et un onglet 'login' apparait en haut de page. S'il est logué un onglet 'logout' apparait en haut de page. Pour y parvenir nous devrons:

  • Créer une action 'Logout'.

  • Supprimer l'action 'begin' de l'application qui redirige s'il l'utilisateur n'est pas logué

  • Modifier nos templates pour qu'ils fasse nt apparaitre une barre de navigation (Login/Logout)

9.4. Ajout de 'Logout'

L'ajout de l'action 'Logout' se fait simplement en modifiant/ajoutant comme suit le code de lib/TestEnzyme/Controller/Login.pm


sub login : Path('/login') {
  my ( $self, $c ) = @_;

  if ($c->req->params->{username}) {
      my $login=$c->session_login(
                      $c->req->params->{username},
                      $c->req->params->{password}
                     );
     if ( $login ){
          $c->res->redirect( '/' );
     }
     else{
       $c->stash->{template} = "login.tt"

     }
  }
  else {
    # save the referring page so we can redirect back
    $c->session->{referer} = $c->req->path;
    $c->stash->{template} = "login.tt"
  }
}


sub logout : Path('/logout') {
        my ($self, $c) = @_;

        $c->session_logout if ($c->req->{user});
        $c->res->redirect('/login/login');
}

On peut dès à présent utiliser l'url 'login/logout' qui après avoir supprimer la session nous redirige vers 'login/login'.

TODO: Crypter les mots de passe dans db/auth.db

10. Intégration d'une barre de navigation

Pour intégrer notre 'barre de navigation' à nos page nous allons l'ajouter à la manière de ce qui est fait dans les templates qui ont été créés automatiquement (root/base/[add,list,edit,view].tt). Ont peut constater qu'il y a un '[% INCLUDE 'header.tt' %]' dans chacun de ceux-ci. C'est donc dans ce template que nous ferons notre modification.

localhost:/var/www/catalyst/TestEnzyme/$ cat root/base/header.tt
...
[% INCLUDE "navbar.tt" %]

Le barre de navigation root/base/navbar.tt se présente sous cette forme

localhost:/var/www/catalyst/TestEnzyme/$ cat root/base/navbar.tt
<div id="navcontainer">
        <ul class="navlist">
                <li [% IF nav == 'acceuil' %]id="active"[% END %]><a href="/">Acceuil</a></li>

                <li [% IF nav == 'search' %]id="active"[% END %]><a href="/page">Page</a></li>
                <li [% IF nav == 'report' %]id="active"[% END %]><a href="/article">Article</a></li>

                [% IF ! c.req.user %]<li><a href="/login/login">Login</a></li>[% END %]
                [% IF c.req.user %]<li><a href="/login/logout">Logout</a></li>[% END %]
        </ul>

</div>

On remarque que le paramètre 'nav' permet d'utiliser un 'id active' qui pourra être utilisée par la feuille de style CSS. Nous y reviendrons plus tard. Voyons ce que ça donne ...

Oui il fallait s'y attendre, notre barre de navigation va devoir être 'habillée' par la page CSS root/static/css/testenzyme.css. On y ajoute ce qui suit

.navlist {
    padding: 3px 0;
    margin-left: 0;
    margin-top: 1em;
    border-bottom: 1px solid #778;
#    font: bold 12px Verdana, sans-serif;
}

.navlist li {
    list-style: none;
    margin: 0;
    display: inline;
}

.navlist li a {
    padding: 3px 0.5em;
    margin-left: 3px;
    border: 1px solid #778;
    border-bottom: none;
    background: #b5cadc;
    text-decoration: none;
}

.navlist li a:link { color: #448; }
.navlist li a:visited { color: #667; }

.navlist li a:hover {
    color: #000;
    background: #eef;
    border-top: 4px solid #7d95b5;
    border-color: #227;
}

.navlist #active a {
    background: white;
    border-bottom: 1px solid white;
    border-top: 4px solid;
}

.loginas{
  background-color:#FF0000;
 }

Ah oui là c'est mieux :)

Le paramètre 'nav' va permettre le colorier l'onglet relatif à la page à laquelle on accède. Pour cela modifons l'action 'default' de lib/TestEnzyme.pm pour qu'il nous transfert vers un template 'index.tt' et l'action 'end' qui enregistre dans le stash nav='acceuil'.

sub default : Private {
    my ( $self, $c ) = @_;


    # Hello World

    $c->stash->{template} ||= "index.tt";
}

sub end : Private {
    my ( $self, $c ) = @_;

    $c->stash->{nav} = "acceuil" unless $c->stash->{nav};

    # Forward to View unless response body is already defined

    $c->forward('View::TT') unless $c->response->body;
        die "Debug forc~" if $c->req->params->{die};
}

Au tours du template root/base/index.tt

[% INCLUDE "header.tt" %]
[% INCLUDE "navbar.tt" %]

Page d'acceuil.  ( ~ ~ ~ ~ ~ )

[% INCLUDE "footer.tt" %]

L'onglet de la page par defaut 'acceuil' est maintenant mise en évidence.

Nous allons procéder de manière identique pour les autres onglets. Les onglets nous transfert vers les controleurs 'Page' et 'Article', c'est donc dans ces controleurs que nous fixerons le paramètre 'nav' spécifique et plus extactement dans l'action 'begin' du controleur. Pour le controleur 'Page' ( ) nous aurons donc

sub begin : Private {
    my ( $self, $c ) = @_;

    $c->stash->{nav} = "page";
}

Et nous ferons de même pour le controleur 'Article'. Notre barre de navigation fonctionne, elle est facilement maintenable et il est possible d'en ajouter ou supprimer facilemnt des onglets. Nous pourrions sur le même princi


Add a comment

Validator_logo
Catapulse v0.06
( 0.082921 s)