From: Robert 'phaylon' Sedlacek Date: Tue, 13 Mar 2007 12:18:18 +0000 (+0000) Subject: Replaced WritingPlugins with ExtendingCatalyst (phaylon) X-Git-Tag: v5.8005~352 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Manual.git;a=commitdiff_plain;h=380174826ce1498648d5ed8471beb2a666d02ce0 Replaced WritingPlugins with ExtendingCatalyst (phaylon) --- diff --git a/lib/Catalyst/Manual/ExtendingCatalyst.pod b/lib/Catalyst/Manual/ExtendingCatalyst.pod new file mode 100644 index 0000000..d15c8c3 --- /dev/null +++ b/lib/Catalyst/Manual/ExtendingCatalyst.pod @@ -0,0 +1,624 @@ +=head1 NAME + +Catalyst::Manual::ExtendingCatalyst - Extending The Framework + +=head1 DESCRIPTION + +This document will provide you with access points, techniques and best +practices to extend the framework itself or to find more elegant ways +to abstract and use your own code. + +The L core developer community tries to build and evolve the +framework in a design that won't get in your way. There are many entry +points to alter or extend the behaviour of the framework and the +applications built upon it, and it can be confusing at first to decide +on a place to start. This document is written to help you orient +yourself in the possibilities, current practices and their consequences. + +Please read the L section before deciding on a design, +especially if you plan to release your code to the CPAN. The Catalyst +developer and user communities, which you naturally also belong to, will +benefit most if we all work together and coordinate. + +If you are unsure on an implementation or have an idea you would like to +have RfC'ed, it surely is a good idea to send your questions and +suggestions to the Catalyst mailing list (See L) or +come to the C<#catalyst> channel on the C network. You +might also want to refer to those places for research if a module doing +what you're trying to implement already exists. This might give you +either a solution to your problem or a already built base, which gives +you at least a head start. + +=head1 BEST PRACTICES + +During Catalyst's early days, it was common to write plugins to provide +functionality application wide. Since then, Catalyst grew a lot more +flexible and powerful. It soon became a best practice to use some other +form of abstraction or interface, to keep the scope of its influence as +close as possible to where it belongs. + +For those in a hurry, here's a quick checklist of some fundamental +points. If you are going to read the whole thing anyway, you can jump +forward to L. + +=head2 Quick Checklist + +=over + +=item Use the C namespace if you can! + +Excluding plugins and of course your C code. B + +=item Don't make it a plugin unless you have to! + +A plugin should be careful as it declares in global namespace. + +=item There's a community. Use it! + +There are many experienced developers in the Catalyst community, there's +always the IRC channel and the mailing list to discuss things. + +=item Add tests and documentation! + +This gives a stable base for contribution, and even more important, +trust. The easiest way is a test application. See +L for more information. + +=back + +=head2 Namespaces + +While some core extensions (engines, plugins, etc.) have to be placed in +the C namespace, the Catalyst core would like to ask +developers to use the C namespace if possible. + +When you try to put a base class for a C, C or C +directly under your C directory as, for example, +C, you will have the problem that Catalyst will +try to load that base class as a component of your application. The +solution is simple: Use another namespace. Common ones are +C or C as examples. + +=head2 Can it be a simple module? + +Sometimes you want to use functionality in your application that doesn't +require the framework at all. Remember that Catalyst is just Perl and you +always can just C a module. If you have application specific code +that doesn't need the framework, there is no problem in putting it in +your C namespace. Just don't put it in C, C +or C, because that would make Catalyst try to load them as +components. + +=head2 Inheritance and overriding methods + +While Catalyst itself is still based on L, extension developers +are encouraged to use L, which is what Catalyst will be +switching to in some point in the future. + +When overriding a method, keep in mind that some day additionally +arguments may be provided to the method, if the last parameter is not +a flat list. It is thus better to override a method by shifting the +invocant off of C<@_> and assign the rest of the used arguments, so +you can pass your complete arguments to the original method via C<@_>: + + use Class::C3; + ... + + sub foo { + my $self = shift; + my ($bar, $baz) = @_; + # ... + return $self->next::method(@_); + } + +If you would do the common + + my ($self, $foo, $bar) = @_; + +you'd have to use a much uglier construct to ensure that all arguments +will be passed along and the method is future proof: + + $self->next::method(@_[ 1 .. $#_ ]); + +=head2 Tests and documentation + +When you release your module to the CPAN, proper documentation and at +least a basic test suite (which means more than pod or even just +C, sorry) gives people a good base to contribute to the module. +It also shows that you care for your users. If you would like your +module to become a recommended addition, these things will prove +invaluable. + +=head2 Maintenance + +In planning to release a module to a broad community like those of +Catalyst or CPAN and Perl themselves, you should include beforehand if +you can spare the resources to keep it up to date, fix bugs and include +contributions. + +If you're not sure about this, you can always ask in the proper Catalyst +or Perl channels if someone else might be interested in the project, and +would jump in as co-maintainer. + +A public repository can further ease interaction with the community. Even +read only access enables people to provide you with patches to your +current development version. subversion, SVN and SVK, are broadly +preferred in the Catalyst community. + +If you're developing a Catalyst extension, please consider asking the +core team for space in Catalyst's own subversion repository. You can get +in touch about this via IRC or the Catalyst developers mailing list. + +=head2 The context object + +Sometimes you want to get a hold of the context object in a component +that was created on startup time, where no context existed yet. Often +this is about the model reading something out of the stash or other +context information (current language, for example). + +First it should be said that, if you use the context object in your +component and therefor tie it to an existing request, you might get into +problems when you try to use the component (e.g. the model, which would +be the most common case) outside of Catalyst, for example in cronjobs. + +A stable solution to this problem is to design the Catalyst model +separately from the underlying model logic. Let's take +L as an example. You can create a +schema outside of Catalyst that knows nothing about the web. This kind +of design ensures encapsulation and makes development and maintenance +a whole lot easier. The you use the aforementioned model to tie your +schema to your application. This gives you a C (the +name is of course just an example) model as well as +C models to access your result sources +directly. + +By creating such a thin layer between the actual model and the Catalyst +application the schema itself is not at all tied to any application, and +the layer in-between can access the model's API using information from +the context object. + +So the only question remaining is, how does a Catalyst component access +the context object at request time? The solution to this problem is +L. + +=head1 CONFIGURATION + +The application and/or its developer have to interact with the extension +by configuring them. There is of course again more than one way to do it. + +=head2 Attributes + +You can specify any valid Perl attribute on Catalyst actions you like. +(See L for a description of what +is valid.) They will be available on the C instance via +its C accessor. To give an example, this action + + sub foo : Local Bar('Baz') { + my ($self, $c) = @_; + my $attributes = $self->action_for('foo')->attributes; + $c->res->body( $attributes->{Bar}[0] ); + } + +will set the response body to C. The values always come in an array +reference. As you can see, you can use attributes to configure your +actions. You can specify or alter these attributes via +L, or even react on them as soon as Catalyst +encounters them by providing your own +L. + +=head2 Creating custom accessors + +L uses L for accessor +creation. Please refer to the modules documentation for usage +information. + +=head2 Component configuration + +On creation time, the class configuration of your component (the one +available via C<$self-Econfig>) will be merged with possible +configuration settings from the applications configuration (either +directly or via config file) and stored in the controller object's +hash reference. So, if you read possible configurations like + + my $model_name = $controller->{model_name}; + +you will get the right value. The C accessor always only +contains the original class configuration and must not be used for +component configuration. + +You are advised to create accessors on your component class for your +configuration values. This is good practice and makes it easier to +capture configuration key typos. You can do this with the +C method provided to L via +L: + + use base 'Catalyst::Controller'; + __PACKAGE__->mk_ro_accessors('model_name'); + ... + my $model_name = $controller->model_name; + +=head1 IMPLEMENTATION + +This part contains the technical details of various implementation +methods. Please read the L before you start your +implementation, if you haven't already. + +=head2 Action classes + +Usually, your action objects are of the class L. +You can override this with the C attribute to influence +execution and/or dispatching of the action. A popular example is +L, which is used in every newly created +Catalyst application in your root controller: + + sub end : ActionClass('RenderView') { } + +Usually, you want to override either the C or the C +method, or both. The execute method of the action will naturally +call the methods code. You can surround this by overriding the method +in a subclass: + + package Catalyst::Action::MyFoo; + use strict; + + use Class::C3; + use base 'Catalyst::Action'; + + sub execute { + my $self = shift; + my ($controller, $c, @args) = @_; + + # put your 'before' code here + my $r = $self->next::method(@_); + # put your 'after' code here + + return $r; + } + + 1; + +We use L to re-dispatch to the original C method in +the L class. + +When a request comes in, Catalyst's dispatcher searches, depending on +the dispatch type, the target action or chain to invoke. From time to +time it asks the actions themselves, or through the controller, if they +would match the current request. That's what the C method does. +So with overriding this, you can change on what the action will match +and add new matching criteria. + +To give a totally bogus example, this action class will make the action +only match on Mondays: + + package Catalyst::Action::OnlyMondays; + use strict; + + use Class::C3; + use base 'Catalyst::Action'; + + sub match { + my $self = shift; + return 0 if ( localtime(time) )[6] == 1; + return $self->next::method(@_); + } + + 1; + +And this is how we'd use it: + + sub foo: Local ActionClass('OnlyMondays') { + my ($self, $c) = @_; + $c->res->body('I feel motivated!'); + } + +If you are using action classes often or have some specific base classes +that you want to specify more conveniently, you can implement a component +base class providing an attribute handler. + +For further information on action classes, please refer to +L and L. + +=head2 Component base classes + +Many plugins that were written should really have been just controller +base classes. With such a class, you could provide functionality scoped +to a single controller, not polluting the global namespace in the context +object. + +You can provide regular Perl methods in a base class as well as actions +which will be inherited to the subclass. Please refer to L +for an example of this. + +You can introduce your own attributes by specifying a handler method in +the controller base. For example, to use a C attribute to +specify a fully qualified action class name, you could use the following +implementation. Note, however, that this functionality is already +provided via the C<+> prefix for action classes. A simple + + sub foo : Local ActionClass('+MyApp::Action::Bar') { ... } + +will use C as action class. + + package MyApp::Base::Controller::FullClass; + use strict; + use base 'Catalyst::Controller'; + + sub _parse_FullClass_attr { + my ($self, $app_class, $action_name, $value, $attrs) = @_; + return( ActionClass => $value ); + } + + 1; + +Note that the full line of arguments is only provided for completeness +sake. We could use this attribute in a subclass like any other Catalyst +attribute: + + package MyApp::Controller::Foo; + use strict; + use base 'MyApp::Base::Controller::FullClass'; + + sub foo : Local FullClass('MyApp::Action::Bar') { ... } + + 1; + +=head2 Controllers + +Many things can happen in controllers, and it often improves +maintainability to abstract some of the code out into reusable base +classes. + +You can provide usual Perl methods that will be available via your +controller object, or you can even define Catalyst actions which will be +inherited by the subclasses. Consider this controller base class: + + package MyApp::Base::Controller::ModelBase; + use strict; + use base 'Catalyst::Controller'; + + sub list : Chained('base') PathPart('') Args(0) { + my ($self, $c) = @_; + my $model = $c->model( $self->{model_name} ); + my $condition = $self->{model_search_condition} || {}; + my $attrs = $self->{model_search_attrs} || {}; + $c->stash(rs => $model->search($condition, $attrs); + } + + sub load : Chained('base') PathPart('') CaptureArgs(1) { + my ($self, $c, $id) = @_; + my $model = $c->model( $self->{model_name} ); + $c->stash(row => $model->find($id)); + } + + 1; + +This example implements two simple actions. The C action chains to +a (currently non-existent) C action and puts a result-set into the +stash taking a configured C as well as a search condition and +attributes. This action is a L +endpoint. The other action, called C is a chain midpoint that takes +one argument. It takes the value as an ID and loads the row from the +configured model. Please not that the above code is simplified for +clarity. It misses error handling, input validation, and probably some +other things too. + +The class above is not very useful by its own, but we can combine it with +some custom actions by sub-classing it: + + package MyApp::Controller::Foo; + use strict; + use base 'MyApp::Base::Controller::ModelBase'; + + __PACKAGE__->config( + model_name => 'DB::Foo', + model_search_condition => { is_active => 1 }, + model_search_attrs => { order_by => 'name' }, + ); + + sub base : Chained PathPart('foo') CaptureArgs(0) { } + + sub view : Chained('load') Args(0) { + my ($self, $c) = @_; + my $row = $c->stash->{row}; + $c->res->body(join ': ', $row->name, $row->description); + } + + 1; + +This class uses the formerly created controller as a base class. First, +we see the configurations that were used in the parent class. Next comes +the C action, where everything chains off of. + +Note that inherited actions act like they were declared in your +controller itself. You can therefor call them just by their name in +Cs, C and C specifications. This is an +important part of what makes this technique so useful. + +The new C action ties itself to the C action specified in the +base class and outputs the loaded row's C and C +columns. The controller C now has these publicly +available paths: + +=over + +=item /foo + +Will call the controller's C, then the base classes C action. + +=item /foo/$id/view + +First, the controller's C will be called, then it will C the +row with the corresponding C<$id>. After that, C will display some +fields out of the object. + +=back + +=head2 Models and Views + +If the functionality you'd like to add is really a data-set you want to +manipulate, for example internal document types, images, files, it might +be better suited as a model. + +Same goes for views. If your code handles representation or deals with +the applications interface and should be universally available, it could +be a perfect candidate for a view. + +Please implement a C method in your views. This method will be +called by Catalyst if it is asked to forward to a component without a +specified action. Note that C is B but +a simple Perl method. + +You are also encouraged to implement a C method corresponding +with the one in L. This has proven invaluable, +because people can use your view for much more fine-grained content +generation. + +Here is some example code for a fictional view: + + package CatalystX::View::MyView; + use strict; + use base 'Catalyst::View'; + + sub process { + my ($self, $c) = @_; + + my $template = $c->stash->{template}; + my $content = $self->render($c, $template, $c->stash); + $c->res->body( $content ); + } + + sub render { + my ($self, $c, $template, $args) = @_; + + # + # prepare content here + # + + return $content; + } + + 1; + +=head2 Plugins + +The first thing to say about plugins is that if you're not sure if your +module should be a plugin, it probably shouldn't. It once was common to +add features to Catalyst by writing plugins that provide accessors to +said functionality. As Catalyst grew more popular, it became obvious that +this qualifies as bad practice. + +By designing your module as a Catalyst plugin, every method you implement, +import or inherit will be available via your applications context object. +Said more harshly, you're polluting global namespace, and you should be +only doing that when you really need. + +Often, developers design extensions as plugins because they need to get +hold of the context object. Either to get at the stash or +request/response objects are the widely spread reasons. It is, however, +perfectly possible to implement a regular Catalyst component (read: +model, view or controller) that receives the current context object via +L. + +So when is a plugin suited to your task? Your code needs to be a plugin +to act upon or alter specific parts of Catalyst's request lifecycle. If +your functionality needs to wrap some C or C +stages, you won't get around a plugin. + +Another valid target for a plugin architecture are things that B +have to be globally available, like sessions or authentication. + +B release Catalyst extensions as plugins only to provide +some functionality application wide. Design it as a controller base class +or another suiting technique with a smaller scope, so that your code only +influences those parts of the application where it is needed, and +namespace clashes and conflicts are ruled out. + +The implementation is pretty easy. Your plugin will be inserted in the +application's inheritance list, above Catalyst itself. You can by this +alter Catalyst's request lifecycle behaviour. Every method you declare, +every import in your package will be available as method on the +application and the context object. As an example, let's say you want +Catalyst to warn you every time uri_for returned an undefined value, for +example because you specified the wrong number of captures for the +targeted action chain. You could do this with this simple +implementation (excuse the lame class name, it's just an example): + + package Catalyst::Plugin::UriforUndefWarning; + use strict; + use Class::C3; + + sub uri_for { + my $c = shift; + my $uri = $c->next::method(@_); + $c->log->warn( + 'uri_for returned undef for:', + join(', ', @_), + ); + return $uri; + } + + 1; + +This would override Catalyst's C method and emit a C log +entry containing the arguments that led to the undefined return value. + +=head2 Factory components with COMPONENT() + +Every component inheriting from L contains a +C method. It is used on application startup by +C to instantiate the component object for the Catalyst +application. By default, this will merge the components own +Curation with the application wide overrides and call the class' +C method to return the component object. + +You can override this method and do and return whatever you want. +However, you should use L to forward to the original +C method to merge the configuration of your component. + +Here is a stub C method: + + package CatalystX::Component::Foo; + use strict; + use base 'Catalyst::Component'; + + use Class::C3; + + sub COMPONENT { + my $class = shift; + my ($app_class, $config) = @_; + + # do things here before instantiation + my $obj = $self->next::method(@_); + # do things to object after instantiation + + return $object; + } + +The arguments are the class name of the component, the class name of +the application instantiating the component, and a hash reference +with the controller's configuration. + +You are free to re-bless the object, instantiate a whole other component +or really do anything compatible with Catalyst's expectations on a +component. + +For more information, please see +L. + +=head1 SEE ALSO + +L, +L, +L + +=head1 AUTHOR + +Robert Sedlacek C + +=head1 LICENSE AND COPYRIGHT + +This document is free, you can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut + diff --git a/lib/Catalyst/Manual/WritingPlugins.pod b/lib/Catalyst/Manual/WritingPlugins.pod deleted file mode 100644 index 5af1713..0000000 --- a/lib/Catalyst/Manual/WritingPlugins.pod +++ /dev/null @@ -1,254 +0,0 @@ -=head1 NAME - -Catalyst::Manual::WritingPlugins - An introduction to writing plugins -with L. - -=head1 DESCRIPTION - -Writing an integrated plugin for L using L. - -=head1 WHY PLUGINS? - -A Catalyst plugin is an integrated part of your application. By writing -plugins you can, for example, perform processing actions automatically, -instead of having to C to a processing method every time you -need it. - -=head1 WHAT'S NEXT? - -L is used to re-dispatch a method call as if the calling method -doesn't exist at all. In other words: If the class you're inheriting -from defines a method, and you're overloading that method in your own -class, NEXT gives you the possibility to call that overloaded method. - -This technique is the usual way to plug a module into Catalyst. - -=head1 INTEGRATING YOUR PLUGIN - -You can use L for your plugin by overloading certain methods which -are called by Catalyst during a request. - -=head2 The request life-cycle - -Catalyst creates a context object (C<$context> or, more usually, its -alias C<$c>) on every request, which is passed to all the handlers that -are called from preparation to finalization. - -For a complete list of the methods called during a request, see -L. The request can be split up in three -main stages: - -=over 4 - -=item preparation - -When the C handler is called, it initializes the request -object, connections, headers, and everything else that needs to be -prepared. C itself calls other methods to delegate these tasks. -After this method has run, everything concerning the request is in -place. - -=item dispatch - -The dispatching phase is where the black magic happens. The C -handler decides which actions have to be called for this request. - -=item finalization - -Catalyst uses the C method to prepare the response to give to -the client. It makes decisions according to your C (e.g. where -you want to redirect the user to). After this method, the response is -ready and waiting for you to do something with it--usually, hand it off -to your View class. - -=back - -=head2 What Plugins look like - -There's nothing special about a plugin except its name. A module named -C will be loaded by Catalyst if you specify it -in your application class, e.g.: - - # your plugin - package Catalyst::Plugin::MyPlugin; - use warnings; - use strict; - ... - - # MyApp.pm, your application class - use Catalyst qw/-Debug MyPlugin/; - -This does nothing but load your module. We'll now see how to overload stages of the request cycle, and provide accessors. - -=head2 Calling methods from your Plugin - -Methods that do not overload a handler are available directly in the -C<$c> context object; they don't need to be qualified with namespaces, -and you don't need to C them. - - package Catalyst::Plugin::Foobar; - use strict; - sub foo { return 'bar'; } - - # anywhere else in your Catalyst application: - - $c->foo(); # will return 'bar' - -That's it. - -=head2 Overloading - Plugging into Catalyst - -If you don't just want to provide methods, but want to actually plug -your module into the request cycle, you have to overload the handler -that suits your needs. - -Every handler gets the context object passed as its first argument. Pass -the rest of the arguments to the next handler in row by calling it via - - $c->NEXT::handler-name( @_ ); - -if you already Ced it out of C<@_>. Remember to C C. - -=head2 Storage and Configuration - -Some Plugins use their accessor names as a storage point, e.g. - - sub my_accessor { - my $c = shift; - $c->{my_accessor} = .. - -but it is more safe and clear to put your data in your configuration -hash: - - $c->config->{my_plugin}{ name } = $value; - -If you need to maintain data for more than one request, you should -store it in a session. - -=head1 EXAMPLE - -Here's a simple example Plugin that shows how to overload C -to add a unique ID to every request: - - package Catalyst::Plugin::RequestUUID; - - use warnings; - use strict; - - use Catalyst::Request; - use Data::UUID; - use NEXT; - - our $VERSION = 0.01; - - { # create a uuid accessor - package Catalyst::Request; - __PACKAGE__->mk_accessors('uuid'); - } - - sub prepare { - my $class = shift; - - # Turns the engine-specific request into a Catalyst context . - my $c = $class->NEXT::prepare( @_ ); - - $c->request->uuid( Data::UUID->new->create_str ); - $c->log->debug( 'Request UUID "'. $c->request->uuid .'"' ); - - return $c; - } - - 1; - -Let's just break it down into pieces: - - package Catalyst::Plugin::RequestUUID; - -The package name has to start with C to make sure you -can load your plugin by simply specifying - - use Catalyst qw/RequestUUID/; - -in the application class. L and L are recommended for -all Perl applications. - - use NEXT; - use Data::UUID; - our $VERSION = 0.01; - -NEXT must be explicitly Cd. L generates our unique -ID. The C<$VERSION> gets set because it's a) a good habit and b) -L likes it. - - sub prepare { - -These methods are called without attributes (Private, Local, etc.). - - my $c = shift; - -We get the context object for this request as the first argument. - -B:Be sure you shift the context object out of C<@_> in this. If -you just do a - - my ( $c ) = @_; - -it remains there, and you may run into problems if you're not aware of -what you pass to the handler you've overloaded. If you take a look at - - $c = $c->NEXT::prepare( @_ ); - -you see you would pass the context twice here if you don't shift it out -of your parameter list. - -This line is the main part of the plugin procedure. We call the -overloaded C method and pass along the parameters we got. We -also overwrite the context object C<$c> with the one returned by the -called method returns. We'll return our modified context object at the -end. - -Note that that if we modify C<$c> before this line, we also modify it -before the original (overloaded) C is run. If we modify it -after, we modify an already prepared context. And, of course, it's no -problem to do both, if you need to. Another example of working on the -context before calling the actual handler would be setting header -information before C does its job. - - $c->req->{req_uuid} = Data::UUID->new->create_str; - -This line creates a new L object and calls the C -method. The value is saved in our request, under the key C. We -can use that to access it in future in our application. - - $c->log->debug( 'Request UUID "'. $c->req->{req_uuid} .'"' ); - -This sends our UUID to the C log. - -The final line - - return $c; - -passes our modified context object back to whoever has called us. This -could be Catalyst itself, or the overloaded handler of another plugin. - -=head1 SEE ALSO - -L, L, L, L, -L. - -=head1 THANKS TO - -Sebastian Riedel and his team of Catalyst developers as well as all the -helpful people in #catalyst. - -=head1 COPYRIGHT - -This program is free software, you can redistribute it and/or modify it -under the same terms as Perl itself. - -=head1 AUTHOR - -S> with a lot of help from the -people on #catalyst. - -=cut