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