X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=ff4ad244f839d06c1486c425dad9bff2d8f22532;hp=c50bc0f04cd221f9418d2fa92e53561c44c2eb07;hb=7cee84a9da0fc31861e2f81d333be5dac73e15d5;hpb=58ece1eaaafa14d3ece3b4d579690162c2ebc8b2 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index c50bc0f..ff4ad24 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -81,8 +81,19 @@ sub _build_request_constructor_args { sub composed_request_class { my $class = shift; + my @traits = (@{$class->request_class_traits||[]}, @{$class->config->{request_class_traits}||[]}); + + # For each trait listed, figure out what the namespace is. First we try the $trait + # as it is in the config. Then try $MyApp::TraitFor::Request:$trait. Last we try + # Catalyst::TraitFor::Request::$trait. If none load, throw error. + + my $trait_ns = 'TraitFor::Request'; + my @normalized_traits = map { + Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_) + } @traits; + return $class->_composed_request_class || - $class->_composed_request_class(Moose::Util::with_traits($class->request_class, @{$class->request_class_traits||[]})); + $class->_composed_request_class(Moose::Util::with_traits($class->request_class, @normalized_traits)); } has response => ( @@ -104,8 +115,15 @@ sub _build_response_constructor_args { sub composed_response_class { my $class = shift; + my @traits = (@{$class->response_class_traits||[]}, @{$class->config->{response_class_traits}||[]}); + + my $trait_ns = 'TraitFor::Response'; + my @normalized_traits = map { + Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_) + } @traits; + return $class->_composed_response_class || - $class->_composed_response_class(Moose::Util::with_traits($class->response_class, @{$class->response_class_traits||[]})); + $class->_composed_response_class(Moose::Util::with_traits($class->response_class, @normalized_traits)); } has namespace => (is => 'rw'); @@ -130,7 +148,8 @@ our $RECURSION = 1000; our $DETACH = Catalyst::Exception::Detach->new; our $GO = Catalyst::Exception::Go->new; -#I imagine that very few of these really need to be class variables. if any. +#I imagine that very few of these really +#need to be class variables. if any. #maybe we should just make them attributes with a default? __PACKAGE__->mk_classdata($_) for qw/components arguments dispatcher engine log dispatcher_class @@ -147,14 +166,21 @@ __PACKAGE__->stats_class('Catalyst::Stats'); sub composed_stats_class { my $class = shift; + my @traits = (@{$class->stats_class_traits||[]}, @{$class->config->{stats_class_traits}||[]}); + + my $trait_ns = 'TraitFor::Stats'; + my @normalized_traits = map { + Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_) + } @traits; + return $class->_composed_stats_class || - $class->_composed_stats_class(Moose::Util::with_traits($class->stats_class, @{$class->stats_class_traits||[]})); + $class->_composed_stats_class(Moose::Util::with_traits($class->stats_class, @normalized_traits)); } __PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.90089_001'; +our $VERSION = '5.90092'; $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases sub import { @@ -196,6 +222,11 @@ sub _application { $_[0] } Catalyst - The Elegant MVC Web Application Framework +=for html +CPAN version +Catalyst></a>
+<a href=Kwalitee Score + =head1 SYNOPSIS See the L distribution for comprehensive @@ -490,7 +521,7 @@ L<< detach|/"$c->detach( $action [, \@arguments ] )" >>. Like C<< $c->visit >>, C<< $c->go >> will perform a full dispatch on the specified action or method, with localized C<< $c->action >> and C<< $c->namespace >>. Like C, C escapes the processing of the current request chain on completion, and -does not return to its caller. +does not return to its cunless blessed $cunless blessed $caller. @arguments are arguments to the final destination of $action. @captures are arguments to the intermediate steps, if any, on the way to the final sub of @@ -539,6 +570,7 @@ t/middleware-stash.t in the distribution /t directory. sub stash { my $c = shift; + $c->log->error("You are requesting the stash but you don't have a context") unless blessed $c; return Catalyst::Middleware::Stash::get_stash($c->req->env)->(@_); } @@ -711,19 +743,15 @@ sub _comp_names { # Filter a component before returning by calling ACCEPT_CONTEXT if available -#our %tracker = (); sub _filter_component { my ( $c, $comp, @args ) = @_; - # die "Circular Dependencies Detected." if $tracker{$comp}; - # $tracker{$comp}++; if(ref $comp eq 'CODE') { - $comp = $comp->($c); + $comp = $comp->(); } - #$tracker{$comp}++; if ( eval { $comp->can('ACCEPT_CONTEXT'); } ) { - return $comp->ACCEPT_CONTEXT( $c, @args ); + return $comp->ACCEPT_CONTEXT( $c, @args ); } $c->log->warn("You called component '${\$comp->catalyst_component_name}' with arguments [@args], but this component does not ACCEPT_CONTEXT, so args are ignored.") if scalar(@args) && $c->debug; @@ -1379,6 +1407,7 @@ EOF : $class->log->debug(q/Couldn't find home/); my $column_width = Catalyst::Utils::term_width() - 8 - 9; + my $t = Text::SimpleTable->new( [ $column_width, 'Class' ], [ 8, 'Type' ] ); for my $comp ( sort keys %{ $class->components } ) { my $type = ref $class->components->{$comp} ? 'instance' : 'class'; @@ -2306,9 +2335,7 @@ sub prepare { $c->response->_context($c); - if($c->use_stats) { - $c->stats($class->composed_stats_class->new)->enable; - } + $c->stats($class->stats_class->new)->enable($c->use_stats); if ( $c->debug || $c->config->{enable_catalyst_header} ) { $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION ); @@ -2717,8 +2744,27 @@ Returns or sets the request class. Defaults to L. =head2 $app->request_class_traits -An arrayref of Ls which are applied to the request class. +An arrayref of Ls which are applied to the request class. You can +name the full namespace of the role, or a namespace suffix, which will then +be tried against the following standard namespace prefixes. + + $MyApp::TraitFor::Request::$trait_suffix + Catalyst::TraitFor::Request::$trait_suffix + +So for example if you set: + + MyApp->request_class_traits(['Foo']); + +We try each possible role in turn (and throw an error if none load) + Foo + MyApp::TraitFor::Request::Foo + Catalyst::TraitFor::Request::Foo + +The namespace part 'TraitFor::Request' was choosen to assist in backwards +compatibility with L which previously provided +these features in a stand alone package. + =head2 $app->composed_request_class This is the request class which has been composed with any request_class_traits. @@ -2729,7 +2775,27 @@ Returns or sets the response class. Defaults to L. =head2 $app->response_class_traits -An arrayref of Ls which are applied to the response class. +An arrayref of Ls which are applied to the response class. You can +name the full namespace of the role, or a namespace suffix, which will then +be tried against the following standard namespace prefixes. + + $MyApp::TraitFor::Response::$trait_suffix + Catalyst::TraitFor::Response::$trait_suffix + +So for example if you set: + + MyApp->response_class_traits(['Foo']); + +We try each possible role in turn (and throw an error if none load) + + Foo + MyApp::TraitFor::Response::Foo + Catalyst::TraitFor::Responset::Foo + +The namespace part 'TraitFor::Response' was choosen to assist in backwards +compatibility with L which previously provided +these features in a stand alone package. + =head2 $app->composed_response_class @@ -2850,26 +2916,110 @@ sub setup_components { # of named components in the configuration that are not actually existing (not a # real file). + my @injected = $class->setup_injected_components; + + # All components are registered, now we need to 'init' them. + foreach my $component_name (@injected, @comps) { + $class->components->{$component_name} = $class->components->{$component_name}->() if + (ref($class->components->{$component_name}) || '') eq 'CODE'; + } +} + +=head2 $app->setup_injected_components + +Called by setup_compoents to setup components that are injected. + +=cut + +sub setup_injected_components { + my ($class) = @_; my @injected_components = keys %{$class->config->{inject_components} ||+{}}; + foreach my $injected_comp_name(@injected_components) { - my $component_class = $class->config->{inject_components}->{$injected_comp_name}->{from_component} || ''; - if($component_class) { - my @roles = @{$class->config->{inject_components}->{$injected_comp_name}->{roles} ||[]}; - my %args = %{ $class->config->{$injected_comp_name} || +{} }; + $class->setup_injected_component( + $injected_comp_name, + $class->config->{inject_components}->{$injected_comp_name}); + } + return map { $class ."::" . $_ } + @injected_components; +} + +=head2 $app->setup_injected_component( $injected_component_name, $config ) + +Setup a given injected component. + +=cut + +sub setup_injected_component { + my ($class, $injected_comp_name, $config) = @_; + if(my $component_class = $config->{from_component}) { + my @roles = @{$config->{roles} ||[]}; Catalyst::Utils::inject_component( into => $class, component => $component_class, (scalar(@roles) ? (traits => \@roles) : ()), as => $injected_comp_name); - } } +} - # All components are registered, now we need to 'init' them. - foreach my $component_name (keys %{$class->components||{}}) { - $class->components->{$component_name} = $class->components->{$component_name}->() if - (ref($class->components->{$component_name}) || '') eq 'CODE'; - } +=head2 $app->inject_component($MyApp_Component_name => \%args); + +Add a component that is injected at setup: + + MyApp->inject_component( 'Model::Foo' => { from_component => 'Common::Foo' } ); + +Must be called before ->setup. Expects a component name for your +current application and \%args where + +=over 4 + +=item from_component + +The target component being injected into your application + +=item roles + +An arrayref of Ls that are applied to your component. + +=back + +Example + + MyApp->inject_component( + 'Model::Foo' => { + from_component => 'Common::Model::Foo', + roles => ['Role1', 'Role2'], + }); + +=head2 $app->inject_components + +Inject a list of components: + + MyApp->inject_components( + 'Model::FooOne' => { + from_component => 'Common::Model::Foo', + roles => ['Role1', 'Role2'], + }, + 'Model::FooTwo' => { + from_component => 'Common::Model::Foo', + roles => ['Role1', 'Role2'], + }); + +=cut + +sub inject_component { + my ($app, $name, $args) = @_; + die "Component $name exists" if + $app->config->{inject_components}->{$name}; + $app->config->{inject_components}->{$name} = $args; +} + +sub inject_components { + my $app = shift; + while(@_) { + $app->inject_component(shift, shift); + } } =head2 $c->locate_components( $setup_component_config ) @@ -2939,8 +3089,7 @@ sub setup_component { return $component; } - my $suffix = Catalyst::Utils::class2classsuffix( $component ); - my $config = $class->config->{ $suffix } || {}; + my $config = $class->config_for($component); # Stash catalyst_component_name in the config here, so that custom COMPONENT # methods also pass it. local to avoid pointlessly shitting in config # for the debug screen, as $component is already the key name. @@ -2978,6 +3127,33 @@ sub setup_component { return $instance; } +=head2 $app->config_for( $component_name ) + +Return the application level configuration (which is not yet merged with any +local component configuration, via $component_class->config) for the named +component or component object. Example: + + MyApp->config( + 'Model::Foo' => { a => 1, b => 2}, + ); + + my $config = MyApp->config_for('MyApp::Model::Foo'); + +In this case $config is the hashref C< {a=>1, b=>2} >. + +This is also handy for looking up configuration for a plugin, to make sure you follow +existing L standards for where a plugin should put its configuration. + +=cut + +sub config_for { + my ($class, $component_name) = @_; + my $component_suffix = Catalyst::Utils::class2classsuffix($component_name); + my $config = $class->config->{ $component_suffix } || {}; + + return $config; +} + =head2 $c->setup_dispatcher Sets up dispatcher. @@ -3787,7 +3963,26 @@ A arrayref of Ls that are applied to the stats_class before creatin =head2 $app->composed_stats_class -this is the stats_class composed with any 'stats_class_traits'. +this is the stats_class composed with any 'stats_class_traits'. You can +name the full namespace of the role, or a namespace suffix, which will then +be tried against the following standard namespace prefixes. + + $MyApp::TraitFor::Stats::$trait_suffix + Catalyst::TraitFor::Stats::$trait_suffix + +So for example if you set: + + MyApp->stats_class_traits(['Foo']); + +We try each possible role in turn (and throw an error if none load) + + Foo + MyApp::TraitFor::Stats::Foo + Catalyst::TraitFor::Stats::Foo + +The namespace part 'TraitFor::Stats' was choosen to assist in backwards +compatibility with L which previously provided +these features in a stand alone package. =head2 $c->use_stats @@ -4069,6 +4264,55 @@ C - See L. C - See L. +=item * + +C + +An arrayref of Ls that get componsed into your stats class. + +=item * + +C + +An arrayref of Ls that get componsed into your request class. + +=item * + +C + +An arrayref of Ls that get componsed into your response class. + +=item * + +C + +A Hashref of L subclasses that are 'injected' into configuration. +For example: + + MyApp->config({ + inject_components => { + 'Controller::Err' => { from_component => 'Local::Controller::Errors' }, + 'Model::Zoo' => { from_component => 'Local::Model::Foo' }, + 'Model::Foo' => { from_component => 'Local::Model::Foo', roles => ['TestRole'] }, + }, + 'Controller::Err' => { a => 100, b=>200, namespace=>'error' }, + 'Model::Zoo' => { a => 2 }, + 'Model::Foo' => { a => 100 }, + }); + +Generally L looks for components in your Model/View or Controller directories. +However for cases when you which to use an existing component and you don't need any +customization (where for when you can apply a role to customize it) you may inject those +components into your application. Please note any configuration should be done 'in the +normal way', with a key under configuration named after the component affix, as in the +above example. + +Using this type of injection allows you to construct significant amounts of your application +with only configuration!. This may or may not lead to increased code understanding. + +Please not you may also call the ->inject_components application method as well, although +you must do so BEFORE setup. + =back =head1 EXCEPTIONS