version 0.08
Résumé
Ce document présente l'utilisation de Catalyst, un framework orienté Web écrit en Perl
Table des matières
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.
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é.
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[root@localtux]#
debootstrap sid chroot_catalyst
# 2 - On prend pour racine ce nouveau système[root@localtux]#
chroot chroot_catalyst
# 3 - Montage du FS /proclocalhost:/#
mount -t proc proc /proc
# 4 - Lecture des derniers packageslocalhost:/#
apt-get update
# 5 - Install minimum packages for catalystlocalhost:/#
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
localhost:/#
dpkg-reconfigure locales
localhost:/#
apt-get clean (=~ 300 Mo)
# 6 - Init .cpanlocalhost:/#
perl -MCPAN -e shell
# 7 - Install lasted packages for Catalystlocalhost:/#
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.
Nous crééons tout dabord le répertoire d'acceuil de notre application et nous nous y rendons.
localhost:/#
mkdir -p /var/www/catalyst && cd /var/www/catalyst
Ensuite création de l'application
localhost:/var/www/catalyst/MonAppli/#
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};
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é.
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.
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 %]
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
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.
localhost:/var/www/catalyst/MonAppli/#
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 | '--------------------------------------+---------------------------------------'
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:
localhost:/var/www/catalyst/MonAppli/#
cat ../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 :)
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 && 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 && 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 /);
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.
localhost:/var/www/catalyst/TestEnzyme/#
cat ../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.
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 ...
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 :)
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)
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
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 principe mettre en place une barre de nagation de second niveau en fonction de l'onglet sur lequel on est situé.
On continue ... Nous aimerions pouvoir accéder au site ou du moins à certaines rubriques sans pour autant être logué. Supprimons l'action 'begin' de lib/TestEnzyme.pm
. Il n'est plus maintenant necessaire de se loguer mais n'importe qui accède à notre base exemple.db. Il nous faut donc aussi modifié les templates pour qu'ils prennent en compte le 'role' des utilisateurs.
Nota: Les actions 'delete' et 'view' ne fonctionne qu'a partir de la version 0.09 de Catalyst::Enzyme.
Le plugin Catalyst Authentification::CDBI prend en compte les roles, mais comment les utiliser. Nous pourrions par exemple ajouter ce code a notre controleur 'Page'.
sub begin : Private { my ( $self, $c ) = @_; if ( ! $c->roles('admin') ){ $c->res->redirect('/acces_refused'); } $c->stash->{nav} = "page"; }
Si le role n'est pas 'admin' et que l'on accède à '/page' nous sommes redirigé vers '/acces_refused'. Nous ferons de même pour le controleur 'Article' et enfin nous ajouterons l'action 'acces_refused' à notre application qui fourniera un temmplate 'acces_refused.tt'.
Dans lib/TestEnzyme.pm
nous ajoutons l'action 'acces_refused':
sub acces_refused : Private { my ( $self, $c ) = @_; $c->stash->{template} = "acces_refused.tt"; }
Et nous ajoutons le template root/base/acces_refused.tt
[% INCLUDE "header.tt" %] [% INCLUDE "navbar.tt" %] Accès refusé [% INCLUDE "footer.tt" %]
De plus il ne faut pas que les onglets 'Page' et 'Article' apparaissent si nous n'avons pas le role 'admin'. 'navbar.tt' nous permet l'affichage de la barre de navigation c'est donc ce fichier (root/base/navbar.tt
)que nous allons modifier comme ceci:
[% IF c.roles('admin') %] <li [% IF nav == 'page' %]id="active"[% END %]><a href="/page">Page</a></li> <li [% IF nav == 'article' %]id="active"[% END %]><a href="/article">Article</a></li> [% END %]
Voici la version 0.02 de notre application : TestEnzyme-0.02.tgz. Elle contient le code de l'application que nous venons de construire.
Les modifications suivantes ont été apportée à la version 0.03 de notre application 'TestEnzyme': TestEnzyme-0.03.tgz
Ajout controleur 'Preferences'
Ajout onglet 'nom de l'utilisateur' qui pointe vers le controleur '/preference'
'error' est utilisé dans header.tt pour donner la raison lors des accès a '/acces_refused'.
Nettoyage du code (suppression des '^M' de notepad ? :)
Les modifications suivantes ont été apportée à la version 0.04 de notre application 'TestEnzyme': TestEnzyme-0.04.tgz
Prise en compte de la version 0.09 de Catalyst::Enzyme ('view' et 'delete' fonctionne correctement)
'navbar.tt' déplacé dans 'header.tt'
Dans ce chapitre nous abandonnerons notre précédente application pour nous focaliser sur le model CDBI. (voir Class::DBI)
Notre Model utilisera une base Mysql. Pour ne pas trop se compliquer la vie, la base ne sera pas installée dans notre 'chroot'. Ce pourra être une base installée sur une autre machine ou en local mais en dehors du 'chroot'. Notre schéma sera similaire a celui d'exemple.sql
à l'exception d'un champ 'date_creation' (que nous utiliserons plus tard) ajouté à la table 'article' et de la mise en place de droit d'accès à celle-ci. Nous ajouterons aussi une table 'auteur'. On sort du chroot (Control D)
tux:#
cat exemple2.sql
-- exemple2db.sql DROP DATABASE IF EXISTS exemple2; CREATE DATABASE exemple2; use exemple2; CREATE TABLE page ( id_page INTEGER PRIMARY KEY, titre VARCHAR(40), auteur INTEGER REFERENCES auteur ); CREATE TABLE article( id_article INTEGER PRIMARY KEY, page INTEGER REFERENCES page, titre VARCHAR(40), date_creation DATE, contenu VARCHAR(2000) ); CREATE TABLE auteur ( id_auteur INTEGER PRIMARY KEY, nom VARCHAR(40) ); INSERT INTO page VALUES(1, 'Titre de la premiere page', 1); INSERT INTO page values(2, 'Titre de la seconde page', 2); INSERT INTO article values(1, 1, 'Titre Article 1', '2005-12-10','contenu article 1'); INSERT INTO article values(2, 1, 'Titre Article 2', '2005-12-10','contenu article 2'); INSERT INTO article values(3, 2, 'Titre Article 3', '2005-12-10','contenu article 3'); INSERT INTO article values(4, 2, 'Titre Article 4', '2005-08-01','contenu article 4'); INSERT INTO auteur values(1, 'dab'); INSERT INTO auteur values(2, 'newton'); GRANT ALL PRIVILEGES ON exemple2.* to myuser@localhost IDENTIFIED BY 'passwd';
Création de la base 'exemple2':
:#
mysql -u root -p < exemple2.sql
Enter password:
Ok notre base est créée. Vérifions que nous pouvons y accéder. On retourne dans notre chroot
tux:#
chroot chroot_catalyst
localhost:#
mysql -u myuser -p -h 127.0.0.1 exemple2
Enter password:(passwd)Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 26 to server version: 4.1.15-Debian_1-log Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> show tables; +--------------------+ | Tables_in_exemple2 | +--------------------+ | article | | auteur | | page | +--------------------+ 3 rows in set (0.00 sec)
OK notre accès à 'exemple2' par le réseau est satisfaisant.
C'est reparti ... Heu ... pas tout à fait, il nous faut tout dabord installé le module Perl Class::DBI::mysql. Malheureusement celui-ci tombe en erreur lorsque nous tentons de l'installer par la méthode 'perl -MCPAN -e "install Class::DBI::mysq" car il essaye d'accéder en local à la base de test de Mysql ( failed: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'). Nous l'installerons donc sans procéder aux tests:
localhost:#
cd /root/.cpan/build/Class-DBI-mysql-1.00 && perl Makefile.PL && make && make install && cd /var/www/catalyst
localhost:/var/www/catalyst#
catalyst.pl AppCDBI && cd AppCDBI
localhost:/var/www/catalyst/AppCDBI#
perl script/appcdbi_create.pl model Exemple2DB CDBI 'dbi:mysql:database=exemple2;host=127.0.0.1' 'myuser' 'passwd'
exists "/var/www/catalyst/AppCDBI/script/../lib/AppCDBI/Model" exists "/var/www/catalyst/AppCDBI/script/../t" exists "/var/www/catalyst/AppCDBI/script/../lib/AppCDBI/Model/Exemple2DB.pm" created "/var/www/catalyst/AppCDBI/script/../lib/AppCDBI/Model/Exemple2DB.pm.new" created "/var/www/catalyst/AppCDBI/script/../lib/AppCDBI/Model/Exemple2DB" created "/var/www/catalyst/AppCDBI/script/../lib/AppCDBI/Model/Exemple2DB/Article.pm" created "/var/www/catalyst/AppCDBI/script/../lib/AppCDBI/Model/Exemple2DB/Auteur.pm" created "/var/www/catalyst/AppCDBI/script/../lib/AppCDBI/Model/Exemple2DB/Page.pm" exists "/var/www/catalyst/AppCDBI/script/../t" created "/var/www/catalyst/AppCDBI/script/../t/model_Exemple2DB-Article.t" exists "/var/www/catalyst/AppCDBI/script/../t" created "/var/www/catalyst/AppCDBI/script/../t/model_Exemple2DB-Auteur.t" exists "/var/www/catalyst/AppCDBI/script/../t" created "/var/www/catalyst/AppCDBI/script/../t/model_Exemple2DB-Page.t"
Pour faire nos divers tests nous créons une vue et son template (root/test.tt
) et comme précédement modifions l'action 'end' de notre application (lib/AppCDBI.pm
) pour quelle 'forward' vers notre vue. :
localhost:/var/www/catalyst/AppCDBI#
perl script/appcdbi_create.pl view TT TT
localhost:/var/www/catalyst/AppCDBI#
cat lib/AppCDBI.pm
... ... sub end : Private { my ( $self, $c ) = @_; $c->forward('View::TT'); }
localhost:/var/www/catalyst/AppCDBI#
cat root/test.tt
Test Class::DBI<br> colonnes page: [% colonnes.join(', ') %]<br> pages: [% pages.join(', ') %]<br>
Et enfin le controleur associé '/test'
localhost:/var/www/catalyst/AppCDBI#
perl script/appcdbi_create.pl controller test
C'est dans ce controleur que nous effecterons nos divers requetes SQL dont le résultat sera stocké dans le 'stash' ($c->stash->{result}).
localhost:/var/www/catalyst/AppCDBI#
cat lib/AppCDBI/Controller/test.pm
... sub default : Local { my ( $self, $c ) = @_; $c->stash->{template} = "test.tt"; my @colonnes_pages= AppCDBI::Model::Exemple2DB::Page->columns; my @pages= AppCDBI::Model::Exemple2DB::Page->retrieve_all; $c->stash->{colonnes} = \@colonnes_pages; $c->stash->{pages} = \@pages; } ...
http://localhost:3000/test nous retourne
test Class::DBI colonnes page: id_page, auteur, titre pages: 1, 2
On constate que le 'retrieve_all' retourne l'id_page des pages. En fait 'retrieve_all' retourne un 'iterator' que nous pouvons utiliser comme ceci dans le template root/test.tt
:
[% FOREACH p = pages %] Id: [% p.id_page %] Titre: [% p.titre %] Auteur: [% p.auteur %]<br> [% END %]
Ce qui nous donne:
Id: 1 Titre: Titre de la premiere page Auteur: 1 Id: 2 Titre: Titre de la seconde page Auteur: 2
Si nous souhaitons faire une recherche en base nous utiliserons la methode 'search' de notre Modèle CDBI. Par exemple nous cherchons tous les articles publié le 10/12/2005. Modifions le controleur 'test' comme ci-dessous:
my @articles = AppCDBI::Model::Exemple2DB::Article->search( date_creation => '2005-12-10'); $c->stash->{articles} = \@articles;
Et ajoutons au template 'test.tt' ceci
[% FOREACH article = articles %] Id: [% article.id_article %] Titre: [% article.titre %]<br> [% END %]
Qui nous retourne :
Id: 1 Titre: Titre Article 1 Id: 2 Titre: Titre Article 2 Id: 3 Titre: Titre Article 3
Il nous est bien sur possible de trier les données par un 'order_by'. Ainsi dans l'exemple ci-dessus nous pourrions vouloir trier les données selon leur date de création :
my @articles = AppCDBI::Model::Exemple2DB::Article->search_like( titre => 'Titre Article%', { order_by => 'date_creation'});
Qui nous retourne :
Id: 4 Titre: Titre Article 4 Id: 1 Titre: Titre Article 1 Id: 2 Titre: Titre Article 2 Id: 3 Titre: Titre Article 3
Jusque là nous avons effectué des requêtes sur des tables uniques, mais comment procéder pour faire référence à plusieurs tables. Comment par exemple selectionner "toutes les pages dont l'auteur est 'dab' ou encore "tous les articles de la page 1"? C'est là qu'interviennent les 'relationship'.
Dans un premier temps nous allons afficher le nom de l'auteur et non plus son 'id', pour cela nous spécifions dans le Modele Page lib/AppCDBI/Model/Exemple2DB/Page.pm
la relation qui existe entre le champ 'auteur' de la table Page et la table Auteur. Pour chaque page il n'existe qu'un auteur.
__PACKAGE__->has_a( auteur => 'AppCDBI::Model::Exemple2DB::Auteur');
et dans le template 'test.tt nous remplacons :
Id: [% p.id_page %] Titre: [% p.titre %] Auteur: [% p.auteur %] par Id: [% p.id_page %] Titre: [% p.titre %] Auteur: [% p.auteur.nom %]
Ok cette fois ci nous avons bien le nom en clair de l'auteur:
Id: 1 Titre: Titre de la premiere page Auteur: dab Id: 2 Titre: Titre de la seconde page Auteur: newton
Pour chaque page il existe plusieurs articles, on peut donc écrire dans lib/AppCDBI/Model/Exemple2DB/Page.pm
__PACKAGE__->has_many( article => 'AppCDBI::Model::Exemple2DB::Article');
Et on modifie le template 'test.tt' comme ceci:
[% FOREACH p = pages %] Id: [% p.id_page %] Titre: [% p.titre %] Auteur: [% p.auteur.nom %]<br> [% FOREACH article = p.article %] Article: [% article %] -> [% article.titre %]<br> [% END %] [% END %]
Et là pour chacune des pages nous avons bien les articles correspondants:
Id: 1 Titre: Titre de la premiere page Auteur: dab Article: 1 -> Titre Article 1 Article: 2 -> Titre Article 2 Id: 2 Titre: Titre de la seconde page Auteur: newton Article: 3 -> Titre Article 3 Article: 4 -> Titre Article 4
Yep :)
Et le source de cette l'application AppCDBI.tgz
Jusqu'a maintenant nous avons interroger la base de donnée, dans ce chapitre il sera question d'insertion, de mise à jour et de suppression d'enregistrement.
Pour l'insertion nous utiliserons la methode find_or_create. Ajoutons un article (toujours à partir du controleur 'test':
my $article5=AppCDBI::Model::Exemple2DB::Article->find_or_create({ id_article => 5, page => 1, titre => 'Test insertion', date_creation => '2005-07-01', contenu => 'Ceci est une insertion', });
Et voilà un nouvel article est ajouté. On peut remarquer que cet article sera ajouté une seule fois.(même si on recharge la page)
Pour permettre la mise à jour d'un enregistrement il nous faut dabord rechercher l'enregistrement (par un search ou search_like), modifier un/des champ(s) de celui-ci et ensuite faire l'update. Si nous souhaitons mettre à jour le titre article5 du paragraphe ci-dessus:
$article5->titre('Nouveau titre'); $article5->update;
Pour supprimer un enregistrement il suffit d'utiliser la méthode delete. Toujours avec l'article5, nous ferions comme ceci:
$article5->delete
Avec la methode delete_all il est possible de supprimer plusieurs enregitrement à la fois.
my @articles = AppCDBI::Model::Exemple2DB::Article->search( date_creation => '2005-12-10')->delete_all;
CDBI nous permet aussi la suppression en cascade, c'est à dire la suppression d'un (ou plusieurs) enregitrement et tout ce qu'il s'y réfère (voir has_a et has_many ). Nous pouvons par exemple supprimer une page et tous les articles qui s'y rattachent en supprimant simplement la page. CDBI se charge de tout :)
Il s'agit d'un plugin permettant de profiter d'une zolie barre de progression lors d'upload de fichiers :)
localhost:/var/www/catalyst/#
svn co http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Plugin-UploadProgress
localhost:/var/www/catalyst/#
cd Catalyst-Plugin-UploadProgress && perl Makefile.PL && make && make install
localhost:/var/www/catalyst/Catalyst-Plugin-UploadProgress#
cd example/Upload/
localhost:/var/www/catalyst/Catalyst-Plugin-UploadProgress/example/Upload#
perl script/upload_server.pl
Et voilà le résultat :)
use Catalyst; my @auth = qw/Authentication Authentication::Credential::Password Authentication::Store::Htpasswd/; my @session = qw/Session Session::State:Cookie Session::Store::Memcached/; __PACKAGE__->setup( @session, @auth );
Voir http://dev.catalyst.perl.org/file/trunk/Catalyst-Plugin-Authentication-Store-DBIC/README
Template Toolkit
TT is great for this. Every template just does something like: [% WRAPPER header.xhtml %] page content here [% END %] And in header.xhtml you have it construct the page: <!-- static header stuff --> [% PROCESS left_menu.xhtml %] [% PROCESS breadcrumb.xhtml %] [% content %] <- page content is inserted here [% PROCESS footer.xhtml %]
Catalyst et Oracle
I am researching the possibility of integrating Catalyst into my company's high-traffic e-commerce site. My firm uses Oracle as its production database, so I am trying to get Catalyst and Oracle to work together. As a first stab, I am trying to get the MiniMojo Wiki from the perl.com article (which I successfully set up) to use Oracle. Here's what I've done so far... * Created a table called "page" in Oracle with the same structure as the SqlLite version in the article. * Updated the MiniMojo::M::CDBI package to use dsn to connect to our Oracle database. * Installed Class::DBI::Oracle and Class::DBI::Loader::Oracle (since they didn't come with the Class::DBI or Catalyst packages). * Created an Oracle sequence called "page_seq" according to the convention outlined in the Class::DBI::Oracle docs. * Added a constraint => '^page$' argument to the call to config() in MiniMojo::M::CDBI, so it wouldn't try to load all of the tables in the (very large) Oracle database. (Is this the best/only way to specify which tables to load?) * Browsed to http://localhost:3000 <http://localhost:3000/> and got the following error. Caught exception "Can't insert new MiniMojo::M::CDBI::Page: DBD::Oracle::st execute failed: ORA-01400: cannot insert NULL into ("CBD"."PAGE"."ID") (DBD ERROR: OCIStmtExecute) [for Statement "INSERT INTO page (title, id) VALUES (?, ?) " with ParamValues: :p1='Frontpage', :p2=undef] at /usr/local/lib/perl5/site_perl/5.8.3/DBIx/ContextualFetch.pm line 51. at /home/simon/catalyst/MiniMojo/script/../lib/MiniMojo/C/Page.pm line 41" ------------------------------------------------------------------------------- Simon Miner wrote: > Can anyone tell me why the Page class is not finding its sequence or > using this code? How can I code logic and configuration specific to > this table? Class::DBI::Loader::Oracle doesn't contain any sequence discovery code. A coworker of mine wrote some but it hasn't made it into the Loader::Oracle distribution yet. You can see the code as part of DBIx::Class though: http://search.cpan.org/src/AGRUNDMA/DBIx-Class-0.02/lib/DBIx/Class/PK/Auto/Oracle.pm But what you really want to do is simply forget about Class::DBI::Loader and define everything manually. I've got 2 production Catalyst+Oracle systems running now and they run just fine with manually defined table classes. Here's what our model classes look like for one of these apps: package Events::M::CDBI; use strict; use base 'Class::DBI::Sweet'; use DateTime::Format::Strptime; use Class::DBI::FromForm; # For debugging, use this: # $DBI::neat_maxlen = 2000; # DBI->trace( 1, '/tmp/dbi.trace' ); # Get your $database, $schema, and $password values however you want __PACKAGE__->connection( "dbi:Oracle:$database", $schema, $password, { LongReadLen => 65536, AutoCommit => 1 } ); # cache all queries # use this with care, on a fast server it will be a performance hit to have resultset_cache enabled globally __PACKAGE__->cache( Events->cache ); __PACKAGE__->default_search_attributes( { use_resultset_cache => 1, profile_cache => 0 } ); # sequence support __PACKAGE__->sequence( 'teched_events_seq_1' ); __PACKAGE__->set_sql( 'Nextval', 'SELECT %s.NEXTVAL from DUAL' ); # create all the date relationships sub setup_date_fields { my $self = shift; foreach my $field ( $self->columns ) { if ( $field =~ /date$/i ) { $self->has_a( $field => 'DateTime', inflate => sub { $self->handle_date(shift) }, deflate => sub { shift->ymd }, ); } } } # handle dates, both from Oracle 16-JUN-05, and from the web YYYY-MM-DD sub handle_date { my ( $self, $date ) = @_; if ($date =~ /\d{4}-\d{2}-\d{2}/) { return DateTime::Format::Strptime->new( pattern => '%Y-%m-%d', time_zone => 'America/New_York', )->parse_datetime( $date ); } else { return DateTime::Format::Strptime->new( pattern => '%d-%b-%y', time_zone => 'America/New_York', )->parse_datetime( $date ); } } 1; ---- And here's one of the table classes. Every table class follows the same basic pattern. Note the alter session code. This changes Oracle to use YYYY-MM-DD instead of the stupid DD-MON-YY format. It's called in triggers because for whatever reason mod_perl connections don't seem to remember their date format. package Events::M::Event; use strict; use base 'Events::M::CDBI'; __PACKAGE__->table('event'); __PACKAGE__->columns(Primary => qw/event_id/); __PACKAGE__->columns(Essential => qw/title start_date end_date region_id location details_url comments enabled/); __PACKAGE__->columns(Others => qw/create_date created_by modified_date modified_by/); __PACKAGE__->setup_date_fields; __PACKAGE__->has_a( region_id => 'Events::M::Region' ); __PACKAGE__->has_many( contacts => [ 'Events::M::EventContact' => 'contact_id' ] ); __PACKAGE__->has_many( markets => [ 'Events::M::EventMarket' => 'market_id' ] ); __PACKAGE__->has_many( product_groups => [ 'Events::M::EventProductGroup' => 'product_group_id' ] ); __PACKAGE__->has_many( types => [ 'Events::M::EventType' => 'type_id' ] ); __PACKAGE__->add_trigger( before_create => \&alter_session ); __PACKAGE__->add_trigger( before_update => &alter_session ); __PACKAGE__->add_trigger( select => \&alter_session ); # Change the Oracle date format # Every mod_perl process needs this to run at least once sub alter_session { my $self = shift; # check the current nls_date_format my ($format) = $self->db_Main->selectrow_array( "SELECT value FROM sys.v_\$nls_parameters WHERE parameter = 'NLS_DATE_FORMAT'" ); if ( $format ne "YYYY-MM-DD" ) { warn "NLS_DATE_FORMAT using incorrect value of $format, resetting to YYYY-MM-DD\n"; $self->db_Main->do( 'alter session set nls_date_format = "YYYY-MM-DD"' ); } } 1;