Use Ref::Util where appropriate
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Component.pm
1 package Catalyst::Component;
2
3 use Moose;
4 use Class::MOP;
5 use Class::MOP::Object;
6 use Catalyst::Utils;
7 use Class::C3::Adopt::NEXT;
8 use Devel::InnerPackage ();
9 use MRO::Compat;
10 use mro 'c3';
11 use Scalar::Util 'blessed';
12 use Class::Load 'is_class_loaded';
13 use Moose::Util 'find_meta';
14 use Ref::Util qw(is_plain_hashref);
15 use namespace::clean -except => 'meta';
16
17 with 'MooseX::Emulate::Class::Accessor::Fast';
18 with 'Catalyst::ClassData';
19
20
21 =head1 NAME
22
23 Catalyst::Component - Catalyst Component Base Class
24
25 =head1 SYNOPSIS
26
27     # lib/MyApp/Model/Something.pm
28     package MyApp::Model::Something;
29
30     use base 'Catalyst::Component';
31
32     __PACKAGE__->config( foo => 'bar' );
33
34     has foo => (
35         is => 'ro',
36     );
37
38     sub test {
39         my $self = shift;
40         return $self->foo;
41     }
42
43     sub forward_to_me {
44         my ( $self, $c ) = @_;
45         $c->response->output( $self->foo );
46     }
47
48     1;
49
50     # Methods can be a request step
51     $c->forward(qw/MyApp::Model::Something forward_to_me/);
52
53     # Or just methods
54     print $c->comp('MyApp::Model::Something')->test;
55
56     print $c->comp('MyApp::Model::Something')->foo;
57
58 =head1 DESCRIPTION
59
60 This is the universal base class for Catalyst components
61 (Model/View/Controller).
62
63 It provides you with a generic new() for component construction through Catalyst's
64 component loader with config() support and a process() method placeholder.
65
66 B<Note> that calling C<< $self->config >> inside a component is strongly
67 not recommended - the correctly merged config should have already been
68 passed to the constructor and stored in attributes - accessing
69 the config accessor directly from an instance is likely to get the
70 wrong values (as it only holds the class wide config, not things loaded
71 from the config file!)
72
73 =cut
74
75 __PACKAGE__->mk_classdata('_plugins');
76 __PACKAGE__->mk_classdata('_config');
77
78 has catalyst_component_name => ( is => 'ro' ); # Cannot be required => 1 as context
79                                        # class @ISA component - HATE
80 # Make accessor callable as a class method, as we need to call setup_actions
81 # on the application class, which we don't have an instance of, ewwwww
82 # Also, naughty modules like Catalyst::View::JSON try to write to _everything_,
83 # so spit a warning, ignore that (and try to do the right thing anyway) here..
84 around catalyst_component_name => sub {
85     my ($orig, $self) = (shift, shift);
86     Carp::cluck("Tried to write to the catalyst_component_name accessor - is your component broken or just mad? (Write ignored - using default value.)") if scalar @_;
87     blessed($self) ? $self->$orig() || blessed($self) : $self;
88 };
89
90 sub BUILDARGS {
91     my $class = shift;
92     my $args = {};
93
94     if (@_ == 1) {
95         $args = $_[0] if is_plain_hashref($_[0]);
96     } elsif (@_ == 2) { # is it ($app, $args) or foo => 'bar' ?
97         if (blessed($_[0])) {
98             $args = $_[1] if is_plain_hashref($_[1]);
99         } elsif (is_class_loaded($_[0]) &&
100                 $_[0]->isa('Catalyst') && is_plain_hashref($_[1])) {
101             $args = $_[1];
102         } else {
103             $args = +{ @_ };
104         }
105     } elsif (@_ % 2 == 0) {
106         $args = +{ @_ };
107     }
108
109     return $class->merge_config_hashes( $class->config, $args );
110 }
111
112 sub COMPONENT {
113     my ( $class, $c ) = @_;
114
115     # Temporary fix, some components does not pass context to constructor
116     my $arguments = is_plain_hashref($_[-1]) ? $_[-1] : {};
117     if ( my $next = $class->next::can ) {
118       my ($next_package) = Class::MOP::get_code_info($next);
119       warn "There is a COMPONENT method resolving after Catalyst::Component in ${next_package}.\n";
120       warn "This behavior can no longer be supported, and so your application is probably broken.\n";
121       warn "Your linearized isa hierarchy is: " . join(', ', @{ mro::get_linear_isa($class) }) . "\n";
122       warn "Please see perldoc Catalyst::Upgrading for more information about this issue.\n";
123     }
124     return $class->new($c, $arguments);
125 }
126
127 sub config {
128     my $self = shift;
129     # Uncomment once sane to do so
130     #Carp::cluck("config method called on instance") if ref $self;
131     my $config = $self->_config || {};
132     if (@_) {
133         my $newconfig = { %{@_ > 1 ? {@_} : $_[0]} };
134         $self->_config(
135             $self->merge_config_hashes( $config, $newconfig )
136         );
137     } else {
138         # this is a bit of a kludge, required to make
139         # __PACKAGE__->config->{foo} = 'bar';
140         # work in a subclass.
141         # TODO maybe this should be a ClassData option?
142         my $class = blessed($self) || $self;
143         my $meta = find_meta($class);
144         unless (${ $meta->get_or_add_package_symbol('$_config') }) {
145             # Call merge_hashes to ensure we deep copy the parent
146             # config onto the subclass
147             $self->_config( Catalyst::Utils::merge_hashes($config, {}) );
148         }
149     }
150     return $self->_config;
151 }
152
153 sub merge_config_hashes {
154     my ( $self, $lefthash, $righthash ) = @_;
155
156     return Catalyst::Utils::merge_hashes( $lefthash, $righthash );
157 }
158
159 sub process {
160
161     Catalyst::Exception->throw( message => ( ref $_[0] || $_[0] )
162           . " did not override Catalyst::Component::process" );
163 }
164
165 sub expand_modules {
166     my ($class, $component) = @_;
167     return Devel::InnerPackage::list_packages( $component );
168 }
169
170 __PACKAGE__->meta->make_immutable;
171
172 1;
173
174 __END__
175
176 =head1 METHODS
177
178 =head2 new($app, $arguments)
179
180 Called by COMPONENT to instantiate the component; should return an object
181 to be stored in the application's component hash.
182
183 =head2 COMPONENT
184
185 C<< my $component_instance = $component->COMPONENT($app, $arguments); >>
186
187 If this method is present (as it is on all Catalyst::Component subclasses),
188 it is called by Catalyst during setup_components with the application class
189 as $app and any config entry on the application for this component (for example,
190 in the case of MyApp::Controller::Foo this would be
191 C<< MyApp->config('Controller::Foo' => \%conf >>).
192
193 The arguments are expected to be a hashref and are merged with the
194 C<< __PACKAGE__->config >> hashref before calling C<< ->new >>
195 to instantiate the component.
196
197 You can override it in your components to do custom construction, using
198 something like this:
199
200   sub COMPONENT {
201       my ($class, $app, $args) = @_;
202       $args = $class->merge_config_hashes($class->config, $args);
203       return $class->new($app, $args);
204   }
205
206 B<NOTE:> Generally when L<Catalyst> starts, it initializes all the components
207 and passes the hashref present in any configuration information to the
208 COMPONENT method.  For example
209
210     MyApp->config(
211       'Model::Foo' => {
212         bar => 'baz',
213       });
214
215 You would expect COMPONENT to be called like this ->COMPONENT( 'MyApp', +{ bar=>'baz'});
216
217 This would happen ONCE during setup.
218
219 =head2 $c->config
220
221 =head2 $c->config($hashref)
222
223 =head2 $c->config($key, $value, ...)
224
225 Accessor for this component's config hash. Config values can be set as
226 key value pair, or you can specify a hashref. In either case the keys
227 will be merged with any existing config settings. Each component in
228 a Catalyst application has its own config hash.
229
230 The component's config hash is merged with any config entry on the
231 application for this component and passed to C<new()> (as mentioned
232 above at L</COMPONENT>). The recommended practice to access the merged
233 config is to use a Moose attribute for each config entry on the
234 receiving component.
235
236 =head2 $c->process()
237
238 This is the default method called on a Catalyst component in the dispatcher.
239 For instance, Views implement this action to render the response body
240 when you forward to them. The default is an abstract method.
241
242 =head2 $c->merge_config_hashes( $hashref, $hashref )
243
244 Merges two hashes together recursively, giving right-hand precedence.
245 Alias for the method in L<Catalyst::Utils>.
246
247 =head2 $c->expand_modules( $setup_component_config )
248
249 Return a list of extra components that this component has created. By default,
250 it just looks for a list of inner packages of this component
251
252 =cut
253
254 =head1 OPTIONAL METHODS
255
256 =head2 ACCEPT_CONTEXT($c, @args)
257
258 Catalyst components are normally initialized during server startup, either
259 as a Class or a Instance. However, some components require information about
260 the current request. To do so, they can implement an ACCEPT_CONTEXT method.
261
262 If this method is present, it is called during $c->comp/controller/model/view
263 with the current $c and any additional args (e.g. $c->model('Foo', qw/bar baz/)
264 would cause your MyApp::Model::Foo instance's ACCEPT_CONTEXT to be called with
265 ($c, 'bar', 'baz')) and the return value of this method is returned to the
266 calling code in the application rather than the component itself.
267
268 B<NOTE:> All classes that are L<Catalyst::Component>s will have a COMPONENT
269 method, but classes that are intended to be factories or generators will
270 have ACCEPT_CONTEXT.  If you have initialization arguments (such as from
271 configuration) that you wish to expose to the ACCEPT_CONTEXT you should
272 proxy them in the factory instance.  For example:
273
274     MyApp::Model::FooFactory;
275
276     use Moose;
277     extends 'Catalyst::Model';
278
279     has type => (is=>'ro', required=>1);
280
281     sub ACCEPT_CONTEXT {
282       my ($self, $c, @args) = @_;
283       return bless { args=>\@args }, $self->type;
284     }
285
286     MyApp::Model::Foo->meta->make_immutable;
287     MyApp::Model::Foo->config( type => 'Type1' );
288
289 And in a controller:
290
291     my $type = $c->model('FooFactory', 1,2,3,4): # $type->isa('Type1')
292
293 B<NOTE:> If you define a ACCEPT_CONTEXT method it MUST check to see if the
294 second argument is blessed (is a context) or not (is an application class name) and
295 it MUST return something valid for the case when the scope is application.  This is
296 required because a component maybe be called from the application scope even if it
297 requires a context and you must prevent errors from being issued if this happens.
298 Remember not all components that ACCEPT_CONTEXT actually need or use context information
299 (and there is a school of thought that suggestions doing so is a design error anyway...)
300
301 =head1 SEE ALSO
302
303 L<Catalyst>, L<Catalyst::Model>, L<Catalyst::View>, L<Catalyst::Controller>.
304
305 =head1 AUTHORS
306
307 Catalyst Contributors, see Catalyst.pm
308
309 =head1 COPYRIGHT
310
311 This library is free software. You can redistribute it and/or modify it under
312 the same terms as Perl itself.
313
314 =cut