Codebase list libclass-container-perl / 8f5f461
[svn-inject] Installing original source of libclass-container-perl Ansgar Burchardt 13 years ago
12 changed file(s) with 1986 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 use Module::Build;
1
2 my $b = Module::Build->new
3 (
4 module_name => 'Class::Container',
5 requires => {
6 'Params::Validate' => '0.23',
7 'Carp' => 0,
8 },
9 recommends => { 'Scalar::Util' => 0 },
10 license => 'perl',
11 create_readme => 1,
12 sign => 1,
13 );
14
15 $b->create_build_script;
0 Revision history for Perl extension Class::Framework.
1
2 0.12 Sun Jan 23 21:02:35 CST 2005
3
4 - Fixed a bug in the container() method, which was only returning
5 valid results for delayed objects, not auto-created ones. [Spotted
6 by Sebastian Willert]
7
8 0.11 Wed Mar 3 21:34:51 CST 2004
9
10 - Fixed a bug in the code that detects whether Scalar::Util is
11 loadable. [Spotted by Michael Alan Dorman]
12
13 0.10 Thu Mar 6 16:06:47 CST 2003
14
15 - The dump_parameters() method will now output the default values (if
16 any) of parameters that haven't been explicitly set.
17
18 0.09 Mon Feb 10 13:12:40 CST 2003
19
20 - Use Carp::croak() instead of die() in most places when throwing a
21 fatal error.
22
23 - Fixed a problem in dump_parameters() in which attributes from
24 superclasses weren't getting dumped.
25
26 - The valid_params() method now always returns a hashref, possibly
27 empty (when initialization hasn't happened yet), instead of
28 sometimes returning undef.
29
30 - Added experimental support for "decorator" classes, via the
31 decorates() method.
32
33 - Fixed the credits in the AUTHOR section to better reflect reality.
34
35 - Added a Module::Build-style Build.PL script for installation.
36
37 0.08 Thu Aug 29 17:11:20 EST 2002
38
39 - Added the dump_parameters() method, which returns a hash reference
40 containing a set of parameters that should be sufficient to
41 re-create the given object using its class's C<new()> method (under
42 normal/simple circumstances).
43
44 0.07 Tue Jul 23 00:34:34 PDT 2002
45
46 - Fix a bug in passing contained objects rather than using the
47 defaultly created one. [found by Ilya Martynov <ilya@martynov.org>]
48
49 - Calling container() when Scalar::Util is not installed now triggers
50 a fatal error instead of returning undef
51
52 - Get rid off the %ALLOWED_CACHE memoization, since it wasn't working
53 properly. It could be done, but not as easily, so maybe it's a
54 future project. [consistent prodding by Dave Rolsky]
55
56 - Replace guts of get_contained_object_spec() and validation_spec()
57 with an _iterate_ISA() internal method. This fixes a bug in
58 validation_spec() in which subclasses weren't overriding superclass
59 validation_spec()s.
60
61 - Make valid_params() a standard get/set accessor method.
62
63 - Document that valid_params() should only be called as a class
64 method, not object method.
65
66 - Improve the output of the show_containers() method.
67
68 - Calling contained_objects() twice on the same package wasn't
69 properly clearing previous entries.
70
71 0.06 Wed Jul 17 17:06:10 EST 2002
72
73 - Memoize the get_contained_object_spec(), validation_spec(), and
74 allowed_params() methods. This can give a big speed boost when
75 methods are called repeatedly, for example when using factory
76 methods. All memoization caches are cleared when valid_params() or
77 contained_objects() is called. The only known pitfall in the
78 caching is that a class that dynamically changes its @ISA will
79 probably mess things up. Idea by Dave Rolsky.
80
81 - Use 'scalar validate_with()' inside new(), which may be faster.
82 Idea by Dave Rolsky.
83
84 - short-circuit create_contained_objects() if there are no contained
85 objects to create. Idea by Dave Rolsky.
86
87 - return a reference from create_contained_objects() rather than a
88 list of key/value pairs. This lets us pass it directly to validation
89 routines.
90
91 - Use the qr// format for regexes in the all_specs() method.
92
93 - Improve the docs for allowed_params() - it's a class method (not an
94 instance method), and it accepts a list of arguments that can
95 affect the return value.
96
97 - Don't copy as many hashes internally. Pass by reference.
98
99 - Fixed some POD formatting problems.
100
101 - Now requires Params::Validate version 0.23
102
103 0.05 Thu Jun 27 16:53:41 EST 2002
104
105 - Fixed a problem in create_contained_objects() in which a 'foo_class'
106 parameter wouldn't get properly passsed to all the contained objects
107 that needed to see it.
108
109 - Added a documentation section "Scenario" explaining the main benefits
110 of using the module.
111
112 - Improved the output of show_containers(), notably the names of
113 delayed classes
114
115 - Merge $self->{container}{delayed} into $self->{container}{contained},
116 with a 'delayed' property. This allows simplification of the rest of
117 the code in several places, notably the show_containers() routine.
118
119 - Simplify show_containers() a little, and make it more accurate on
120 contained objects' classes
121
122 - Fixed a doc error in the first example
123
124 - Added a bit in the first doc paragraph, saying that any of the
125 Mason objects can be replaced by a subclass.
126
127 - Added an internal comment about the strategy inside the
128 allowed_params method.
129
130 - Simplified the allowed_params method internally.
131
132 - Got rid of special-casing to check for circular containment
133 relationships. This seemed to have been added for HTML::Mason, but
134 all Mason tests (as well as all Class::Container tests) still pass
135 when I remove it.
136
137 0.04 Wed Jun 26 19:15:26 EST 2002
138
139 - Add the show_containers() method, which should be a godsend during
140 debugging.
141
142 - Convert contained_objects() string specs to hashes upon input, rather
143 than checking them every time they're used later in the code.
144
145 - Change " if (%args)" to " if (keys %args)", which is more officially
146 correct (though both would work in this particular case).
147
148 0.03 Fri Jun 21 17:44:37 EST 2002
149
150 - Subclasses can now override contained_objects settings of their
151 superclass (previously it was backwards).
152
153 - Let call_method() accept arbitrary additional parameters, don't
154 force them into a hash.
155
156 - Added contained_class() method.
157
158 - Use new contained_class() method inside call_method().
159
160 - delayed_object_class() shouldn't be settable, it'll mess up the
161 parameters accepted.
162
163 - The 'container' parameter shouldn't be shared among containers the way
164 other parameters are.
165
166 - Made create_delayed_object() a little more efficient by not shifting
167 things off @_ - just pass @_ to the next new() method.
168
169 - Don't check for $contained_class->can('allowed_params'), check for
170 $contained_class->isa(__PACKAGE__).
171
172 - Clarified a few error messages.
173
174 - Clarified documentation and removed a couple of doc errors.
175
176
177 0.02 Wed Jun 19 10:52:48 AEST 2002
178 - Made Scalar::Util a little more optional - the container() method
179 is just a no-op if it's not around.
180
181 - Use Params::Validate 0.18 new validate_with() method to set a
182 meaningful subroutine name in error messages
183
184 - Added the delayed_object_class() method
185
186 - Documented how delayed objects are declared and created [Dave
187 Rolsky]
188
189 - Added some tests for the above stuff
190
191 - Various documentation spruce-ups
192
193 0.01_05 Fri May 10 15:29:46 AEST 2002
194 - If a container has two contained classes that both need to see a
195 parameter of the same name, it will now be passed to both. Previously
196 it was passed to one of them, randomly.
197
198 - Added 2 tests for the above.
199
200 - Added 4 tests to make sure class names can be properly overridden.
201
202 - Got rid of _make_contained_object() method.
203
204 - Changed the (undocumented) get_contained_objects() method to
205 get_contained_object_spec().
206
207 - Added the container() method, to get a reference to the thingy that
208 created you. Uses weak references if Scalar::Util is available.
209
210 - Consolidated all Container metadata in $self->{container} (subject to
211 change to {_container} or something).
212
213 - Added call_method() method.
214
215 0.01 Wed Mar 20 19:33:40 2002
216 - original version, based on HTML::Mason::Container
0 Installation instructions for Class::Container
1
2 To install this module, follow the standard steps for installing most
3 Perl modules:
4
5 perl Makefile.PL
6 make
7 make test
8 make install
9
10 Or you may use the newer Module::Build-style installation script:
11
12 perl Build.PL
13 ./Build
14 ./Build test
15 ./Build install
16
17 Or you may use the CPAN.pm module, which will automatically execute
18 these steps for you.
19
20 -Ken
0 Build.PL
1 Changes
2 INSTALL
3 lib/Class/Container.pm
4 Makefile.PL
5 MANIFEST This list of files
6 META.yml
7 README
8 t/01-basic.t
9 t/02-decorator.t
10 t/classes.pl
11 SIGNATURE Added here by Module::Build
0 --- #YAML:1.0
1 name: Class-Container
2 version: 0.12
3 author:
4 - |-
5 Originally by Ken Williams <ken@mathforum.org> and Dave Rolsky
6 <autarch@urth.org> for the HTML::Mason project. Important feedback
7 contributed by Jonathan Swartz <swartz@pobox.com>. Extended by Ken
8 Williams for the AI::Categorizer project.
9 abstract: Glues object frameworks together transparently
10 license: perl
11 requires:
12 Carp: 0
13 Params::Validate: 0.23
14 recommends:
15 Scalar::Util: 0
16 provides:
17 Class::Container:
18 file: lib/Class/Container.pm
19 version: 0.12
20 generated_by: Module::Build version 0.26
0
1 use ExtUtils::MakeMaker;
2
3 unless (eval "use Scalar::Util; 1") {
4 warn("You seem not to have the Scalar::Util module installed.\n" .
5 "Its installation is recommended (but not required) for Class::Container - see the README.\n");
6 sleep 4;
7 }
8
9 my $module = 'Class::Container';
10 my ($file, $dir);
11 ($file = "lib/$module.pm") =~ s{::}{/}g;
12 ($dir = $module) =~ s/::/-/g;
13
14 WriteMakefile
15 (
16 'NAME' => $module,
17 'VERSION_FROM' => $file, # finds $VERSION
18 'dist' => { COMPRESS=>"gzip",
19 SUFFIX=>"gz",
20 PREOP=>('rm -f README; '.
21 "pod2text -80 < $file > README; ".
22 "cp -f README $dir-\$(VERSION); "
23 ),
24 },
25 'PREREQ_PM' => {
26 Params::Validate => '0.23',
27 },
28 'PL_FILES' => {},
29 ($] >= 5.005 ? ## Add these new keywords supported since 5.005
30 (ABSTRACT_FROM => $file, # retrieve abstract from module
31 AUTHOR => 'Ken Williams <ken@mathforum.org>') :
32 ()
33 ),
34 NO_META => 1,
35 );
36
0 NAME
1 Class::Container - Glues object frameworks together transparently
2
3 SYNOPSIS
4 package Car;
5 use Class::Container;
6 @ISA = qw(Class::Container);
7
8 __PACKAGE__->valid_params
9 (
10 paint => {default => 'burgundy'},
11 style => {default => 'coupe'},
12 windshield => {isa => 'Glass'},
13 radio => {isa => 'Audio::Device'},
14 );
15
16 __PACKAGE__->contained_objects
17 (
18 windshield => 'Glass::Shatterproof',
19 wheel => { class => 'Vehicle::Wheel',
20 delayed => 1 },
21 radio => 'Audio::MP3',
22 );
23
24 sub new {
25 my $package = shift;
26
27 # 'windshield' and 'radio' objects are created automatically by
28 # SUPER::new()
29 my $self = $package->SUPER::new(@_);
30
31 $self->{right_wheel} = $self->create_delayed_object('wheel');
32 ... do any more initialization here ...
33 return $self;
34 }
35
36 DESCRIPTION
37 This class facilitates building frameworks of several classes that
38 inter-operate. It was first designed and built for "HTML::Mason", in
39 which the Compiler, Lexer, Interpreter, Resolver, Component, Buffer, and
40 several other objects must create each other transparently, passing the
41 appropriate parameters to the right class, possibly substituting other
42 subclasses for any of these objects.
43
44 The main features of "Class::Container" are:
45
46 * Explicit declaration of containment relationships (aggregation,
47 factory creation, etc.)
48
49 * Declaration of constructor parameters accepted by each member in a
50 class framework
51
52 * Transparent passing of constructor parameters to the class that
53 needs them
54
55 * Ability to create one (automatic) or many (manual) contained objects
56 automatically and transparently
57
58 Scenario
59 Suppose you've got a class called "Parent", which contains an object of
60 the class "Child", which in turn contains an object of the class
61 "GrandChild". Each class creates the object that it contains. Each class
62 also accepts a set of named parameters in its "new()" method. Without
63 using "Class::Container", "Parent" will have to know all the parameters
64 that "Child" takes, and "Child" will have to know all the parameters
65 that "GrandChild" takes. And some of the parameters accepted by "Parent"
66 will really control aspects of "Child" or "GrandChild". Likewise, some
67 of the parameters accepted by "Child" will really control aspects of
68 "GrandChild". So, what happens when you decide you want to use a
69 "GrandDaughter" class instead of the generic "GrandChild"? "Parent" and
70 "Child" must be modified accordingly, so that any additional parameters
71 taken by "GrandDaughter" can be accommodated. This is a pain - the kind
72 of pain that object-oriented programming was supposed to shield us from.
73
74 Now, how can "Class::Container" help? Using "Class::Container", each
75 class ("Parent", "Child", and "GrandChild") will declare what arguments
76 they take, and declare their relationships to the other classes
77 ("Parent" creates/contains a "Child", and "Child" creates/contains a
78 "GrandChild"). Then, when you create a "Parent" object, you can pass
79 "Parent->new()" all the parameters for all three classes, and they will
80 trickle down to the right places. Furthermore, "Parent" and "Child"
81 won't have to know anything about the parameters of its contained
82 objects. And finally, if you replace "GrandChild" with "GrandDaughter",
83 no changes to "Parent" or "Child" will likely be necessary.
84
85 METHODS
86 new()
87 Any class that inherits from "Class::Container" should also inherit its
88 "new()" method. You can do this simply by omitting it in your class, or
89 by calling "SUPER::new(@_)" as indicated in the SYNOPSIS. The "new()"
90 method ensures that the proper parameters and objects are passed to the
91 proper constructor methods.
92
93 At the moment, the only possible constructor method is "new()". If you
94 need to create other constructor methods, they should call "new()"
95 internally.
96
97 __PACKAGE__->contained_objects()
98 This class method is used to register what other objects, if any, a
99 given class creates. It is called with a hash whose keys are the
100 parameter names that the contained class's constructor accepts, and
101 whose values are the default class to create an object of.
102
103 For example, consider the "HTML::Mason::Compiler" class, which uses the
104 following code:
105
106 __PACKAGE__->contained_objects( lexer => 'HTML::Mason::Lexer' );
107
108 This defines the relationship between the "HTML::Mason::Compiler" class
109 and the class it creates to go in its "lexer" slot. The
110 "HTML::Mason::Compiler" class "has a" "lexer". The
111 "HTML::Mason::Compiler->new()" method will accept a "lexer" parameter
112 and, if no such parameter is given, an object of the
113 "HTML::Mason::Lexer" class should be constructed.
114
115 We implement a bit of magic here, so that if
116 "HTML::Mason::Compiler->new()" is called with a "lexer_class" parameter,
117 it will load the indicated class (presumably a subclass of
118 "HTML::Mason::Lexer"), instantiate a new object of that class, and use
119 it for the Compiler's "lexer" object. We're also smart enough to notice
120 if parameters given to "HTML::Mason::Compiler->new()" actually should go
121 to the "lexer" contained object, and it will make sure that they get
122 passed along.
123
124 Furthermore, an object may be declared as "delayed", which means that an
125 object *won't* be created when its containing class is constructed.
126 Instead, these objects will be created "on demand", potentially more
127 than once. The constructors will still enjoy the automatic passing of
128 parameters to the correct class. See the "create_delayed_object()" for
129 more.
130
131 To declare an object as "delayed", call this method like this:
132
133 __PACKAGE__->contained_objects( train => { class => 'Big::Train',
134 delayed => 1 } );
135
136 __PACKAGE__->valid_params(...)
137 Specifies the parameters accepted by this class's "new()" method as a
138 set of key/value pairs. Any parameters accepted by a superclass/subclass
139 will also be accepted, as well as any parameters accepted by contained
140 objects. This method is a get/set accessor method, so it returns a
141 reference to a hash of these key/value pairs. As a special case, if you
142 wish to set the valid params to an empty set and you previously set it
143 to a non-empty set, you may call "__PACKAGE__->valid_params(undef)".
144
145 "valid_params()" is called with a hash that contains parameter names as
146 its keys and validation specifications as values. This validation
147 specification is largely the same as that used by the "Params::Validate"
148 module, because we use "Params::Validate" internally.
149
150 As an example, consider the following situation:
151
152 use Class::Container;
153 use Params::Validate qw(:types);
154 __PACKAGE__->valid_params
155 (
156 allow_globals => { type => ARRAYREF, parse => 'list', default => [] },
157 default_escape_flags => { type => SCALAR, parse => 'string', default => '' },
158 lexer => { isa => 'HTML::Mason::Lexer' },
159 preprocess => { type => CODEREF, parse => 'code', optional => 1 },
160 postprocess_perl => { type => CODEREF, parse => 'code', optional => 1 },
161 postprocess_text => { type => CODEREF, parse => 'code', optional => 1 },
162 );
163
164 __PACKAGE__->contained_objects( lexer => 'HTML::Mason::Lexer' );
165
166 The "type", "default", and "optional" parameters are part of the
167 validation specification used by "Params::Validate". The various
168 constants used, "ARRAYREF", "SCALAR", etc. are all exported by
169 "Params::Validate". This means that any of these six parameter names,
170 plus the "lexer_class" parameter (because of the "contained_objects()"
171 specification given earlier), are valid arguments to the Compiler's
172 "new()" method.
173
174 Note that there are also some "parse" attributes declared. These have
175 nothing to do with "Class::Container" or "Params::Validate" - any extra
176 entries like this are simply ignored, so you are free to put extra
177 information in the specifications as long as it doesn't overlap with
178 what "Class::Container" or "Params::Validate" are looking for.
179
180 $self->create_delayed_object()
181 If a contained object was declared with "delayed => 1", use this method
182 to create an instance of the object. Note that this is an object method,
183 not a class method:
184
185 my $foo = $self->create_delayed_object('foo', ...); # YES!
186 my $foo = __PACKAGE__->create_delayed_object('foo', ...); # NO!
187
188 The first argument should be a key passed to the "contained_objects()"
189 method. Any additional arguments will be passed to the "new()" method of
190 the object being created, overriding any parameters previously passed to
191 the container class constructor. (Could I possibly be more alliterative?
192 Veni, vedi, vici.)
193
194 $self->delayed_object_params($name, [params])
195 Allows you to adjust the parameters that will be used to create any
196 delayed objects in the future. The first argument specifies the "name"
197 of the object, and any additional arguments are key-value pairs that
198 will become parameters to the delayed object.
199
200 When called with only a $name argument and no list of parameters to set,
201 returns a hash reference containing the parameters that will be passed
202 when creating objects of this type.
203
204 $self->delayed_object_class($name)
205 Returns the class that will be used when creating delayed objects of the
206 given name. Use this sparingly - in most situations you shouldn't care
207 what the class is.
208
209 __PACKAGE__->decorates()
210 Version 0.09 of Class::Container added [as yet experimental] support for
211 so-called "decorator" relationships, using the term as defined in
212 *Design Patterns* by Gamma, et al. (the Gang of Four book). To declare a
213 class as a decorator of another class, simply set @ISA to the class
214 which will be decorated, and call the decorator class's "decorates()"
215 method.
216
217 Internally, this will ensure that objects are instantiated as
218 decorators. This means that you can mix & match extra add-on
219 functionality classes much more easily.
220
221 In the current implementation, if only a single decoration is used on an
222 object, it will be instantiated as a simple subclass, thus avoiding a
223 layer of indirection.
224
225 $self->validation_spec()
226 Returns a hash reference suitable for passing to the "Params::Validate"
227 "validate" function. Does *not* include any arguments that can be passed
228 to contained objects.
229
230 $class->allowed_params(\%args)
231 Returns a hash reference of every parameter this class will accept,
232 *including* parameters it will pass on to its own contained objects. The
233 keys are the parameter names, and the values are their corresponding
234 specifications from their "valid_params()" definitions. If a parameter
235 is used by both the current object and one of its contained objects, the
236 specification returned will be from the container class, not the
237 contained.
238
239 Because the parameters accepted by "new()" can vary based on the
240 parameters *passed* to "new()", you can pass any parameters to the
241 "allowed_params()" method too, ensuring that the hash you get back is
242 accurate.
243
244 $self->container()
245 Returns the object that created you. This is remembered by storing a
246 reference to that object, so we use the "Scalar::Utils" "weakref()"
247 function to avoid persistent circular references that would cause memory
248 leaks. If you don't have "Scalar::Utils" installed, we don't make these
249 references in the first place, and calling "container()" will result in
250 a fatal error.
251
252 If you weren't created by another object via "Class::Container",
253 "container()" returns "undef".
254
255 In most cases you shouldn't care what object created you, so use this
256 method sparingly.
257
258 $object->show_containers
259 $package->show_containers
260 This method returns a string meant to describe the containment
261 relationships among classes. You should not depend on the specific
262 formatting of the string, because I may change things in a future
263 release to make it prettier.
264
265 For example, the HTML::Mason code returns the following when you do
266 "$interp->show_containers":
267
268 HTML::Mason::Interp=HASH(0x238944)
269 resolver -> HTML::Mason::Resolver::File
270 compiler -> HTML::Mason::Compiler::ToObject
271 lexer -> HTML::Mason::Lexer
272 request -> HTML::Mason::Request (delayed)
273 buffer -> HTML::Mason::Buffer (delayed)
274
275 Currently, containment is shown by indentation, so the Interp object
276 contains a resolver and a compiler, and a delayed request (or several
277 delayed requests). The compiler contains a lexer, and each request
278 contains a delayed buffer (or several delayed buffers).
279
280 $object->dump_parameters
281 Returns a hash reference containing a set of parameters that should be
282 sufficient to re-create the given object using its class's "new()"
283 method. This is done by fetching the current value for each declared
284 parameter (i.e. looking in $object for hash entries of the same name),
285 then recursing through all contained objects and doing the same.
286
287 A few words of caution here. First, the dumped parameters represent the
288 *current* state of the object, not the state when it was originally
289 created.
290
291 Second, a class's declared parameters may not correspond exactly to its
292 data members, so it might not be possible to recover the former from the
293 latter. If it's possible but requires some manual fudging, you can
294 override this method in your class, something like so:
295
296 sub dump_parameters {
297 my $self = shift;
298 my $dump = $self->SUPER::dump_parameters();
299
300 # Perform fudgery
301 $dump->{incoming} = $self->{_private};
302 delete $dump->{superfluous};
303 return $dump;
304 }
305
306 SEE ALSO
307 Params::Validate
308
309 AUTHOR
310 Originally by Ken Williams <ken@mathforum.org> and Dave Rolsky
311 <autarch@urth.org> for the HTML::Mason project. Important feedback
312 contributed by Jonathan Swartz <swartz@pobox.com>. Extended by Ken
313 Williams for the AI::Categorizer project.
314
315 Currently maintained by Ken Williams.
316
317 COPYRIGHT
318 This program is free software; you can redistribute it and/or modify it
319 under the same terms as Perl itself.
320
0 This file contains message digests of all files listed in MANIFEST,
1 signed via the Module::Signature module, version 0.38.
2
3 To verify the content in this distribution, first make sure you have
4 Module::Signature installed, then type:
5
6 % cpansign -v
7
8 It would check each file's integrity, as well as the signature's
9 validity. If "==> Signature verified OK! <==" is not displayed,
10 the distribution may already have been compromised, and you should
11 not run its Makefile.PL or Build.PL.
12
13 -----BEGIN PGP SIGNED MESSAGE-----
14 Hash: SHA1
15
16 SHA1 3b225b4d443775932c127fe00d5dddca58820aa6 Build.PL
17 SHA1 8cd28fa1763a93ffc196226642df8c584a2adc70 Changes
18 SHA1 1b386906514c738a00fcd4b1b7ba74ac1925c227 INSTALL
19 SHA1 6e250a09fdb471a3dcd7b9f267d4585dc2e5de18 MANIFEST
20 SHA1 976fabe5ba08d4f96ec5d764a30e3f37d46f4019 META.yml
21 SHA1 48dcc0a806041eaf9d020b5305d0c4bb32898f51 Makefile.PL
22 SHA1 336c070360a737f094645d80d4c82d5fd02ae973 README
23 SHA1 4286abb1ee51dcfa9868ccb754ec63b4ff684ad2 lib/Class/Container.pm
24 SHA1 39edf6c1802d4be568c18968ea5389965a0a6ff1 t/01-basic.t
25 SHA1 8154cdf85123d748ae210fa86a58e5484cb79275 t/02-decorator.t
26 SHA1 56e282a6a8379f3a3a42d5f78cd1682ab1cfc68b t/classes.pl
27 -----BEGIN PGP SIGNATURE-----
28 Version: GnuPG v1.2.2 (Darwin)
29
30 iD8DBQFB9GYigrvMBLfvlHYRAry6AJ0R722RV5sESZlMjvcID/M6ms/7HwCdGHfI
31 DhsUAU9sOA7srQVl1uZMJ90=
32 =oCuW
33 -----END PGP SIGNATURE-----
0 package Class::Container;
1
2 $VERSION = '0.12';
3 $VERSION = eval $VERSION if $VERSION =~ /_/;
4
5 my $HAVE_WEAKEN;
6 BEGIN {
7 eval {
8 require Scalar::Util;
9 Scalar::Util->import('weaken');
10 $HAVE_WEAKEN = 1;
11 };
12
13 *weaken = sub {} unless defined &weaken;
14 }
15
16 use strict;
17 use Carp;
18
19 # The create_contained_objects() method lets one object
20 # (e.g. Compiler) transparently create another (e.g. Lexer) by passing
21 # creator parameters through to the created object.
22 #
23 # Any auto-created objects should be declared in a class's
24 # %CONTAINED_OBJECTS hash. The keys of this hash are objects which
25 # can be created and the values are the default classes to use.
26
27 # For instance, the key 'lexer' indicates that a 'lexer' parameter
28 # should be silently passed through, and a 'lexer_class' parameter
29 # will trigger the creation of an object whose class is specified by
30 # the value. If no value is present there, the value of 'lexer' in
31 # the %CONTAINED_OBJECTS hash is used. If no value is present there,
32 # no contained object is created.
33 #
34 # We return the list of parameters for the creator. If contained
35 # objects were auto-created, their creation parameters aren't included
36 # in the return value. This lets the creator be totally ignorant of
37 # the creation parameters of any objects it creates.
38
39 use Params::Validate qw(:all);
40 Params::Validate::validation_options( on_fail => sub { die @_ } );
41
42 my %VALID_PARAMS = ();
43 my %CONTAINED_OBJECTS = ();
44 my %VALID_CACHE = ();
45 my %CONTAINED_CACHE = ();
46 my %DECORATEES = ();
47
48 sub new
49 {
50 my $proto = shift;
51 my $class = ref($proto) || $proto;
52 my $self = bless scalar validate_with
53 (
54 params => $class->create_contained_objects(@_),
55 spec => $class->validation_spec,
56 called => "$class->new()",
57 ), $class;
58 if ($HAVE_WEAKEN) {
59 my $c = $self->get_contained_object_spec;
60 foreach my $name (keys %$c) {
61 next if $c->{$name}{delayed};
62 $self->{$name}{container}{container} = $self;
63 weaken $self->{$name}{container}{container};
64 }
65 }
66 return $self;
67 }
68
69 sub all_specs
70 {
71 require B::Deparse;
72 my %out;
73
74 foreach my $class (sort keys %VALID_PARAMS)
75 {
76 my $params = $VALID_PARAMS{$class};
77
78 foreach my $name (sort keys %$params)
79 {
80 my $spec = $params->{$name};
81 my ($type, $default);
82 if ($spec->{isa}) {
83 my $obj_class;
84
85 $type = 'object';
86
87 if (exists $CONTAINED_OBJECTS{$class}{$name}) {
88 $default = "$CONTAINED_OBJECTS{$class}{$name}{class}->new";
89 }
90 } else {
91 ($type, $default) = ($spec->{parse}, $spec->{default});
92 }
93
94 if (ref($default) eq 'CODE') {
95 $default = 'sub ' . B::Deparse->new()->coderef2text($default);
96 $default =~ s/\s+/ /g;
97 } elsif (ref($default) eq 'ARRAY') {
98 $default = '[' . join(', ', map "'$_'", @$default) . ']';
99 } elsif (ref($default) eq 'Regexp') {
100 $type = 'regex';
101 $default =~ s,^\(\?(\w*)-\w*:(.*)\),/$2/$1,;
102 $default = "qr$default";
103 }
104 unless ($type) {
105 # Guess from the validation spec
106 $type = ($spec->{type} & ARRAYREF ? 'list' :
107 $spec->{type} & SCALAR ? 'string' :
108 $spec->{type} & CODEREF ? 'code' :
109 $spec->{type} & HASHREF ? 'hash' :
110 undef); # Oh well
111 }
112
113 my $descr = $spec->{descr} || '(No description available)';
114 $out{$class}{valid_params}{$name} = { type => $type,
115 pv_type => $spec->{type},
116 default => $default,
117 descr => $descr,
118 required => defined $default || $spec->{optional} ? 0 : 1,
119 public => exists $spec->{public} ? $spec->{public} : 1,
120 };
121 }
122
123 $out{$class}{contained_objects} = {};
124 next unless exists $CONTAINED_OBJECTS{$class};
125 my $contains = $CONTAINED_OBJECTS{$class};
126
127 foreach my $name (sort keys %$contains)
128 {
129 $out{$class}{contained_objects}{$name}
130 = {map {$_, $contains->{$name}{$_}} qw(class delayed descr)};
131 }
132 }
133
134 return %out;
135 }
136
137 sub dump_parameters {
138 my $self = shift;
139 my $class = ref($self) || $self;
140
141 my %params;
142 foreach my $param (keys %{ $class->validation_spec }) {
143 next if $param eq 'container';
144 my $spec = $class->validation_spec->{$param};
145 if (ref($self) and defined $self->{$param}) {
146 $params{$param} = $self->{$param};
147 } else {
148 $params{$param} = $spec->{default} if exists $spec->{default};
149 }
150 }
151
152 foreach my $name (keys %{ $class->get_contained_object_spec }) {
153 next unless ref($self);
154 my $contained = ($self->{container}{contained}{$name}{delayed} ?
155 $self->delayed_object_class($name) :
156 $params{$name});
157
158 my $subparams = UNIVERSAL::isa($contained, __PACKAGE__) ? $contained->dump_parameters : {};
159
160 my $more = $self->{container}{contained}{$name}{args} || {};
161 $subparams->{$_} = $more->{$_} foreach keys %$more;
162
163 @params{ keys %$subparams } = values %$subparams;
164 delete $params{$name};
165 }
166 return \%params;
167 }
168
169 sub show_containers {
170 my $self = shift;
171 my $name = shift;
172 my %args = (indent => '', @_);
173
174 $name = defined($name) ? "$name -> " : "";
175
176 my $out = "$args{indent}$name$self";
177 $out .= " (delayed)" if $args{delayed};
178 $out .= "\n";
179 return $out unless $self->isa(__PACKAGE__);
180
181 my $specs = ref($self) ? $self->{container}{contained} : $self->get_contained_object_spec;
182
183 while (my ($name, $spec) = each %$specs) {
184 my $class = $args{args}{"${name}_class"} || $spec->{class};
185 $self->_load_module($class);
186
187 if ($class->isa(__PACKAGE__)) {
188 $out .= $class->show_containers($name,
189 indent => "$args{indent} ",
190 args => $spec->{args},
191 delayed => $spec->{delayed});
192 } else {
193 $out .= "$args{indent} $name -> $class\n";
194 }
195 }
196
197 return $out;
198 }
199
200 sub _expire_caches {
201 %VALID_CACHE = %CONTAINED_CACHE = ();
202 }
203
204 sub valid_params {
205 my $class = shift;
206 if (@_) {
207 $class->_expire_caches;
208 $VALID_PARAMS{$class} = @_ == 1 && !defined($_[0]) ? {} : {@_};
209 }
210 return $VALID_PARAMS{$class} ||= {};
211 }
212
213 sub contained_objects
214 {
215 my $class = shift;
216 $class->_expire_caches;
217 $CONTAINED_OBJECTS{$class} = {};
218 while (@_) {
219 my ($name, $spec) = (shift, shift);
220 $CONTAINED_OBJECTS{$class}{$name} = ref($spec) ? $spec : { class => $spec };
221 }
222 }
223
224 sub _decorator_AUTOLOAD {
225 my $self = shift;
226 no strict 'vars';
227 my ($method) = $AUTOLOAD =~ /([^:]+)$/;
228 return if $method eq 'DESTROY';
229 die qq{Can't locate object method "$method" via package $self} unless ref($self);
230 my $subr = $self->{_decorates}->can($method)
231 or die qq{Can't locate object method "$method" via package } . ref($self);
232 unshift @_, $self->{_decorates};
233 goto $subr;
234 }
235
236 sub _decorator_CAN {
237 my ($self, $method) = @_;
238 return $self->SUPER::can($method) if $self->SUPER::can($method);
239 if (ref $self) {
240 return $self->{_decorates}->can($method) if $self->{_decorates};
241 return undef;
242 } else {
243 return $DECORATEES{$self}->can($method);
244 }
245 }
246
247 sub decorates {
248 my ($class, $super) = @_;
249
250 no strict 'refs';
251 $super ||= ${$class . '::ISA'}[0];
252
253 # Pass through unknown method invocations
254 *{$class . '::AUTOLOAD'} = \&_decorator_AUTOLOAD;
255 *{$class . '::can'} = \&_decorator_CAN;
256
257 $DECORATEES{$class} = $super;
258 $VALID_PARAMS{$class}{_decorates} = { isa => $super, optional => 1 };
259 }
260
261 sub container {
262 my $self = shift;
263 die "The ", ref($self), "->container() method requires installation of Scalar::Util" unless $HAVE_WEAKEN;
264 return $self->{container}{container};
265 }
266
267 sub call_method {
268 my ($self, $name, $method, @args) = @_;
269
270 my $class = $self->contained_class($name)
271 or die "Unknown contained item '$name'";
272
273 $self->_load_module($class);
274 return $class->$method( %{ $self->{container}{contained}{$name}{args} }, @args );
275 }
276
277 # Accepts a list of key-value pairs as parameters, representing all
278 # parameters taken by this object and its descendants. Returns a list
279 # of key-value pairs representing *only* this object's parameters.
280 sub create_contained_objects
281 {
282 # Typically $self doesn't exist yet, $_[0] is a string classname
283 my $class = shift;
284
285 my $c = $class->get_contained_object_spec;
286 return {@_, container => {}} unless %$c or $DECORATEES{$class};
287
288 my %args = @_;
289
290 if ($DECORATEES{$class}) {
291 # Fix format
292 $args{decorate_class} = [$args{decorate_class}]
293 if $args{decorate_class} and !ref($args{decorate_class});
294
295 # Figure out which class to decorate
296 my $decorate;
297 if (my $c = $args{decorate_class}) {
298 $decorate = @$c ? shift @$c : undef;
299 delete $args{decorate_class} unless @$c;
300 }
301 $c->{_decorates} = { class => $decorate } if $decorate;
302 }
303
304 # This one is special, don't pass to descendants
305 my $container_stuff = delete($args{container}) || {};
306
307 keys %$c; # Reset the iterator - why can't I do this in get_contained_object_spec??
308 my %contained_args;
309 my %to_create;
310
311 while (my ($name, $spec) = each %$c) {
312 # Figure out exactly which class to make an object of
313 my ($contained_class, $c_args) = $class->_get_contained_args($name, \%args);
314 @contained_args{ keys %$c_args } = (); # Populate with keys
315 $to_create{$name} = { class => $contained_class,
316 args => $c_args };
317 }
318
319 while (my ($name, $spec) = each %$c) {
320 # This delete() needs to be outside the previous loop, because
321 # multiple contained objects might need to see it
322 delete $args{"${name}_class"};
323
324 if ($spec->{delayed}) {
325 $container_stuff->{contained}{$name} = $to_create{$name};
326 $container_stuff->{contained}{$name}{delayed} = 1;
327 } else {
328 $args{$name} ||= $to_create{$name}{class}->new(%{$to_create{$name}{args}});
329 $container_stuff->{contained}{$name}{class} = ref $args{$name};
330 }
331 }
332
333 # Delete things that we're not going to use - things that are in
334 # our contained object specs but not in ours.
335 my $my_spec = $class->validation_spec;
336 delete @args{ grep {!exists $my_spec->{$_}} keys %contained_args };
337 delete $c->{_decorates} if $DECORATEES{$class};
338
339 $args{container} = $container_stuff;
340 return \%args;
341 }
342
343 sub create_delayed_object
344 {
345 my ($self, $name) = (shift, shift);
346 croak "Unknown delayed item '$name'" unless $self->{container}{contained}{$name}{delayed};
347
348 if ($HAVE_WEAKEN) {
349 push @_, container => {container => $self};
350 weaken $_[-1]->{container};
351 }
352 return $self->call_method($name, 'new', @_);
353 }
354
355 sub delayed_object_class
356 {
357 my $self = shift;
358 my $name = shift;
359 croak "Unknown delayed item '$name'"
360 unless $self->{container}{contained}{$name}{delayed};
361
362 return $self->{container}{contained}{$name}{class};
363 }
364
365 sub contained_class
366 {
367 my ($self, $name) = @_;
368 croak "Unknown contained item '$name'"
369 unless my $spec = $self->{container}{contained}{$name};
370 return $spec->{class};
371 }
372
373 sub delayed_object_params
374 {
375 my ($self, $name) = (shift, shift);
376 croak "Unknown delayed object '$name'"
377 unless $self->{container}{contained}{$name}{delayed};
378
379 if (@_ == 1) {
380 return $self->{container}{contained}{$name}{args}{$_[0]};
381 }
382
383 my %args = @_;
384
385 if (keys %args)
386 {
387 @{ $self->{container}{contained}{$name}{args} }{ keys %args } = values %args;
388 }
389
390 return %{ $self->{container}{contained}{$name}{args} };
391 }
392
393 # Everything the specified contained object will accept, including
394 # parameters it will pass on to its own contained objects.
395 sub _get_contained_args
396 {
397 my ($class, $name, $args) = @_;
398
399 my $spec = $class->get_contained_object_spec->{$name}
400 or croak "Unknown contained object '$name'";
401
402 my $contained_class = $args->{"${name}_class"} || $spec->{class};
403 croak "Invalid class name '$contained_class'"
404 unless $contained_class =~ /^[\w:]+$/;
405
406 $class->_load_module($contained_class);
407 return ($contained_class, {}) unless $contained_class->isa(__PACKAGE__);
408
409 my $allowed = $contained_class->allowed_params($args);
410
411 my %contained_args;
412 foreach (keys %$allowed) {
413 $contained_args{$_} = $args->{$_} if exists $args->{$_};
414 }
415 return ($contained_class, \%contained_args);
416 }
417
418 sub _load_module {
419 my ($self, $module) = @_;
420
421 unless ( eval { $module->can('new') } )
422 {
423 no strict 'refs';
424 eval "use $module";
425 croak $@ if $@;
426 }
427 }
428
429 sub allowed_params
430 {
431 my $class = shift;
432 my $args = ref($_[0]) ? shift : {@_};
433
434 # Strategy: the allowed_params of this class consists of the
435 # validation_spec of this class, merged with the allowed_params of
436 # all contained classes. The specific contained classes may be
437 # affected by arguments passed in, like 'interp' or
438 # 'interp_class'. A parameter like 'interp' doesn't add anything
439 # to our allowed_params (because it's already created) but
440 # 'interp_class' does.
441
442 my $c = $class->get_contained_object_spec;
443 my %p = %{ $class->validation_spec };
444
445 foreach my $name (keys %$c)
446 {
447 # Can accept a 'foo' parameter - should already be in the validation_spec.
448 # Also, its creation parameters should already have been extracted from $args,
449 # so don't extract any parameters.
450 next if exists $args->{$name};
451
452 # Figure out what class to use for this contained item
453 my $contained_class;
454 if ( exists $args->{"${name}_class"} ) {
455 $contained_class = $args->{"${name}_class"};
456 $p{"${name}_class"} = { type => SCALAR }; # Add to spec
457 } else {
458 $contained_class = $c->{$name}{class};
459 }
460
461 # We have to make sure it is loaded before we try calling allowed_params()
462 $class->_load_module($contained_class);
463 next unless $contained_class->can('allowed_params');
464
465 my $subparams = $contained_class->allowed_params($args);
466
467 foreach (keys %$subparams) {
468 $p{$_} ||= $subparams->{$_};
469 }
470 }
471
472 return \%p;
473 }
474
475 sub _iterate_ISA {
476 my ($class, $look_in, $cache_in, $add) = @_;
477
478 return $cache_in->{$class} if $cache_in->{$class};
479
480 my %out;
481
482 no strict 'refs';
483 foreach my $superclass (@{ "${class}::ISA" }) {
484 next unless $superclass->isa(__PACKAGE__);
485 my $superparams = $superclass->_iterate_ISA($look_in, $cache_in, $add);
486 @out{keys %$superparams} = values %$superparams;
487 }
488 if (my $x = $look_in->{$class}) {
489 @out{keys %$x} = values %$x;
490 }
491
492 @out{keys %$add} = values %$add if $add;
493
494 return $cache_in->{$class} = \%out;
495 }
496
497 sub get_contained_object_spec {
498 return (ref($_[0]) || $_[0])->_iterate_ISA(\%CONTAINED_OBJECTS, \%CONTAINED_CACHE);
499 }
500
501 sub validation_spec {
502 return (ref($_[0]) || $_[0])->_iterate_ISA(\%VALID_PARAMS, \%VALID_CACHE, { container => {type => HASHREF} });
503 }
504
505 1;
506
507 __END__
508
509 =head1 NAME
510
511 Class::Container - Glues object frameworks together transparently
512
513 =head1 SYNOPSIS
514
515 package Car;
516 use Class::Container;
517 @ISA = qw(Class::Container);
518
519 __PACKAGE__->valid_params
520 (
521 paint => {default => 'burgundy'},
522 style => {default => 'coupe'},
523 windshield => {isa => 'Glass'},
524 radio => {isa => 'Audio::Device'},
525 );
526
527 __PACKAGE__->contained_objects
528 (
529 windshield => 'Glass::Shatterproof',
530 wheel => { class => 'Vehicle::Wheel',
531 delayed => 1 },
532 radio => 'Audio::MP3',
533 );
534
535 sub new {
536 my $package = shift;
537
538 # 'windshield' and 'radio' objects are created automatically by
539 # SUPER::new()
540 my $self = $package->SUPER::new(@_);
541
542 $self->{right_wheel} = $self->create_delayed_object('wheel');
543 ... do any more initialization here ...
544 return $self;
545 }
546
547 =head1 DESCRIPTION
548
549 This class facilitates building frameworks of several classes that
550 inter-operate. It was first designed and built for C<HTML::Mason>, in
551 which the Compiler, Lexer, Interpreter, Resolver, Component, Buffer,
552 and several other objects must create each other transparently,
553 passing the appropriate parameters to the right class, possibly
554 substituting other subclasses for any of these objects.
555
556 The main features of C<Class::Container> are:
557
558 =over 4
559
560 =item *
561
562 Explicit declaration of containment relationships (aggregation,
563 factory creation, etc.)
564
565 =item *
566
567 Declaration of constructor parameters accepted by each member in a
568 class framework
569
570 =item *
571
572 Transparent passing of constructor parameters to the class
573 that needs them
574
575 =item *
576
577 Ability to create one (automatic) or many (manual) contained
578 objects automatically and transparently
579
580 =back
581
582 =head2 Scenario
583
584 Suppose you've got a class called C<Parent>, which contains an object of
585 the class C<Child>, which in turn contains an object of the class
586 C<GrandChild>. Each class creates the object that it contains.
587 Each class also accepts a set of named parameters in its
588 C<new()> method. Without using C<Class::Container>, C<Parent> will
589 have to know all the parameters that C<Child> takes, and C<Child> will
590 have to know all the parameters that C<GrandChild> takes. And some of
591 the parameters accepted by C<Parent> will really control aspects of
592 C<Child> or C<GrandChild>. Likewise, some of the parameters accepted
593 by C<Child> will really control aspects of C<GrandChild>. So, what
594 happens when you decide you want to use a C<GrandDaughter> class
595 instead of the generic C<GrandChild>? C<Parent> and C<Child> must be
596 modified accordingly, so that any additional parameters taken by
597 C<GrandDaughter> can be accommodated. This is a pain - the kind of
598 pain that object-oriented programming was supposed to shield us from.
599
600 Now, how can C<Class::Container> help? Using C<Class::Container>,
601 each class (C<Parent>, C<Child>, and C<GrandChild>) will declare what
602 arguments they take, and declare their relationships to the other
603 classes (C<Parent> creates/contains a C<Child>, and C<Child>
604 creates/contains a C<GrandChild>). Then, when you create a C<Parent>
605 object, you can pass C<< Parent->new() >> all the parameters for all
606 three classes, and they will trickle down to the right places.
607 Furthermore, C<Parent> and C<Child> won't have to know anything about
608 the parameters of its contained objects. And finally, if you replace
609 C<GrandChild> with C<GrandDaughter>, no changes to C<Parent> or
610 C<Child> will likely be necessary.
611
612 =head1 METHODS
613
614 =head2 new()
615
616 Any class that inherits from C<Class::Container> should also inherit
617 its C<new()> method. You can do this simply by omitting it in your
618 class, or by calling C<SUPER::new(@_)> as indicated in the SYNOPSIS.
619 The C<new()> method ensures that the proper parameters and objects are
620 passed to the proper constructor methods.
621
622 At the moment, the only possible constructor method is C<new()>. If
623 you need to create other constructor methods, they should call
624 C<new()> internally.
625
626 =head2 __PACKAGE__->contained_objects()
627
628 This class method is used to register what other objects, if any, a given
629 class creates. It is called with a hash whose keys are the parameter
630 names that the contained class's constructor accepts, and whose values
631 are the default class to create an object of.
632
633 For example, consider the C<HTML::Mason::Compiler> class, which uses
634 the following code:
635
636 __PACKAGE__->contained_objects( lexer => 'HTML::Mason::Lexer' );
637
638 This defines the relationship between the C<HTML::Mason::Compiler>
639 class and the class it creates to go in its C<lexer> slot. The
640 C<HTML::Mason::Compiler> class "has a" C<lexer>. The C<<
641 HTML::Mason::Compiler->new() >> method will accept a C<lexer>
642 parameter and, if no such parameter is given, an object of the
643 C<HTML::Mason::Lexer> class should be constructed.
644
645 We implement a bit of magic here, so that if C<<
646 HTML::Mason::Compiler->new() >> is called with a C<lexer_class>
647 parameter, it will load the indicated class (presumably a subclass of
648 C<HTML::Mason::Lexer>), instantiate a new object of that class, and
649 use it for the Compiler's C<lexer> object. We're also smart enough to
650 notice if parameters given to C<< HTML::Mason::Compiler->new() >>
651 actually should go to the C<lexer> contained object, and it will make
652 sure that they get passed along.
653
654 Furthermore, an object may be declared as "delayed", which means that
655 an object I<won't> be created when its containing class is constructed.
656 Instead, these objects will be created "on demand", potentially more
657 than once. The constructors will still enjoy the automatic passing of
658 parameters to the correct class. See the C<create_delayed_object()>
659 for more.
660
661 To declare an object as "delayed", call this method like this:
662
663 __PACKAGE__->contained_objects( train => { class => 'Big::Train',
664 delayed => 1 } );
665
666 =head2 __PACKAGE__->valid_params(...)
667
668 Specifies the parameters accepted by this class's C<new()> method as a
669 set of key/value pairs. Any parameters accepted by a
670 superclass/subclass will also be accepted, as well as any parameters
671 accepted by contained objects. This method is a get/set accessor
672 method, so it returns a reference to a hash of these key/value pairs.
673 As a special case, if you wish to set the valid params to an empty set
674 and you previously set it to a non-empty set, you may call
675 C<< __PACKAGE__->valid_params(undef) >>.
676
677 C<valid_params()> is called with a hash that contains parameter names
678 as its keys and validation specifications as values. This validation
679 specification is largely the same as that used by the
680 C<Params::Validate> module, because we use C<Params::Validate>
681 internally.
682
683 As an example, consider the following situation:
684
685 use Class::Container;
686 use Params::Validate qw(:types);
687 __PACKAGE__->valid_params
688 (
689 allow_globals => { type => ARRAYREF, parse => 'list', default => [] },
690 default_escape_flags => { type => SCALAR, parse => 'string', default => '' },
691 lexer => { isa => 'HTML::Mason::Lexer' },
692 preprocess => { type => CODEREF, parse => 'code', optional => 1 },
693 postprocess_perl => { type => CODEREF, parse => 'code', optional => 1 },
694 postprocess_text => { type => CODEREF, parse => 'code', optional => 1 },
695 );
696
697 __PACKAGE__->contained_objects( lexer => 'HTML::Mason::Lexer' );
698
699 The C<type>, C<default>, and C<optional> parameters are part of the
700 validation specification used by C<Params::Validate>. The various
701 constants used, C<ARRAYREF>, C<SCALAR>, etc. are all exported by
702 C<Params::Validate>. This means that any of these six parameter
703 names, plus the C<lexer_class> parameter (because of the
704 C<contained_objects()> specification given earlier), are valid
705 arguments to the Compiler's C<new()> method.
706
707 Note that there are also some C<parse> attributes declared. These
708 have nothing to do with C<Class::Container> or C<Params::Validate> -
709 any extra entries like this are simply ignored, so you are free to put
710 extra information in the specifications as long as it doesn't overlap
711 with what C<Class::Container> or C<Params::Validate> are looking for.
712
713 =head2 $self->create_delayed_object()
714
715 If a contained object was declared with C<< delayed => 1 >>, use this
716 method to create an instance of the object. Note that this is an
717 object method, not a class method:
718
719 my $foo = $self->create_delayed_object('foo', ...); # YES!
720 my $foo = __PACKAGE__->create_delayed_object('foo', ...); # NO!
721
722 The first argument should be a key passed to the
723 C<contained_objects()> method. Any additional arguments will be
724 passed to the C<new()> method of the object being created, overriding
725 any parameters previously passed to the container class constructor.
726 (Could I possibly be more alliterative? Veni, vedi, vici.)
727
728 =head2 $self->delayed_object_params($name, [params])
729
730 Allows you to adjust the parameters that will be used to create any
731 delayed objects in the future. The first argument specifies the
732 "name" of the object, and any additional arguments are key-value pairs
733 that will become parameters to the delayed object.
734
735 When called with only a C<$name> argument and no list of parameters to
736 set, returns a hash reference containing the parameters that will be
737 passed when creating objects of this type.
738
739 =head2 $self->delayed_object_class($name)
740
741 Returns the class that will be used when creating delayed objects of
742 the given name. Use this sparingly - in most situations you shouldn't
743 care what the class is.
744
745 =head2 __PACKAGE__->decorates()
746
747 Version 0.09 of Class::Container added [as yet experimental] support
748 for so-called "decorator" relationships, using the term as defined in
749 I<Design Patterns> by Gamma, et al. (the Gang of Four book). To
750 declare a class as a decorator of another class, simply set C<@ISA> to
751 the class which will be decorated, and call the decorator class's
752 C<decorates()> method.
753
754 Internally, this will ensure that objects are instantiated as
755 decorators. This means that you can mix & match extra add-on
756 functionality classes much more easily.
757
758 In the current implementation, if only a single decoration is used on
759 an object, it will be instantiated as a simple subclass, thus avoiding
760 a layer of indirection.
761
762 =head2 $self->validation_spec()
763
764 Returns a hash reference suitable for passing to the
765 C<Params::Validate> C<validate> function. Does I<not> include any
766 arguments that can be passed to contained objects.
767
768 =head2 $class->allowed_params(\%args)
769
770 Returns a hash reference of every parameter this class will accept,
771 I<including> parameters it will pass on to its own contained objects.
772 The keys are the parameter names, and the values are their
773 corresponding specifications from their C<valid_params()> definitions.
774 If a parameter is used by both the current object and one of its
775 contained objects, the specification returned will be from the
776 container class, not the contained.
777
778 Because the parameters accepted by C<new()> can vary based on the
779 parameters I<passed> to C<new()>, you can pass any parameters to the
780 C<allowed_params()> method too, ensuring that the hash you get back is
781 accurate.
782
783 =head2 $self->container()
784
785 Returns the object that created you. This is remembered by storing a
786 reference to that object, so we use the C<Scalar::Utils> C<weakref()>
787 function to avoid persistent circular references that would cause
788 memory leaks. If you don't have C<Scalar::Utils> installed, we don't
789 make these references in the first place, and calling C<container()>
790 will result in a fatal error.
791
792 If you weren't created by another object via C<Class::Container>,
793 C<container()> returns C<undef>.
794
795 In most cases you shouldn't care what object created you, so use this
796 method sparingly.
797
798 =head2 $object->show_containers
799
800 =head2 $package->show_containers
801
802 This method returns a string meant to describe the containment
803 relationships among classes. You should not depend on the specific
804 formatting of the string, because I may change things in a future
805 release to make it prettier.
806
807 For example, the HTML::Mason code returns the following when you do
808 C<< $interp->show_containers >>:
809
810 HTML::Mason::Interp=HASH(0x238944)
811 resolver -> HTML::Mason::Resolver::File
812 compiler -> HTML::Mason::Compiler::ToObject
813 lexer -> HTML::Mason::Lexer
814 request -> HTML::Mason::Request (delayed)
815 buffer -> HTML::Mason::Buffer (delayed)
816
817 Currently, containment is shown by indentation, so the Interp object
818 contains a resolver and a compiler, and a delayed request (or several
819 delayed requests). The compiler contains a lexer, and each request
820 contains a delayed buffer (or several delayed buffers).
821
822 =head2 $object->dump_parameters
823
824 Returns a hash reference containing a set of parameters that should be
825 sufficient to re-create the given object using its class's C<new()>
826 method. This is done by fetching the current value for each declared
827 parameter (i.e. looking in C<$object> for hash entries of the same
828 name), then recursing through all contained objects and doing the
829 same.
830
831 A few words of caution here. First, the dumped parameters represent
832 the I<current> state of the object, not the state when it was
833 originally created.
834
835 Second, a class's declared parameters may not correspond exactly to
836 its data members, so it might not be possible to recover the former
837 from the latter. If it's possible but requires some manual fudging,
838 you can override this method in your class, something like so:
839
840 sub dump_parameters {
841 my $self = shift;
842 my $dump = $self->SUPER::dump_parameters();
843
844 # Perform fudgery
845 $dump->{incoming} = $self->{_private};
846 delete $dump->{superfluous};
847 return $dump;
848 }
849
850 =head1 SEE ALSO
851
852 L<Params::Validate>
853
854 =head1 AUTHOR
855
856 Originally by Ken Williams <ken@mathforum.org> and Dave Rolsky
857 <autarch@urth.org> for the HTML::Mason project. Important feedback
858 contributed by Jonathan Swartz <swartz@pobox.com>. Extended by Ken
859 Williams for the AI::Categorizer project.
860
861 Currently maintained by Ken Williams.
862
863 =head1 COPYRIGHT
864
865 This program is free software; you can redistribute it and/or modify
866 it under the same terms as Perl itself.
867
868 =cut
0 #!/usr/bin/perl -w
1
2 # Note - I create a bunch of classes in these tests and then change
3 # their valid_params() and contained_objects() lists several times.
4 # This isn't really supported behavior of this module, but it's
5 # necessary to do it in the tests.
6
7 use strict;
8
9 use Test;
10
11 use Class::Container;
12 use Params::Validate qw(:types);
13 use File::Spec;
14 require File::Spec->catfile('t', 'classes.pl');
15
16 my $HAVE_WEAKEN = 0 + exists $INC{'Scalar/Util.pm'};
17
18 plan tests => 67 + 1*$HAVE_WEAKEN;
19
20 use Carp; $SIG{__DIE__} = \&Carp::confess;
21
22 eval {new Daughter(hair => 'long')};
23 ok $@, '', "Try making an object";
24
25 eval {new Parent()};
26 ok $@, '/mood/', "Should fail, missing required parameter";
27
28 my %args = (parent_val => 7,
29 mood => 'bubbly');
30
31 eval {new Parent(%args)};
32 ok $@, '', "Try creating top-level object";
33
34 my $mood = eval {Parent->new(%args)->{son}->{mood}};
35 ok $mood, 'bubbly';
36 ok $@, '', "Make sure sub-objects are created with proper values";
37
38 if ($HAVE_WEAKEN) {
39 my $p = Parent->new(%args);
40 ok $p->{son}->container, $p, "Container of son should be parent";
41 }
42
43 eval {my $p = new Parent(%args);
44 $p->create_delayed_object('daughter')};
45 ok $@, '', "Create a delayed object";
46
47 my $d = eval {Parent->new(%args)->create_delayed_object('daughter', hair => 'short')};
48 ok $@, '', "Create a delayed object with parameters";
49 ok $d->{hair}, 'short', "Make sure parameters are propogated to delayed object";
50
51 eval {new Daughter(foo => 'invalid')};
52 ok $@, '/Daughter/', "Make sure error messages contain the name of the class";
53
54 # Make sure we can override class names
55 {
56 ok my $p = eval {new Parent(mood => 'foo', parent_val => 1,
57 daughter_class => 'StepDaughter',
58 toy_class => 'Ball',
59 other_toys_class => 'Streamer',
60 son_class => 'StepSon')};
61 warn $@ if $@;
62
63 my $d = eval {$p->create_delayed_object('daughter')};
64 ok $@, '';
65
66 ok ref($d), 'StepDaughter';
67 ok ref($p->{son}), 'StepSon';
68
69 # Note - if one of these fails and the other succeeds, then we're
70 # not properly passing 'toy_class' to both son & daughter classes.
71 ok ref($d->{toy}), 'Ball';
72 ok ref($p->{son}{toy}), 'Ball';
73
74 ok $d->delayed_object_class('other_toys'), 'Streamer';
75 ok $p->{son}->delayed_object_class('other_toys'), 'Streamer';
76
77 # Special 'container' parameter shouldn't be shared among objects
78 ok ($p->{container} ne $p->{son}{container});
79
80 # Check some of the formatting of show_containers()
81 my $string = $p->show_containers;
82 ok $string, '/\n son -> StepSon/', $string;
83 }
84
85
86 {
87 # Check that subclass contained_objects override superclass
88
89 local @Superclass::ISA = qw(Class::Container);
90 local @Subclass::ISA = qw(Superclass);
91 'Superclass'->valid_params( foo => {isa => 'Foo'} );
92 'Subclass'->valid_params( foo => {isa => 'Bar'} );
93 'Superclass'->contained_objects( foo => 'Foo' );
94 'Subclass'->contained_objects( foo => 'Bar' );
95 local @Bar::ISA = qw(Foo);
96 sub Foo::new { bless {}, 'Foo' }
97 sub Bar::new { bless {}, 'Bar' }
98
99 my $child = 'Subclass'->new;
100 ok ref($child->{foo}), 'Bar', 'Subclass contained_object should override superclass';
101
102 my $spec = 'Subclass'->validation_spec;
103 ok $spec->{foo}{isa}, 'Bar';
104 }
105
106 {
107 local @Top::ISA = qw(Class::Container);
108 'Top'->valid_params( document => {isa => 'Document'} );
109 'Top'->contained_objects( document => 'Document',
110 collection => {class => 'Collection', delayed => 1} );
111
112 local @Collection::ISA = qw(Class::Container);
113 'Collection'->contained_objects( document => {class => 'Document', delayed => 1} );
114
115 local @Document::ISA = qw(Class::Container);
116 local @Document2::ISA = qw(Document);
117
118 my $k = new Top;
119 print $k->show_containers;
120 ok $k->contained_class('document'), 'Document';
121 my $collection = $k->create_delayed_object('collection');
122 ok ref($collection), 'Collection';
123 ok $collection->contained_class('document'), 'Document';
124
125 my $string = $k->show_containers;
126 ok $string, '/ collection -> Collection \(delayed\)/';
127 ok $string, '/ document -> Document \(delayed\)/';
128
129 my $k2 = new Top(document_class => 'Document2');
130 print $k2->show_containers;
131 ok $k2->contained_class('document'), 'Document2';
132 my $collection2 = $k2->create_delayed_object('collection');
133 ok ref($collection2), 'Collection';
134 ok $collection2->contained_class('document'), 'Document2';
135
136 my $string2 = $k2->show_containers;
137 ok $string2, '/ collection -> Collection \(delayed\)/';
138 ok $string2, '/ document -> Document2 \(delayed\)/';
139 }
140
141 {
142 local @Top::ISA = qw(Class::Container);
143 'Top'->valid_params( document => {isa => 'Document1'} );
144 'Top'->contained_objects( document => 'Document1' );
145
146 my $contained = 'Top'->get_contained_object_spec;
147 ok $contained->{document};
148 ok !$contained->{collection}; # Shouldn't have anything left over from the last block
149
150 local @Document1::ISA = qw(Class::Container);
151 'Document1'->valid_params( doc1 => {type => SCALAR} );
152
153 local @Document2::ISA = qw(Class::Container);
154 'Document2'->valid_params( doc2 => {type => SCALAR} );
155
156 my $allowed = 'Top'->allowed_params();
157 ok $allowed->{doc1};
158 ok !$allowed->{doc2};
159
160 $allowed = 'Top'->allowed_params( document_class => 'Document2' );
161 ok $allowed->{doc2};
162 ok !$allowed->{doc1};
163 }
164
165 {
166 local @Top::ISA = qw(Class::Container);
167 'Top'->_expire_caches;
168 'Top'->valid_params( document => {isa => 'Document1'} );
169 'Top'->contained_objects( document => 'Document1' );
170
171 local @Document1::ISA = qw(Class::Container);
172 'Document1'->valid_params();
173 local @Document2::ISA = qw(Document1);
174 'Document2'->valid_params();
175
176 my $t = new Top( document => bless {}, 'Document2' );
177 ok $t;
178 ok ref($t->{document}), 'Document2';
179 }
180
181 {
182 local @Top::ISA = qw(Class::Container);
183 'Top'->valid_params( document => {isa => 'Document'} );
184 'Top'->contained_objects( document => 'Document' );
185
186 local @Document::ISA = qw(Class::Container);
187 'Document'->valid_params( sub => {isa => 'Class::Container'} );
188 'Document'->contained_objects( sub => 'Sub1' );
189
190 local @Sub1::ISA = qw(Class::Container);
191 'Sub1'->valid_params( bar => {type => SCALAR} );
192 'Sub1'->contained_objects();
193
194 local @Sub2::ISA = qw(Class::Container);
195 'Sub2'->valid_params( foo => {type => SCALAR} );
196 'Sub2'->contained_objects();
197
198 my $allowed = 'Top'->allowed_params();
199 ok $allowed->{document};
200 ok $allowed->{bar};
201 ok !$allowed->{foo};
202
203 $allowed = 'Top'->allowed_params(sub_class => 'Sub2');
204 ok $allowed->{document};
205 ok !$allowed->{bar};
206 ok $allowed->{foo};
207 }
208
209 {
210 local @Top::ISA = qw(Class::Container);
211 Top->valid_params(foo => {type => SCALAR});
212 Top->contained_objects();
213
214 ok 'Top'->valid_params;
215 ok 'Top'->valid_params->{foo}{type}, SCALAR;
216 }
217
218 {
219 local @Top::ISA = qw(Class::Container);
220 Top->valid_params(foo => {type => SCALAR}, child => {isa => 'Child'});
221 Top->contained_objects(child => 'Child');
222
223 local @Child::ISA = qw(Class::Container);
224 Child->valid_params(bar => {type => SCALAR}, grand_child => {isa => 'GrandChild'});
225 Child->contained_objects(grand_child => 'GrandChild');
226
227 local @GrandChild::ISA = qw(Class::Container);
228 GrandChild->valid_params(baz => {type => SCALAR}, boo => {default => 5});
229 GrandChild->contained_objects();
230
231 local @GrandSibling::ISA = qw(GrandChild);
232
233 my $dump = GrandSibling->new(baz => 'BAZ')->dump_parameters;
234 ok keys(%$dump), 2;
235 ok $dump->{baz}, 'BAZ', "Sibling has baz=BAZ";
236 ok $dump->{boo}, 5, "Sibling has boo=5";
237
238 $dump = Child->new(bar => 'BAR', baz => 'BAZ')->dump_parameters;
239 ok keys(%$dump), 3;
240 ok $dump->{bar}, 'BAR';
241 ok $dump->{baz}, 'BAZ';
242
243 $dump = Child->new(bar => 'BAR', baz => 'BAZ', grand_child_class => 'GrandChild')->dump_parameters;
244 ok keys(%$dump), 3;
245 ok $dump->{bar}, 'BAR';
246 ok $dump->{baz}, 'BAZ';
247
248 $dump = Top->new(foo => 'FOO', bar => 'BAR', baz => 'BAZ')->dump_parameters;
249 ok keys(%$dump), 4;
250 ok $dump->{foo}, 'FOO';
251 ok $dump->{bar}, 'BAR';
252 ok $dump->{baz}, 'BAZ';
253
254
255 # Test default values in a delayed object
256 Top->valid_params(undef);
257 Top->contained_objects(child => {class => 'Child', delayed => 1});
258
259 Child->valid_params(bar => {default => 4});
260 Child->contained_objects();
261
262 $dump = Top->new()->dump_parameters;
263 ok keys(%$dump), 1;
264 ok $dump->{bar}, 4;
265
266 $dump = Top->new(bar => 6)->dump_parameters;
267 ok keys(%$dump), 1;
268 ok $dump->{bar}, 6;
269 }
270
271 {
272 # Make sure a later call to valid_params() clears the param list
273 local @Top::ISA = qw(Class::Container);
274 Top->valid_params(undef);
275 Top->contained_objects();
276
277 ok eval{ new Top };
278 }
279
280 {
281 # Make sure valid_params() gives sensible null output
282 local @Nonexistent::ISA = qw(Class::Container);
283 my $params = Nonexistent->valid_params;
284 ok ref($params), 'HASH';
285 ok keys(%$params), 0;
286 }
0 use strict;
1
2 use Test;
3 BEGIN { plan tests => 24 }
4
5 use Class::Container;
6 use Params::Validate qw(:types);
7 use File::Spec;
8 require File::Spec->catfile('t', 'classes.pl');
9
10
11
12 # Decorator stuff
13 {
14 local @Top::ISA = qw(Class::Container);
15 Top->valid_params(undef);
16 Top->contained_objects();
17 sub Top::foo { "foo" }
18
19 local @Decorator::ISA = qw(Top);
20 Decorator->decorates;
21 sub Decorator::bar { "bar" }
22
23 local @OtherDec::ISA = qw(Top);
24 OtherDec->decorates;
25 sub OtherDec::baz { "baz" }
26
27 # Make sure a simple 1-level decorator works
28 {
29 my $d = new Decorator;
30 ok $d;
31
32 ok $d->foo, 'foo';
33 ok $d->bar, 'bar';
34
35 # Should be using simple subclassing since it's just 1 level (no interface for this)
36 ok !$d->{_decorates};
37
38 # Make sure can() is correct
39 # Test.pm will run subrefs (don't want that), so make them booleans
40 ok !!$d->can('foo');
41 ok !!$d->can('bar');
42 ok !$d->can('baz');
43 }
44
45 # Try a 2-level decorator
46 {
47 my $d = new Decorator(decorate_class => 'OtherDec');
48 ok $d;
49
50 ok !!$d->can('foo');
51 ok !!$d->can('bar');
52 ok !!$d->can('baz');
53
54 ok $d->foo, 'foo';
55 ok $d->bar, 'bar';
56 ok $d->baz, 'baz';
57
58 # Make sure it's using decoration containment at top level, and subclassing below.
59 ok $d->{_decorates};
60 ok ref($d->{_decorates}), 'OtherDec';
61 ok !$d->{_decorates}{_decorates};
62 }
63
64 # Make sure arguments are passed correctly
65 Top->valid_params( one => { type => SCALAR } );
66 Decorator->valid_params( two => { type => SCALAR } );
67 Top->decorates;
68 Decorator->decorates;
69 OtherDec->decorates;
70 my $d = Decorator->new( one => 1, two => 2 );
71 ok $d;
72
73 $d = OtherDec->new( decorate_class => 'Decorator', one => 1, two => 2 );
74 ok $d;
75 ok $d->{one}, 1;
76 ok $d->{_decorates}{two}, 2;
77
78 $d = Decorator->new( decorate_class => 'OtherDec', one => 1, two => 2 );
79 ok $d;
80 ok $d->{one}, 1;
81 ok $d->{two}, 2;
82 }
83
0 use strict;
1
2 use Params::Validate qw(:types);
3 my $SCALAR = SCALAR; # So we don't have to keep importing it below
4
5 # Create some boilerplate classes
6 {
7 no strict 'refs';
8 foreach my $class (qw(Parent Boy Toy Daughter)) {
9 push @{$class.'::ISA'}, 'Class::Container';
10 }
11 }
12
13 # Define the relationships
14 {
15 package Parent;
16 push @Parent::ISA, 'Foo'; # Make sure it works with non-container superclasses
17 # Has one son and several daughters
18 __PACKAGE__->valid_params( parent_val => { type => $SCALAR },
19 son => {isa => 'Son'},
20 );
21 __PACKAGE__->contained_objects( son => 'Son',
22 daughter => {delayed => 1,
23 class => 'Daughter'});
24 }
25
26 {
27 package Boy;
28 __PACKAGE__->valid_params( eyes => { default => 'brown', type => $SCALAR },
29 toy => {isa => 'Toy'});
30 __PACKAGE__->contained_objects( toy => 'Slingshot',
31 other_toys => {class => 'Toy', delayed => 1},
32 );
33 }
34
35 {
36 package Son;
37 push @Son::ISA, 'Boy';
38 __PACKAGE__->valid_params( mood => { type => $SCALAR } );
39 }
40
41 {
42 package Slingshot;
43 push @Slingshot::ISA, 'Toy';
44 __PACKAGE__->valid_params( weapon => { default => 'rock', type => $SCALAR } );
45 }
46
47 {
48 package Daughter;
49 __PACKAGE__->valid_params( hair => { default => 'short' } );
50 }
51
52 {
53 package StepDaughter;
54 push @StepDaughter::ISA, 'Daughter';
55 __PACKAGE__->valid_params( toy => {isa => 'Toy'} );
56 __PACKAGE__->contained_objects( toy => { class => 'Toy'},
57 other_toys => {class => 'Toy', delayed => 1},
58 );
59 }
60 {
61 push @StepSon::ISA, 'Son';
62 push @Ball::ISA, 'Toy';
63 push @Streamer::ISA, 'Toy';
64 }
65
66 1;