In Catalyst::Test, don't mangle headers of non-HTML responses. RT#79043
[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';
7a5ed4ef 12use namespace::clean -except => 'meta';
5595dd2f 13
a7caa492 14with 'MooseX::Emulate::Class::Accessor::Fast';
15with 'Catalyst::ClassData';
16
17
158c88c0 18=head1 NAME
19
20Catalyst::Component - Catalyst Component Base Class
21
22=head1 SYNOPSIS
23
24 # lib/MyApp/Model/Something.pm
25 package MyApp::Model::Something;
26
e7f1cf73 27 use base 'Catalyst::Component';
158c88c0 28
29 __PACKAGE__->config( foo => 'bar' );
30
d8ccdd9d 31 has foo => (
32 is => 'ro',
33 );
34
158c88c0 35 sub test {
36 my $self = shift;
d8ccdd9d 37 return $self->foo;
158c88c0 38 }
39
40 sub forward_to_me {
41 my ( $self, $c ) = @_;
d8ccdd9d 42 $c->response->output( $self->foo );
158c88c0 43 }
43c58153 44
158c88c0 45 1;
46
47 # Methods can be a request step
48 $c->forward(qw/MyApp::Model::Something forward_to_me/);
49
50 # Or just methods
51 print $c->comp('MyApp::Model::Something')->test;
52
d8ccdd9d 53 print $c->comp('MyApp::Model::Something')->foo;
158c88c0 54
55=head1 DESCRIPTION
56
43c58153 57This is the universal base class for Catalyst components
158c88c0 58(Model/View/Controller).
59
cf8eab35 60It provides you with a generic new() for component construction through Catalyst's
158c88c0 61component loader with config() support and a process() method placeholder.
62
d8ccdd9d 63B<Note> that calling C<< $self->config >> inside a component is strongly
c80736fa 64not recommended - the correctly merged config should have already been
d8ccdd9d 65passed to the constructor and stored in attributes - accessing
66the config accessor directly from an instance is likely to get the
67wrong values (as it only holds the class wide config, not things loaded
68from the config file!)
69
7cd1a42b 70=cut
158c88c0 71
46d0346d 72__PACKAGE__->mk_classdata('_plugins');
11b256bc 73__PACKAGE__->mk_classdata('_config');
e8b9f2a9 74
8f6cebb2 75has catalyst_component_name => ( is => 'ro' ); # Cannot be required => 1 as context
d2598ac8 76 # class @ISA component - HATE
77# Make accessor callable as a class method, as we need to call setup_actions
78# on the application class, which we don't have an instance of, ewwwww
e65d000f 79# Also, naughty modules like Catalyst::View::JSON try to write to _everything_,
80# so spit a warning, ignore that (and try to do the right thing anyway) here..
8f6cebb2 81around catalyst_component_name => sub {
d2598ac8 82 my ($orig, $self) = (shift, shift);
8f6cebb2 83 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 84 blessed($self) ? $self->$orig() || blessed($self) : $self;
d2598ac8 85};
1b79e199 86
2ef59958 87sub BUILDARGS {
7a5ed4ef 88 my $class = shift;
89 my $args = {};
90
91 if (@_ == 1) {
92 $args = $_[0] if ref($_[0]) eq 'HASH';
93 } elsif (@_ == 2) { # is it ($app, $args) or foo => 'bar' ?
94 if (blessed($_[0])) {
95 $args = $_[1] if ref($_[1]) eq 'HASH';
96 } elsif (Class::MOP::is_class_loaded($_[0]) &&
97 $_[0]->isa('Catalyst') && ref($_[1]) eq 'HASH') {
98 $args = $_[1];
7a5ed4ef 99 } else {
100 $args = +{ @_ };
101 }
102 } elsif (@_ % 2 == 0) {
103 $args = +{ @_ };
104 }
43c58153 105
7a5ed4ef 106 return $class->merge_config_hashes( $class->config, $args );
2ef59958 107}
4090e3bb 108
22247e54 109sub COMPONENT {
1b79e199 110 my ( $class, $c ) = @_;
22247e54 111
112 # Temporary fix, some components does not pass context to constructor
113 my $arguments = ( ref( $_[-1] ) eq 'HASH' ) ? $_[-1] : {};
1b79e199 114 if ( my $next = $class->next::can ) {
6a7254b5 115 my ($next_package) = Class::MOP::get_code_info($next);
7e2ec16e 116 warn "There is a COMPONENT method resolving after Catalyst::Component in ${next_package}.\n";
117 warn "This behavior can no longer be supported, and so your application is probably broken.\n";
1cc8db0c 118 warn "Your linearized isa hierarchy is: " . join(', ', @{ mro::get_linear_isa($class) }) . "\n";
7e2ec16e 119 warn "Please see perldoc Catalyst::Upgrading for more information about this issue.\n";
6a7254b5 120 }
1b79e199 121 return $class->new($c, $arguments);
22247e54 122}
123
158c88c0 124sub config {
11b256bc 125 my $self = shift;
df960201 126 # Uncomment once sane to do so
127 #Carp::cluck("config method called on instance") if ref $self;
11b256bc 128 my $config = $self->_config || {};
129 if (@_) {
130 my $newconfig = { %{@_ > 1 ? {@_} : $_[0]} };
131 $self->_config(
132 $self->merge_config_hashes( $config, $newconfig )
133 );
134 } else {
135 # this is a bit of a kludge, required to make
136 # __PACKAGE__->config->{foo} = 'bar';
edffeb5a 137 # work in a subclass.
7a5ed4ef 138 # TODO maybe this should be a ClassData option?
e106a59f 139 my $class = blessed($self) || $self;
140 my $meta = Class::MOP::get_metaclass_by_name($class);
fc9ec364 141 unless (${ $meta->get_or_add_package_symbol('$_config') }) {
c03aaf03 142 # Call merge_hashes to ensure we deep copy the parent
143 # config onto the subclass
144 $self->_config( Catalyst::Utils::merge_hashes($config, {}) );
46d0346d 145 }
158c88c0 146 }
7a5ed4ef 147 return $self->_config;
158c88c0 148}
149
7cd1a42b 150sub merge_config_hashes {
151 my ( $self, $lefthash, $righthash ) = @_;
158c88c0 152
7cd1a42b 153 return Catalyst::Utils::merge_hashes( $lefthash, $righthash );
154}
158c88c0 155
156sub process {
157
158 Catalyst::Exception->throw( message => ( ref $_[0] || $_[0] )
159 . " did not override Catalyst::Component::process" );
160}
161
5d02e790 162sub expand_modules {
163 my ($class, $component) = @_;
164 return Devel::InnerPackage::list_packages( $component );
165}
166
46d0346d 167__PACKAGE__->meta->make_immutable;
7a5ed4ef 168
7cd1a42b 1691;
baf6a3db 170
7cd1a42b 171__END__
baf6a3db 172
7cd1a42b 173=head1 METHODS
baf6a3db 174
58064941 175=head2 new($app, $arguments)
baf6a3db 176
7cd1a42b 177Called by COMPONENT to instantiate the component; should return an object
178to be stored in the application's component hash.
179
7a5ed4ef 180=head2 COMPONENT
181
182C<< my $component_instance = $component->COMPONENT($app, $arguments); >>
7cd1a42b 183
f8a54681 184If this method is present (as it is on all Catalyst::Component subclasses),
7cd1a42b 185it is called by Catalyst during setup_components with the application class
58064941 186as $app and any config entry on the application for this component (for example,
7cd1a42b 187in the case of MyApp::Controller::Foo this would be
9779c885 188C<< MyApp->config('Controller::Foo' => \%conf >>).
58064941 189
9779c885 190The arguments are expected to be a hashref and are merged with the
191C<< __PACKAGE__->config >> hashref before calling C<< ->new >>
192to instantiate the component.
7cd1a42b 193
cf8eab35 194You can override it in your components to do custom construction, using
7a5ed4ef 195something like this:
196
197 sub COMPONENT {
198 my ($class, $app, $args) = @_;
2a787673 199 $args = $class->merge_config_hashes($class->config, $args);
7a5ed4ef 200 return $class->new($app, $args);
201 }
202
7cd1a42b 203=head2 $c->config
204
205=head2 $c->config($hashref)
206
207=head2 $c->config($key, $value, ...)
208
43c58153 209Accessor for this component's config hash. Config values can be set as
7cd1a42b 210key value pair, or you can specify a hashref. In either case the keys
43c58153 211will be merged with any existing config settings. Each component in
212a Catalyst application has its own config hash.
7cd1a42b 213
f8a54681 214The component's config hash is merged with any config entry on the
215application for this component and passed to C<new()> (as mentioned
d8ccdd9d 216above at L</COMPONENT>). The recommended practice to access the merged
f8a54681 217config is to use a Moose attribute for each config entry on the
218receiving component.
219
7cd1a42b 220=head2 $c->process()
221
222This is the default method called on a Catalyst component in the dispatcher.
43c58153 223For instance, Views implement this action to render the response body
7cd1a42b 224when you forward to them. The default is an abstract method.
225
226=head2 $c->merge_config_hashes( $hashref, $hashref )
227
228Merges two hashes together recursively, giving right-hand precedence.
229Alias for the method in L<Catalyst::Utils>.
baf6a3db 230
5d02e790 231=head2 $c->expand_modules( $setup_component_config )
232
233Return a list of extra components that this component has created. By default,
234it just looks for a list of inner packages of this component
235
236=cut
237
825dbf85 238=head1 OPTIONAL METHODS
239
240=head2 ACCEPT_CONTEXT($c, @args)
241
f9c35d6c 242Catalyst components are normally initialized during server startup, either
825dbf85 243as a Class or a Instance. However, some components require information about
244the current request. To do so, they can implement an ACCEPT_CONTEXT method.
245
246If this method is present, it is called during $c->comp/controller/model/view
247with the current $c and any additional args (e.g. $c->model('Foo', qw/bar baz/)
248would cause your MyApp::Model::Foo instance's ACCEPT_CONTEXT to be called with
249($c, 'bar', 'baz')) and the return value of this method is returned to the
250calling code in the application rather than the component itself.
251
158c88c0 252=head1 SEE ALSO
253
e7f1cf73 254L<Catalyst>, L<Catalyst::Model>, L<Catalyst::View>, L<Catalyst::Controller>.
158c88c0 255
2f381252 256=head1 AUTHORS
158c88c0 257
2f381252 258Catalyst Contributors, see Catalyst.pm
158c88c0 259
260=head1 COPYRIGHT
261
536bee89 262This library is free software. You can redistribute it and/or modify it under
158c88c0 263the same terms as Perl itself.
264
85d9fce6 265=cut