Writing Your Application Declaratively

Some History

There have been many shiny new developments in the Perl community of late. With Catalyst's switch to Moose, a whole new world of possibilities was introduced and suddenly available to all Catalyst developers. Another exciting new idea came along with Devel::Declare: the ability to provide a declarative syntax for things that used to be more verbose or repetitious, or just simply to provide an API that goes beyond what the Perl parser itself provides. One of the neater examples that came with the availability of those modules is MooseX::Declare, which allows you to write your classes like this: class SomeClass extends SomeParentClass { has foo => (is => 'rw'); method bar (Int $baz) { $self->foo + $baz } } While it might look like it, this is not a source filter in the sense that we all (hopefully) learned to avoid. The module is still parsed by perl. When for example the class keyword is hit, MooseX::Declare takes over and parses the special syntax. When the { opening brace is found, it gives control back to perl. This has the big advantage that extensions can be safely combined and also be written in Perl itself. In fact, the class and method keywords are handled by separate parts of MooseX::Declare.

What's this got to do with Catalyst?

A very good question! The module CatalystX::Declare is an extension of MooseX::Declare that allows you to write your Catalyst applications declaratively. An example CatalystX::Declare controller would look like this: controller MyApp::Controller::Foo { action base as '' under '/'; final action bar (Int $id) under base { $ctx->stash(object => $ctx->model('Baz')->find($id)); } } The controller keyword is an extension of the class handler from MooseX::Declare. While class (like Moose) will always automatically inherit from Moose::Object when no extends option is provided, controller s will have a default superclass of Catalyst::Controller. The first thing you might notice about the actions is that there are no attributes. Everything is specified with a simple declarative syntax. The first action you see is this: action base as '' under '/'; This is roughly equivalent to the following: sub base: Chained('/') PathPart('') CaptureArgs(0) { } Yes, all actions in CatalystX::Declare are chained actions, since Catalyst::DispatchType::Chained is the most flexible way of designing Catalyst applications (and one that lends itself especially well to declarative syntax). The next action is where it becomes really interesting: final action bar (Int $id) under base { $ctx->stash(object => $ctx->model('Baz')->find($id)); } The final action creates an endpoint chained to the base we declared above. The actions all automatically receive a $ctx context variable in addition to the $self that MooseX::Declare already provides for methods. The interesting part is the (Int $id) signature for the action. The number of positional arguments is used to determine the number of arguments in the public URL. The above chain would run if the resource /bar/23 is hit, and it will only match if its argument is an Int. To expand a bit on the last comment, let me show you an example: controller MyApp::Controller::Foo { # see MooseX::Types use MyApp::Types qw( PageName SequentialID Language ); action base (Language $lang) as '' under '/' { $ctx->stash(language => $lang); } final action show_page (PageName $name) as show under base { $ctx->stash(page => $ctx->model('DB::Page')->find($name)); } final action show_item (SequentialID $id) as show under base { $ctx->stash(item => $ctx->model('DB::Item')->find($id)); } } We assume that Language must be a valid language string (such as en ); PageName must be an identifier in the Perl sense (starts with a letter or underscore, not a digit); and SequentialID is a positive integer. Then, you can hit the above controller with the following: * /en/show/foo This will load base with a $lang of en and then dispatch to show_page with a $name of foo . * /de/show/23 This will first run base like before, but this time with de as $lang argument and then execute show_item with an $id of 23 . Of course you can use any number of arguments, and for endpoints even slurpy arguments are possible: final action show_page (Str @path) as page under base { ... }

Grouping actions by their base

You often have a single action as a base and many actions that chain off of it. If that is the case for you, you can use under as a keyword to group the actions together and save yourself the repetition of the under option: action base as '' under '/'; under base { final action foo { ... } final action bar { ... } } Both foo and bar in the above example will implicitly chain off base .

Declaring other parts of the application

Of course controllers and actions are the most important components for which you'd want to have a declarative syntax. But they are not all that CatalystX::Declare provides you with. You can also declare your application in a shinier syntax: application MyApp with ConfigLoader with Static::Simple { $CLASS->config(name => 'My App'); } There you go; no need to inherit from Catalyst, no need to call setup . And the plugins are nicely specified as roles (which is what plugins are likely to become in the future). So now we have the application, and we already had the C in MVC , but CatalystX::Declare also gives you keywords for the two other letters: model MyApp::Model::DBIC extends Catalyst::Model::DBIC::Schema { method foo (Int $x) { ... } } for models, and view MyApp::View::HTML extends Catalyst::View::TT { after process { ... } } for views. Additionally you can also define controller roles like this: controller_role MyApp::ControllerRole::DefaultBase { action base as '' under '/'; } and you can consume them on the other side like usual in MooseX::Declare based modules: controller MyApp::Controller::Qux with MyApp::ControllerRole::DefaultBase { action view under base { ... } }

Methods and attributes

Since CatalystX::Declare is just an extension of MooseX::Declare you can use its full power to declare methods and attributes as well. Here is an example of a complete root controller with methods and attributes: controller MyApp::Controller::Root { use MooseX::Types::Moose qw( Str ); has home_action => ( is => 'ro', isa => Str, required => 1, default => '/somewhere/else', ); method home_uri (Object $ctx) { return $ctx->uri_for_action($self->home_action); } action app_base as '' under '/'; action end (@) isa RenderView; under base { # matches / final action app_root as '' { $ctx->response->redirect($self->home_uri($ctx)); } # matches /... final action fallback (@) as '' { $ctx->response->status(404); $ctx->response->body('File not found'); } } }

Conclusion

The real hard work for all this shinyness is done by MooseX::Declare. Take a look at CatalystX::Declare on CPAN for a full description of the available syntax, since the above is only a quick tour. I also casually skipped over the use of method modifiers that are applied to actions, which adds even more possibilities. If you want more samples, there is an example application shipped with the distribution at http://cpansearch.perl.org/src/PHAYLON/CatalystX-Declare-0.011/examples/MyApp-Web/.

Author and Copyright