CatalystBasics? Dokumentation | Download als POD | Wie kann ich hier etwas ändern?

NAME

Catalyst::Manual::Tutorial::CatalystBasics - Catalyst Tutorial - Teil 2: Catalyst Grundlagen der Anwendungsentwicklung

ÜBERSICHT

Dies ist Teil 2 von 9 des Catalyst Tutorials.

L<Tutorial Übersicht|Catalyst::Manual::Tutorial>

  1. L<Einführung|Catalyst::Manual::Tutorial::Intro>

  2. Catalyst Grundlagen

  3. Grundlegendes CRUD?

  4. Authentifizierung?

  5. Autorisierung?

  6. Debuggen?

  7. Testen?

  8. Fortgeschrittenes CRUD?

  9. Anhang?

BESCHREIBUNG

In diesem Teil des Tutorials, werden wir eine sehr einfache Catalyst Webanwendung erstellen. Auch wenn dieser Abschnitt in vielerlei Weise sehr einfach gehalten ist, wird er doch eine Reihe mächtiger Fähigkeiten demonstrieren, so wie:

* Helper Scripts

Catalyst Helper Scripts können verwendet werden, um schnell das Grundgerüst einer Anwendung zu erstellen.

* MVC

Model/View/Controller (MVC übersetzt etwa Modell/Präsentation/Steuerung) stellt eine Struktur bereit, die eine saubere Trennung der einzelnen Teile einer Applikation erleichtert. Da viele andere Dokumente dieses Thema im Detail besprechen, werden wir es hier nicht erschöpfend behandeln (für eine erstklassige Einführung in MVC und grundlegender Catalyst-Konzepte, lesen Sie bitte Catalyst::Manual::About?). Kurz gesagt:

* Modell (Model)

Das Modell repräsentiert normalerweise einen Datenspeicher. In den meisten Anwendungen entspricht das den Objekten, die aus der SQL-Datenbank erzeugt bzw. dorthin gespeichert werden.

* Präsentation (View)

Die Präsentation erstellt aus den Modell-Objekten etwas, das der Endbenutzer ansehen kann. Normalerweise ist das ein Modul zur Verarbeitung von Templates, welches HTML für den Webbrowser des Benutzers erstellt. Es könnte sich dabei aber auch einfach um Code handeln, der andere Formate wie PDF-Dokumente, E-Mails oder Exceltabellen erstellt.

* Steuerung (Controller)

Wie durch den Namen bereits sugeriert, nimmt die Steuerung Nutzeranfragen entgegen und leitet diese entsprechend zum Modell und zur Präsentation weiter.

* ORM

Die Verwendung von Object-Relational Mapping (ORM) Technologie für den Zugriff auf Datenbanken. Genauer gesagt stellt ORM automatisierte und standarisierte Methoden zur verfügung, um Objekte in eine Relationale Datenbank zu speichern und von dort wieder herzustellen.

Der Sourcecode für dieses Beispiel kann aus dem Catalyst Subversion Repository mit Hilfe der Anletung aus Catalyst::Manual::Tutorial::Intro? herunter geladen werden.

EIN CATALYST PROJEKT ERSTELLEN

Catalyst stellt eine Menge an Helper Scripten zur Verfügung, die verwendet werden können, um das Grundgerüst einer Anwendung schnell mit Leben zu füllen. Alle Catalyst-Projekte fangen mit dem catalyst.pl -Helper an (siehe Catalyst::Helper? für mehr Informationen zu den Helper Sripten). Es ist zu beachten, dass seit Catalyst 5.7000 die Helper Scripte nur zur Verfügung stehen, wenn sowohl Catalyst::Runtime? als auch Catalyst::Devel? installiert sind.

In diesem Fall verwenden wir das Catalyst Script catalyst.pl um die Umgebung für eine Applikation namens MyApp zu erstellen.

    $ catalyst.pl MyApp
    created "MyApp"
    created "MyApp/script"
    created "MyApp/lib"
    created "MyApp/root"
    ...
    created "MyApp/script/myapp_create.pl"
    $ cd MyApp

Das Helper Script catalyst.pl stellt die Namen aller Verzeichnisse und Dateien dar, die es erstellt.

Auch wenn es noch zu früh ist, um in Freudentaumel auszubrechen, haben wir doch bereits eine funktionierende Applikation. Mittels folgendem Befehl lässt sich die Applikation unter dem eingebauten Entwicklungsserver starten.

    $ script/myapp_server.pl
    [debug] Debug messages enabled
    [debug] Loaded plugins:
    .----------------------------------------------------------------------------.
    | Catalyst::Plugin::ConfigLoader  0.13                                       |
    | Catalyst::Plugin::Static::Simple  0.14                                     |
    '----------------------------------------------------------------------------'
    
    [debug] Loaded dispatcher "Catalyst::Dispatcher"
    [debug] Loaded engine "Catalyst::Engine::HTTP"
    [debug] Found home "/home/me/MyApp"
    [debug] Loaded Config "/home/me/myapp.yml"
    [debug] Loaded components:
    .-----------------------------------------------------------------+----------.
    | Class                                                           | Type     |
    +-----------------------------------------------------------------+----------+
    | MyApp::Controller::Root                                         | instance |
    '-----------------------------------------------------------------+----------'
    
    [debug] Loaded Private actions:
    .----------------------+--------------------------------------+--------------.
    | Private              | Class                                | Method       |
    +----------------------+--------------------------------------+--------------+
    | /default             | MyApp::Controller::Root              | default      |
    | /end                 | MyApp::Controller::Root              | end          |
    '----------------------+--------------------------------------+--------------'
    
    [info] MyApp powered by Catalyst 5.7002
    You can connect to your server at http://localhost:3000

ANMERKUNG : Es sollte sicher gestellt werden, dass der Befehl script/myapp_server.pl aus dem Basisverzeichniss der Applikation heraus aufgrufen wird und nicht aus dem Verzeichnis script . Momentan macht dies noch keinen Unterschied, aber sobald wir eine Datenbank einbinden, wird das wichtig.

Wenn der Webbrowser nun http://localhost:3000 (passen Sie den Hostnamen bzw. IP-Adresse entsprechend an) aufruft, sollte er den Begrüßungsbildschirm von Catalyst darstellen. Der Entwicklungsserver sollte Logmeldungen ähnlich den folgenden ausgeben:

    [info] *** Request 1 (0.043/s) [6003] [Fri Jul  7 13:32:53 2006] ***
    [debug] "GET" request for "/" from "127.0.0.1"
    [info] Request took 0.067675s (14.777/s)
    .----------------------------------------------------------------+-----------.
    | Action                                                         | Time      |
    +----------------------------------------------------------------+-----------+
    | /default                                                       | 0.002844s |
    | /end                                                           | 0.000207s |
    '----------------------------------------------------------------+-----------'

Mit Strg-C lässt sich der Entwicklungsserver stoppen.

ERSTELLEN EINER SQLITE DATENBANK

In diesem Schritt wird eine Textdatei mit den SQL-Befehlen erstellt, die notwendig sind, um eine Datenbanktabelle zu erstellen und mit einigen Beispieldaten zu füllen. Öffnen Sie myapp01.sql in Ihrem Editor und geben Sie folgendes ein:

    --
    -- Eine sehr einfache Datenbank für Buch und Autoreninformationen
    --
    CREATE TABLE books (
            id          INTEGER PRIMARY KEY,
            title       TEXT ,
            rating      INTEGER
    );
    -- 'book_authors' ist eine many-to-many join table zwischen books & authors
    CREATE TABLE book_authors (
            book_id     INTEGER,
            author_id   INTEGER,
            PRIMARY KEY (book_id, author_id)
    );
    CREATE TABLE authors (
            id          INTEGER PRIMARY KEY,
            first_name  TEXT,
            last_name   TEXT
    );
    ---
    --- Einige Beispieldaten einfügen
    ---
    INSERT INTO books VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
    INSERT INTO books VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
    INSERT INTO books VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
    INSERT INTO books VALUES (4, 'Perl Cookbook', 5);
    INSERT INTO books VALUES (5, 'Designing with Web Standards', 5);
    INSERT INTO authors VALUES (1, 'Greg', 'Bastien');
    INSERT INTO authors VALUES (2, 'Sara', 'Nasseh');
    INSERT INTO authors VALUES (3, 'Christian', 'Degu');
    INSERT INTO authors VALUES (4, 'Richard', 'Stevens');
    INSERT INTO authors VALUES (5, 'Douglas', 'Comer');
    INSERT INTO authors VALUES (6, 'Tom', 'Christiansen');
    INSERT INTO authors VALUES (7, 'Nathan', 'Torkington');
    INSERT INTO authors VALUES (8, 'Jeffrey', 'Zeldman');
    INSERT INTO book_authors VALUES (1, 1);
    INSERT INTO book_authors VALUES (1, 2);
    INSERT INTO book_authors VALUES (1, 3);
    INSERT INTO book_authors VALUES (2, 4);
    INSERT INTO book_authors VALUES (3, 5);
    INSERT INTO book_authors VALUES (4, 6);
    INSERT INTO book_authors VALUES (4, 7);
    INSERT INTO book_authors VALUES (5, 8);

TIP : Siehe Anhang 1 für Tips bzgl. des Entfernens führender Leerzeichen bei Cut&Paste von Beispielcode aus POD-formatierten Dokumenten.

Verwenden Sie folgenden Befehl um die SQLite Datenbank myapp.db zu erstellen:

    $ sqlite3 myapp.db < myapp01.sql

Falls es notwendig werden sollte, die Datenbank mehrfach zu erstellen, sollte der Befehl rm myapp.db verwendet werden, um die Datenbank zu löschen bevor sie mit sqlite3 myapp.db < myapp01.sql wieder neu erstellt wird.

Sobald die Datenbankdatei myapp.db erstellt wurde, kann das Komandozeilentool von SQLite verwendet werden, um eine kurze Ausgabe des Datenbankinhalts auszugeben:

    $ sqlite3 myapp.db
    SQLite version 3.2.2
    Enter ".help" for instructions
    sqlite> select * from books;
    1|CCSP SNRS Exam Certification Guide|5
    2|TCP/IP Illustrated, Volume 1|5
    3|Internetworking with TCP/IP Vol.1|4
    4|Perl Cookbook|5
    5|Designing with Web Standards|5
    sqlite> .q
    $

Oder:

    $ sqlite3 myapp.db "select * from books"
    1|CCSP SNRS Exam Certification Guide|5
    2|TCP/IP Illustrated, Volume 1|5
    3|Internetworking with TCP/IP Vol.1|4
    4|Perl Cookbook|5
    5|Designing with Web Standards|5

So wie bei den meisten anderen SQL-Tools auch, muss in der voll "interaktiven" Umgebung jeder SQL-Befehl mit einem ";" abgeschlossen werden (dies ist nicht erforderlich, wenn ein einzelner SQL-Befehl über die Komandozeile ausgeführt wird). Mit ".q" verlässt man die interaktive Umgebung von SQLite und kehrt zur Eingabeaufforderung des Bestriebssystems zurück.

ÄNDERN DER LISTE DER CATAYST-PLUGINS

Eines der größten Vorteile von Catalyst ist, dass es über eine große Bibliothek von Plugins verfügt. Plugins werden verwendet, um bestehende Perlmodule natlos in das Catalyst Framework einzufügen. Normalerweise wird dies getan, indem weitere Methoden zum context -Objekt (normalerweise als $c geschieben) hinuzgefügt werden, welches Catalyst an jede Komponente des Frameworks weiter gibt.

Standartmäßig aktiviert Catalyst drei Plugins/Flags:

  • -Debug Flag

    Aktiviert die Catalyst Debugausgabe die beim Start des script/myapp_server.pl Entwicklungsservers zu sehen war. Dieses Plugin kann deaktiviert werden, wenn die Applikation in den Produktionsbetrieb überführt wird.

    Wie Sie vieleicht bemerkt haben ist -Debug kein Plugin sondern ein Flag . Auch wenn die meisten Elemente, die auf der use Catalyst -Zeile deklariert werden Plugins sind, bietet Catalyst eine begrenzte Anzahl an Flag-Optionen (unter denen -Debug die gebräuclichste ist). In der Dokumentation von Catalyst.pm sind die anderen Flags (momentan -Engine , -Home , und -Log ) detailiert beschrieben.

    Falls bevorzugt, können die Debug-Meldungen auch über die Funktion $c->debug aktiviert werden.

  • Catalyst::Plugin::ConfigLoader?

    ConfigLoader stellt ein automatisiertes Verfahren zur Verfügung, welches das Laden von Konfigurationsparametern für die Anwendung aus einer einzelnen YAML-Datei ermöglicht (als Alternative zu der Notwendigkeit, alle Werte hart in die Permodule zu programmieren). Falls YAML noch unbekannt sein sollte, sei hier kurz erklärt, dass es sich dabei um ein für Menschen lesabres Format zur Serialisierung von Daten handelt, welches von/zu Dateien gelesen/geschrieben werden kann. Dieses Feature von Catalyst wird während der Kapitel zu Authentifizierung und Autorisierung näher behandelt (Kapitel 4 und 5).

  • Catalyst::Plugin::Static::Simple?

    Static::Simple stellt eine einfache Methode dar, um statische Inhalte wie Bilder oder CSS-Dateien mit dem Entwicklungsserver zur Verfügung zu stellen.

Um die Liste der Plugins anzupassen, muss lib/MyApp.pm (diese Datei wird oft auch Applikationsklasse genannt) editiert werden, indem die folgende Zeile gelöscht wird:

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

Sie wir durch folgendes ersetzt:

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

Dies weist Catalyst an, ein neues Plugin zu verwenden:

  • Catalyst::Plugin::StackTrace?

    Dies fügt einen Stacktrace zu standart Debug Screen von Catalyst hinzu (dabei handelt es sich um den Bildschirm, der im Webbrowser angezeigt wird, wenn ein Error auftritt).

    Anmerkung: Die Ausgabe von StackTrace? wird im Webbrowser angezeit, nicht im Konsolenfenster, in dem die Applikation läuft. Dort werden normalerweise Logmeldungen ausgegeben.

Wenn Plugins auf der use Catalyst Zeile konfiguriert werden, kann der Prefix Catalyst::Plugin:: weggelassen werden. Außerdem können die Pluginnamen über mehere Zeilen verteilt werden, wie oben gezeigt, oder sie können alle in einer Zeile (oder mehr) geschrieben werden, wie in der Standartkonfiguration.

TIP: Evtl. sind noch Beispiele bekannt, die das Catalyst::Plugin::DefaultEnd? Plugin enthalten. Seit Catalyst 5.7000 wurde DefaultEnd von Catalyst::Action::RenderView? abgelöst (wie der Name des Pakets nahe legt, handelt es sich bei RenderView nicht um ein Plugin, sonder um eine Action). Der Zweck der beiden Pakete ist grundsätzlich der selbe: die Verarbeitung zu der View weiter zu leiten, die angezeigt werden soll. Applikationen, die unter 5.7000 erstellt wurden, sollten automatisch RenderView verwenden und in den meisten fällen "einfach funktionieren". Mehr Informationen über RenderView und die vielfältigen Optionen, die es für das Weiterleiten zur View bietet, sind im Teil "Die Verwendung von RenderView als Standartview" weiter unten unter "CATALYST VIEWS" zu finden.

DATENBANKZUGRIFF MIT DBIx::Class

Catalyst kann mit so ziemlich jeder Art von persistentem Datenspeicher benutzt werden, der von Perl aus erreichbar ist. Zum Beispiel kann Catalyst::Model::DBI? benutzt werden, um auf Datenbanken über das bewährte Perlmodul DBI zuzugrifen. Allerdings werden die meisten Catalyst Applikationen irgend eine Art von ORM-Technologie verwenden, um Modelobjekte automatisch zu erstellen und zu speichern sobald diese verwendet werden. Auch wenn Tony Bowdens Class::DBI? die etablierteste ORM-Engine war, hat sich Matt Trouts DBIx::Class? (abgekürzt "DBIC") schnell als perlbasierte ORM-Technologie erster Wahl hervorgetan. Die meisten Catalyst Applikationen vertrauen auf DBIC, so auch dieses Tutorial.

Anmerkung: Mehr Informationen bzgl. der Verwendung von Class::DBI? unter Catalyst sind unter Catalyst::Model::CDBI? zu finden.

Eine Schemadatei für DBIC erstellen

DBIx::Class verwendet eine Schemadatei, um Klassen zu laden welche die Tabelle der Datenbank repräsentieren (DBIC bezeichnet diese "Tabellenobjekte" als "result sources" (Ergebnisquellen); siehe DBIx::Class::ResultSource?). In diesem Fall werden Objekte für die Tabellen books , book_authors , und authors , welche im vorangegangenen Teil erstellt wurden, geladen.

Erstellen Sie lib/MyAppDB.pm in Ihrem Edtior und fügen Sie folgendes ein:

    package MyAppDB;
    
    =head1 NAME 
    
    MyAppDB - DBIC Schema Klasse
    
    =cut
    
    # Unser Schema sollte von 'DBIx::Class::Schema' vererbt sein
    use base qw/DBIx::Class::Schema/;
    
    # An dieres Stelle muss die DB Modelklasse geladen werden.
    # Falls gewünscht kann folgende Syntax verwendet werden:
    #    __PACKAGE__->load_classes(qw/Book BookAuthor Author/);
    # Außerdem kann folgendes verwendet werden, wenn lediglich alle Klassen
    # im Verzeichnis des selben Namens wie die Schemaklasse geladen 
    # werden sollen (was hier der Fall ist): 
    #    __PACKAGE__->load_classes(qw//);
    # Aber die folgende Variante ist flexibler, da sie verwendet werden 
    # kann, um Klassen aus unterschiedlichen Namensräumen zu laden.
    __PACKAGE__->load_classes({
        MyAppDB => [qw/Book BookAuthor Author/]
    });
    
    1;

Anmerkung: __PACKAGE__ ist lediglich ein schneller weg, um den Namen der aktuellen Pakets anzugeben. Daher entspricht innerhalb von MyAppDB.pm __PACAKGE__ MyAppDB .

Anmerkung: Wie in jedem Perlpaket, muss die letzte Zeile mit einem Ausdruck abgeschlossen werden, welches wahr zurück gibt. Dies wird normalerweise mit einer einzelnen 1 in der Zeile gemacht, wie oben zu sehen.

Erstellen der DBIC "Result Source" Dateien

In diesem Schritt werden die Tabellenklassen (nochmals, diese werden in DBIC "result sources" genannt) erstellt, welche als Modelobjekte für die Tabellen books , book_authors , und authors der Datenbank verwendet werden.

Als erstes erstellen Sie ein Verzeichnis, welches die Klassen beinhaltet:

    $ mkdir lib/MyAppDB

Dann erstellen Sie lib/MyAppDB/Book.pm im Editor und geben folgendes ein:

    package MyAppDB::Book;
    
    use base qw/DBIx::Class/;  
    
    # Den benötighten DBIC-Kram laden
    __PACKAGE__->load_components(qw/PK::Auto Core/);
    # Setzen des Tabellennamens
    __PACKAGE__->table('books');
    # Die Spaltennamen in der Tabelle angeben
    __PACKAGE__->add_columns(qw/id title rating/);
    # Den primary key der Tabelle angeben
    __PACKAGE__->set_primary_key(qw/id/);
    
    #
    # Beziehungen festlegen:
    #
    
    # has_many():
    #   Argumente:
    #     1) Name der Beziehung, DBIC wird einen Accessor mit diesem Namen 
    #        erstellen
    #     2) Name der Modelklasse auf die sich die Beziehung bezieht
    #     3) Spaltenname in der *fremden* Tabelle
    __PACKAGE__->has_many(book_authors => 'MyAppDB::BookAuthor', 'book_id');
    
    # many_to_many():
    #   Argumente:
    #     1) Name der Beziehung, DBIC wird einen Accessor mit diesem Namen 
    #        erstellen
    #     2) Name der has_many() Beziehung, für die diese many_to_many() 
    #        eine Abkürzung ist
    #     3) Name der belongs_to() Beziehung in der Modelklasse des 
    #        has_many() oben
    # has_many() muss bereits deklariert sein, bevor many_to_many() benutzt 
    # werden kann.
    __PACKAGE__->many_to_many(authors => 'book_authors', 'author');
    
    
    =head1 NAME
    
    MyAppDB::Book - Ein Modelobjekt welches ein Buch repräsentiert.
    
    =head1 BESCHREIBUNG
    
    Dies ist ein Objekt, welches die Tabelle 'books' in der Datenbank der 
    Applikation repäsentiert. Es verwendet DBIx::Class (alias DBIC) für
    ORM.

    Es wurde so geschrieben, dass Catalyst über MyApp::Model::MyAppDB darauf 
    zugreifen kann. Andere Scripte möchten diese Klasse wahrscheinlich 
    direkt benutzen.
    
    =cut
    
    1;

Dies definiert sowohl eine has_many als auch eine many_to_many Beziehung. Die many_to_many Beziehung ist optional, aber sie macht es einfacher, alle Autoren eines Buches aufzurufen. Ohne sie, müssten wir die gesamte Tabelle book_authors durchlaufen, etwa so: $book->book_authors->first->author->last_name (wir werden uns bald Beispiele anschauen, wie DBIC-Objekte im Quellcode verwendet werden. Es sei nur darauf hingewiesen, dass $book->book_authors mehrere Autoren zurückgeben kann, daher muss first benutzt werden, um einen einzelnen Autor darzustellen). many_to_many erlaubt es, das kürzere $book->authors->first->last_name zu verwenden. Um eine many_to_many Beziehung zu deifinieren, muss vorher immer schon eine has_many Beziehung existieren.

Als nächstes, erstelle lib/MyAppDB/Author.pm mit folgendem Inhalt:

    package MyAppDB::Author;
    
    use base qw/DBIx::Class/;
    
    # Den benötighten DBIC-Kram laden 
    __PACKAGE__->load_components(qw/PK::Auto Core/);
    # Setzen des Tabellennamens
    __PACKAGE__->table('authors');
    # Die Spaltennamen in der Tabelle angeben
    __PACKAGE__->add_columns(qw/id first_name last_name/);
    # Den primary key der Tabelle angeben
    __PACKAGE__->set_primary_key(qw/id/);
    
    #
    # Beziehungen festlegen:
    #
    
    # has_many():
    #   Argumente:
    #     1) Name der Beziehung, DBIC wird einen Accessor mit diesem Namen 
    #        erstellen
    #     2) Name der Modelklasse auf die sich die Beziehung bezieht
    #     3) Spaltenname in der *fremden* Tabelle
    __PACKAGE__->has_many(book_author => 'MyAppDB::BookAuthor', 'author_id');
    
    # many_to_many():
    #   Argumente:
    #     1) Name der Beziehung, DBIC wird einen Accessor mit diesem Namen 
    #        erstellen
    #     2) Name der has_many() Beziehung, für die diese many_to_many() 
    #        eine Abkürzung ist
    #     3) Name der belongs_to() Beziehung in der Modelklasse des 
    #        has_many() oben
    # has_many() muss bereits deklariert sein, bevor many_to_many() benutzt 
    # werden kann.
    __PACKAGE__->many_to_many(books => 'book_author', 'book');
    
    
    =head1 NAME
    
    MyAppDB::Author - Ein Modelobjekt welches den Autor eines Buches 
    repräsentiert (wenn ein Buch mehrere Autoren hat, wird jeder durch 
    ein eigenes Objekt dargestellt).
    
    =head1 BESCHREIBUNG
    
    Dies ist ein Objekt, welches die Tabelle 'authors' in der Datenbank der 
    Applikation repäsentiert. Es verwendet DBIx::Class (alias DBIC) für
    ORM.

    Es wurde so geschrieben, dass Catalyst über MyApp::Model::MyAppDB darauf 
    zugreifen kann. Andere Scripte möchten diese Klasse wahrscheinlich 
    direkt benutzen.

    =cut
    
    1;

Abschließend erstellen Sie lib/MyAppDB/BookAuthor.pm mit folgendem Inhalt:

    package MyAppDB::BookAuthor;
    
    use base qw/DBIx::Class/;
    
    # Den benötighten DBIC-Kram laden
    __PACKAGE__->load_components(qw/PK::Auto Core/);
    # Setzen des Tabellennamens
    __PACKAGE__->table('book_authors');
    # Die Spaltennamen in der Tabelle angeben
    __PACKAGE__->add_columns(qw/book_id author_id/);
    # Den primary key der Tabelle angeben
    __PACKAGE__->set_primary_key(qw/book_id author_id/);
    
    #
    # Beziehungen festlegen:
    #
    
    # belongs_to():
    #   Argumente:
    #     1) Name der Beziehung, DBIC wird einen Accessor mit diesem Namen 
    #        erstellen
    #     2) Name der Modelklasse auf die sich die Beziehung bezieht
    #     3) Spaltenname in der *dieser* Tabelle
    __PACKAGE__->belongs_to(book => 'MyAppDB::Book', 'book_id');
    
    # belongs_to():
    #   Argumente:
    #     1) Name der Beziehung, DBIC wird einen Accessor mit diesem Namen 
    #        erstellen
    #     2) Name der Modelklasse auf die sich die Beziehung bezieht
    #     3) Spaltenname in der *dieser* Tabelle
    __PACKAGE__->belongs_to(author => 'MyAppDB::Author', 'author_id');
    
    
    =head1 NAME
    
    MyAppDB::BookAuthor - Ein Modelobjekt, welches die Beziehung zwischen 
    einem Autor und einem Buch repräsentiert
    
    =head1 BESCHREIBUNG
    
    Dies ist ein Objekt, welches eine Zeile in der Tabelle 'authors' der 
    Applikationsdatenbank repäsentiert. Es verwendet DBIx::Class (alias 
    DBIC) für ORM.
    
    Es wird wahrscheinlich nicht notwendig sein, diese Klasse direkt zu 
    benutzen, da es automatisch von DBIC verwendet wird, wenn Joins 
    erforderlich werden.
    
    Es wurde so geschrieben, dass Catalyst über MyApp::Model::MyAppDB darauf 
    zugreifen kann. Andere Scripte möchten diese Klasse wahrscheinlich 
    direkt benutzen.
    
    =cut
    
    1;

Anmerkung: Diese Beispielapplikation verwendet den Plural für Tabellennamen in der Datenbank (z.B. books und authors ) und den Singular für Modelobjekte (z.B. Book und Author ). Catalyst selbst setzt aber keine Beshcränkungen, nach welchem Schema Namen vergeben werden.

Modelklassen mit Catalyst::Model::DBIC::Schema laden

Sobald Catalyst::Model::DBIC::Schema? verwendet wird, liest Catalyst das bestehende Datenbankmodel ein und erstellt einen neuen Satz von Objekten unter MyApp::Model .

Anmerkung: Wenn Catalyst::Model::DBIC::Schema? verwendet wird, kommen am Ende zwei unterschiedliche Sätze von Modellklassen dabei heraus (nur einer davon wird vom Programmierer erstellt, der andere wird automatisch im Speicher erstellt, sobald die Catalyst Appliaktion initialisiert wird). Für dieses Tutorial ist es wichtig im Hinterkopf zu behalten, dass die Dateien in MyAppDB eine Ergebnissquelle sind, innerhalb von Catalyst wird aber die automatisch generierte Modellklasse unter MyApp:Model verwendet.

Mit dem Helper Script von Catalyst::Helper::Model::DBIC::Schema? wird nun die Modellklasse erstellt, die das Modell läd, welches im vorherigen Schritt erstellt wurde:

    $ script/myapp_create.pl model MyAppDB DBIC::Schema MyAppDB dbi:SQLite:myapp.db '' '' '{ AutoCommit => 1 }'
     exists "/root/dev/MyApp/script/../lib/MyApp/Model"
     exists "/root/dev/MyApp/script/../t"
    created "/root/dev/MyApp/script/../lib/MyApp/Model/MyAppDB.pm"
    created "/root/dev/MyApp/script/../t/model_MyAppDB.t"

Das erste MyAppDB ist der Name der Klasse die von dem Helper in lib/MyApp/Model erstellt wird, während das zweite MyAppDB der Name der bereits existierenden Schema-Datei ist ( lib/MyAppDB.pm ). Der Helper erstellt nun eine neue Modell-Datei unter lib/MyApp/Model (Catalyst hat eigene Verzeichnisse unter lib/MyApp für jeden der drei Teile von MVC: Model , View und Controller [bei älteren Catalyst Anwendungen werden oft auch die Verzeichnisse M , V , und C verwendet]).

ERSTELLEN EINES CATALYST CONTROLLERS

Innerhalb von Controllern befinden sich Methoden, die mit den Benutzereingaben interagieren. Normalerweise regaieren Controller auf GET und POST Anfragen vom Webbrowser des Benutzers.

Erstellen Sie nun mit Hilfe des dem Catalysts create -Scripts einen Controller für buchbezogene Aktionen.

    $ script/myapp_create.pl controller Books
     exists "/root/dev/MyApp/script/../lib/MyApp/Controller"
     exists "/root/dev/MyApp/script/../t"
    created "/root/dev/MyApp/script/../lib/MyApp/Controller/Books.pm"
    created "/root/dev/MyApp/script/../t/controller_Books.t"

Editieren Sie nun lib/MyApp/Controller/Books.pm und fügen Sie die folgende Methode dem Controller hinzu:

    =head2 list
    
    Hole alle Buchobjekte und reiche sie im Stash für die Anzeige an 
    books/list.tt2 weiter.
    
    =cut
     
    sub list : Local {
        # Hole das übliche Perl OO '$self' für dieses Objekt. $s ist der 
        # Catalyst Kontext, der verwendet wird, um die verschiedenen Teile 
        # der Applikation miteinander arbeiten und kommunizieren zu lassen.
        my ($self, $c) = @_;
    
        # Hole alle Bucheinträge als Buch-Modellobjekte und speichere sie 
        # im Stash, damit das TT-Template darauf zugreifen kann.
        $c->stash->{books} = [$c->model('MyAppDB::Book')->all];
        
        # Definiere das zu verwendende TT-Template. Dies will man fast immer 
        # in den "action methods" machen (action methods reagieren auf 
        # die Benutzereingaben in den Controllern).
        $c->stash->{template} = 'books/list.tt2';
    }

Anmerkung: Programmierer, die mit Objekt-orientiertem Perl vertraut sind, dürften $self als eine Referenz wiedererkennen, die auf das Objekt weist, von dem aus die Methode aufgerufen wurde. $c wird aber für viele Perl-Programmierer die noch nie mit Catalyst gearbeitet haben neu sein (es wird manchmal auch als $context geschieben). Das Kontextobjekt wird automatisch an alle Cataylst Komponenten weitergegeben. Es wird verwendet, um Informationen zwischen einzelnen Catalyst Komponenten auszutauschen und Zugriff auf geladene Pugins und auf Catalyst selbst zu gewähren.

TIP : Das oben verwendete $c->model('MyAppDB::Book') wird manchmal auch als $c->model('MyAppDB')->resultset('Book) geschieben. Die beiden Schreibweisen sind equivalent.

Anmerkung: Catalyst Aktionen sind normale Perl Methoden, aber sie verwenden Nicolas Clarks attributes Modul (das ist das : Local neben dem sub list in obigem Code) um zusätzliche Informationen für die Catalyst Dispatcher-Logik bereit zu stellen.

CATALYST VIEWS

Views are where you render output, typically for display in the user's web browser, but also possibly using other display output-generation systems. As with virtually every aspect of Catalyst, options abound when it comes to the specific view technology you adopt inside your application. However, most Catalyst applications use the Template Toolkit, known as TT (for more information on TT, see http://www.template-toolkit.org). Other popular view technologies include Mason (http://www.masonhq.com and http://www.masonbook.com) and HTML::Template? (http://html-template.sourceforge.net).

Create a Catalyst View Using TTSite

When using TT for the Catalyst view, there are two main helper scripts:

  • Catalyst::Helper::View::TT?

  • Catalyst::Helper::View::TTSite?

Both are similar, but TT merely creates the lib/MyApp/View/TT.pm file and leaves the creation of any hierarchical template organization entirely up to you. (It also creates a t/view_TT.t file for testing; test cases will be discussed in Part 7). The TTSite helper creates a modular and hierarchical view layout with separate Template Toolkit (TT) files for common header and footer information, configuration values, a CSS stylesheet, and more.

While TTSite is useful to bootstrap a project, we recommend that unless you know what your're doing or want to pretty much use the supplied templates as is, that you use the plain Template Toolkit view when starting a project from scratch. This is because TTSite can be tricky to customize. Additionally TT contains constructs that you need to learn yourself if you're going to be a serious user of TT. Our experience suggests that you're better off learning these from scratch. We use TTSite here precisely because it is useful for bootstrap/prototype purposes.

Enter the following command to enable the TTSite style of view rendering for this tutorial:

    $ script/myapp_create.pl view TT TTSite
     exists "/root/dev/MyApp/script/../lib/MyApp/View"
     exists "/root/dev/MyApp/script/../t"
    created "/root/dev/MyApp/script/../lib/MyApp/View/TT.pm"
    created "/root/dev/MyApp/script/../root/lib"
    ...
    created "/root/dev/MyApp/script/../root/src/ttsite.css"

This puts a number of files in the root/lib and root/src directories that can be used to customize the look and feel of your application. Also take a look at lib/MyApp/View/TT.pm for config values set by the TTSite helper.

TIP : Note that TTSite does one thing that could confuse people who are used to the normal TT Catalyst view: it redefines the Catalyst context object in templates from its usual c to Catalyst . When looking at other Catalyst examples, remember that they almost always use c . Note that Catalyst and TT do not complain when you use the wrong name to access the context object...TT simply outputs blanks for that bogus logic (see next tip to change this behavior with TT DEBUG options). Finally, be aware that this change in name only applies to how the context object is accessed inside your TT templates; your controllers will continue to use $c (or whatever name you use when fetching the reference from @_ inside your methods). (You can change back to the "default" behavior be removing the CATALYST_VAR line from lib/MyApp/View/TT.pm , but you will also have to edit root/lib/config/main and root/lib/config/url . If you do this, be careful not to have a collision between your own c variable and the Catalyst c variable.)

TIP : When troubleshooting TT it can be helpful to enable variable DEBUG options. You can do this in a Catalyst environment by adding a DEBUG line to the __PACKAGE__- config> declaration in lib/MyApp/View/TT.pm :

    __PACKAGE__->config({
        CATALYST_VAR => 'Catalyst',
        ...
        DEBUG        => 'undef',
        ...
    });

There are a variety of options you can use, such as 'undef', 'all', 'service', 'context', 'parser', 'provider', and 'service'. See Template::Constants? for more information (remove the DEBUG_ portion of the name shown in the TT docs and convert to lower case for use inside Catalyst).

NOTE: Please be sure to disable TT debug options before continuing the tutorial (especially the 'undef' option -- leaving this enabled will conflict with several of the conventions used by this tutorial and TTSite to leave some variables undefined on purpose).

Die Verwendung von RenderView als Standartview

Once your controller logic has processed the request from a user, it forwards processing to your view in order to generate the appropriate response output. Catalyst v5.7000 ships with a new mechanism, Catalyst::Action::RenderView?, that automatically performs this operation. If you look in lib/MyApp/Controller/Root.pm , you should see the empty definition for the sub end method:

    sub end : ActionClass('RenderView') {}

The following bullet points provide a quick overview of the RenderView process:

  • Root.pm is designed to hold application-wide logic.

  • At the end of a given user request, Catalyst will call the most specific end method that's appropriate. For example, if the controller for a request has an end method defined, it will be called. However, if the controller does not define a controller-specific end method, the "global" end method in Root.pm will be called.

  • Because the definition includes an ActionClass attribute, the Catalyst::Action::RenderView? logic will be executed after any code inside the definition of sub end is run. See Catalyst::Manual::Actions? for more information on ActionClass .

  • Because sub end is empty, this effectively just runs the default logic in RenderView . However, you can easily extend the RenderView logic by adding your own code inside the empty method body ( {} ) created by the Catalyst Helpers when we first ran the catalyst.pl to initialize our application. See Catalyst::Action::RenderView? for more detailed information on how to extended RenderView in sub end .

The History Leading Up To RenderView

Although RenderView strikes a nice balance between default behavior and easy extensibility, it is a new feature that won't appear in most existing Catalyst examples. This section provides some brief background on the evolution of default view rendering logic with an eye to how they can be migrated to RenderView :

  • Private end Action in Application Class

    Older Catalyst-related documents often suggest that you add a "private end action" to your application class ( MyApp.pm ) or Root.pm ( MyApp/Controller/Root.pm ). These examples should be easily converted to RenderView? by simply adding the attribute :ActionClass('RenderView') to the sub end definition. If end sub is defined in your application class ( MyApp.pm ), you should also migrate it to MyApp/Controller/Root.pm .

  • Catalyst::Plugin::DefaultEnd?

    DefaultEnd represented the "next step" in passing processing from your controller to your view. It has the advantage of only requiring that DefaultEnd be added to the list of plugins in lib/MyApp.pm . It also allowed you to add "dump_info=1" (precede with "?" or "&" depending on where it is in the URL) to force the debug screen at the end of the Catalyst request processing cycle. However, it was more difficult to extend than the RenderView mechanism, and is now deprecated.

  • Catalyst::Action::RenderView?

    As discussed above, the current recommended approach to handling your view logic relies on Catalyst::Action::RenderView?. Although similar in first appearance to the "private end action" approach, it utilizes Catalyst's "ActionClass" mechanism to provide both automatic default behavior (you don't have to include a plugin as with DefaultEnd ) and easy extensibility. As with DefaultEnd , it allows you to add "dump_info=1" (precede with "?" or "&" depending on where it is in the URL) to force the debug screen at the end of the Catalyst request processing cycle.

It is recommended that all Catalyst applications use or migrate to the RenderView approach.

Globally Customize Every View

When using TTSite, files in the subdirectories of root/lib can be used to make changes that will appear in every view. For example, to display optional status and error messages in every view, edit root/lib/site/layout , updating it to match the following (the two HTML span elements are new):

    <div id="header">[% PROCESS site/header %]</div>
    
    <div id="content">
    <span class="message">[% status_msg %]</span>
    <span class="error">[% error_msg %]</span>
    [% content %]
    </div>
    
    <div id="footer">[% PROCESS site/footer %]</div>

If we set either message in the Catalyst stash (e.g., $c->stash->{status_msg} = 'Request was successful!' ) it will be displayed whenever any view used by that request is rendered. The message and error CSS styles are automatically defined in root/src/ttsite.css and can be customized to suit your needs.

Note: The Catalyst stash only lasts for a single HTTP request. If you need to retain information across requests you can use Catalyst::Plugin::Session? (we will use Catalyst sessions in the Authentication part of the tutorial).

Create a TT Template Page

To add a new page of content to the TTSite view hierarchy, just create a new .tt2 file in root/src . Only include HTML markup that goes inside the HTML <body> and </body> tags, TTSite will use the contents of root/lib/site to add the top and bottom.

First create a directory for book-related TT templates:

    $ mkdir root/src/books

Then create root/src/books/list.tt2 in your editor and enter:

    [% # This is a TT comment.  The '-' at the end "chomps" the newline.  You won't -%]
    [% # see this "chomping" in your browser because HTML ignores blank lines, but  -%]
    [% # it WILL eliminate a blank line if you view the HTML source.  It's purely   -%]
    [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
    
    [% # Provide a title to root/lib/site/header -%]
    [% META title = 'Book List' -%]
    
    <table>
    <tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr>
    [% # Display each book in a table row %]
    [% FOREACH book IN books -%]
      <tr>
        <td>[% book.title %]</td>
        <td>[% book.rating %]</td>
        <td>
          [% # First initialize a TT variable to hold a list.  Then use a TT FOREACH -%]
          [% # loop in 'side effect notation' to load just the last names of the     -%]
          [% # authors into the list.  Note that the 'push' TT vmethod does not      -%]
          [% # a value, so nothing will be printed here.  But, if you have something -%]
          [% # in TT that does return a method and you don't want it printed, you    -%]
          [% # can: 1) assign it to a bogus value, or 2) use the CALL keyword to     -%]
          [% # call it and discard the return value.                                 -%]
          [% tt_authors = [ ];
             tt_authors.push(author.last_name) FOREACH author = book.authors %]
          [% # Now use a TT 'virtual method' to display the author count in parens   -%]
          ([% tt_authors.size %])
          [% # Use another TT vmethod to join & print the names & comma separators   -%]
          [% tt_authors.join(', ') %]
        </td>
      </tr>
    [% END -%]
    </table>

As indicated by the inline comments above, the META title line uses TT's META feature to provide a title to root/lib/site/header . Meanwhile, the outer FOREACH loop iterates through each book model object and prints the title and rating fields. An inner FOREACH loop prints the last name of each author in a comma-separated list within a single table cell.

If you are new to TT, the [% and %] tags are used to delimit TT code. TT supports a wide variety of directives for "calling" other files, looping, conditional logic, etc. In general, TT simplifies the usual range of Perl operators down to the single dot ( . ) operator. This applies to operations as diverse as method calls, hash lookups, and list index values (see http://www.template-toolkit.org/docs/default/Manual/Variables.html for details and examples). In addition to the usual Template module Pod documentation, you can access the TT manual at http://www.template-toolkit.org/docs/default/.

NOTE : The TTSite helper creates several TT files using an extension of .tt2 . Most other Catalyst and TT examples use an extension of .tt . You can use either extension (or no extension at all) with TTSite and TT, just be sure to use the appropriate extension for both the file itself and the $c->stash->{template} = ... line in your controller. This document will use .tt2 for consistency with the files already created by the TTSite helper.

RUN THE APPLICATION

First, let's enable an environment variable option that causes DBIx::Class to dump the SQL statements it's using to access the database (this option can provide extremely helpful troubleshooting information):

    $ export DBIC_TRACE=1

NOTE : You can also use the older export DBIX_CLASS_STORAGE_DBI_DEBUG=1 , but that's a lot more to type.

This assumes you are using BASH as your shell -- adjust accordingly if you are using a different shell (for example, under tcsh, use setenv DBIX_CLASS_STORAGE_DBI_DEBUG 1 ).

NOTE : You can also set this in your code using $class->storage->debug(1); . See DBIx::Class::Manual::Troubleshooting? for details (including options to log to file instead of displaying to the Catalyst development server log).

Then run the Catalyst "demo server" script:

    $ script/myapp_server.pl

Your development server log output should display something like:

    $ script/myapp_server.pl
    [debug] Debug messages enabled
    [debug] Loaded plugins:
    .----------------------------------------------------------------------------.
    | Catalyst::Plugin::ConfigLoader  0.13                                       |
    | Catalyst::Plugin::StackTrace  0.06                                         |
    | Catalyst::Plugin::Static::Simple  0.14                                     |
    '----------------------------------------------------------------------------'
    
    [debug] Loaded dispatcher "Catalyst::Dispatcher"
    [debug] Loaded engine "Catalyst::Engine::HTTP"
    [debug] Found home "/home/me/MyApp"
    [debug] Loaded Config "/home/me/myapp.yml"
    [debug] Loaded components:
    .-----------------------------------------------------------------+----------.
    | Class                                                           | Type     |
    +-----------------------------------------------------------------+----------+
    | MyApp::Controller::Books                                        | instance |
    | MyApp::Controller::Root                                         | instance |
    | MyApp::Model::MyAppDB                                           | instance |
    | MyApp::Model::MyAppDB::Author                                   | class    |
    | MyApp::Model::MyAppDB::Book                                     | class    |
    | MyApp::Model::MyAppDB::BookAuthor                               | class    |
    | MyApp::View::TT                                                 | instance |
    '-----------------------------------------------------------------+----------'
    
    [debug] Loaded Private actions:
    .----------------------+--------------------------------------+--------------.
    | Private              | Class                                | Method       |
    +----------------------+--------------------------------------+--------------+
    | /default             | MyApp::Controller::Root              | default      |
    | /end                 | MyApp::Controller::Root              | end          |
    | /books/index         | MyApp::Controller::Books             | index        |
    | /books/list          | MyApp::Controller::Books             | list         |
    '----------------------+--------------------------------------+--------------'
    
    [debug] Loaded Path actions:
    .-------------------------------------+--------------------------------------.
    | Path                                | Private                              |
    +-------------------------------------+--------------------------------------+
    | /books/list                         | /books/list                          |
    '-------------------------------------+--------------------------------------'
    
    [info] MyApp powered by Catalyst 5.7002
    You can connect to your server at http://localhost:3000

Some things you should note in the output above:

*

Catalyst::Model::DBIC::Schema took our MyAppDB::Book and made it MyApp::Model::MyAppDB::Book (and similar actions were performed on MyAppDB::Author and MyAppDB::BookAuthor ).

*

The "list" action in our Books controller showed up with a path of /books/list .

Point your browser to http://localhost:3000 and you should still get the Catalyst welcome page.

Next, to view the book list, change the URL in your browser to http://localhost:3000/books/list. You should get a list of the five books loaded by the myapp01.sql script above, with TTSite providing the formatting for the very simple output we generated in our template. The count and space-separated list of author last names appear on the end of each row.

Also notice in the output of the script/myapp_server.pl that DBIC used the following SQL to retrieve the data:

    SELECT me.id, me.title, me.rating FROM books me

Along with a list of the following commands to retrieve the authors for each book (the lines have been "word wrapped" here to improve legibility):

    SELECT author.id, author.first_name, author.last_name 
        FROM book_authors me  
        JOIN authors author ON ( author.id = me.author_id ) 
        WHERE ( me.book_id = ? ): `1'

You should see 5 such lines of debug output as DBIC fetches the author information for each book.

USING THE DEFAULT TEMPLATE NAME

By default, Catalyst::View::TT will look for a template that uses the same name as your controller action, allowing you to save the step of manually specifying the template name in each action. For example, this would allow us to remove the $c->stash->{template} = 'books/list.tt2'; line of our list action in the Books controller. Open lib/MyApp/Controller/Books.pm in your editor and comment out this line to match the following (only the $c->stash->{template} line has changed):

    =head2 list
    
    Fetch all book objects and pass to books/list.tt2 in stash to be displayed
    
    =cut
    
    sub list : Local {
        # Retrieve the usual perl OO '$self' for this object. $c is the Catalyst
        # 'Context' that's used to 'glue together' the various components
        # that make up the application
        my ($self, $c) = @_;
    
        # Retrieve all of the book records as book model objects and store in the
        # stash where they can be accessed by the TT template
        $c->stash->{books} = [$c->model('MyAppDB::Book')->all];
    
        # Set the TT template to use.  You will almost always want to do this
        # in your action methods (actions methods respond to user input in
        # your controllers).
        #$c->stash->{template} = 'books/list.tt2';
    }

Catalyst::View::TT defaults to looking for a template with no extension. In our case, we need to override this to look for an extension of .tt2 . Open lib/MyApp/View/TT.pm and add the TEMPLATE_EXTENSION definition as follows:

    __PACKAGE__->config({
        CATALYST_VAR => 'Catalyst',
        INCLUDE_PATH => [
            MyApp->path_to( 'root', 'src' ),
            MyApp->path_to( 'root', 'lib' )
        ],
        PRE_PROCESS  => 'config/main',
        WRAPPER      => 'site/wrapper',
        ERROR        => 'error.tt2',
        TIMER        => 0,
        TEMPLATE_EXTENSION => '.tt2',
    });

You should now be able to restart the development server as per the previous section and access the http://localhost:3000/books/list as before.

NOTE: Please note that if you use the default template technique, you will not be able to use either the $c->forward or the $c->detach mechanisms (these are discussed in Part 2 and Part 8 of the Tutorial).

RETURN TO A MANUALLY-SPECIFIED TEMPLATE

In order to be able to use $c->forward and $c->detach later in the tutorial, you should remove the comment from the statement in sub list :

    $c->stash->{template} = 'books/list.tt2';

Then delete the TEMPLATE_EXTENSION line in lib/MyApp/View/TT.pm .

You should then be able to restart the development server and access http://localhost:3000/books/list in the same manner as with earlier sections.

AUTHOR

Kennedy Clark, hkclark@gmail.com

Please report any errors, issues or suggestions to the author. The most recent version of the Catalyst Tutorial can be found at http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/.

Copyright 2006, Kennedy Clark, under Creative Commons License (http://creativecommons.org/licenses/by-nc-sa/2.5/).

Topic revision: r6 - 2008-04-23 - 15:56:40 - JuergenPeters
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um WebBottomBarExample">Rückmeldung.