File Coverage

blib/lib/Basset/Object.pm
Criterion Covered Total %
statement 528 548 96.3
branch 242 270 89.6
condition 85 118 72.0
subroutine 76 76 100.0
pod 43 44 97.7
total 974 1056 92.2


line stmt bran cond sub pod time code
1             package Basset::Object;
2              
3             #Basset::Object Copyright and (c) 1999, 2000, 2002-2006 James A Thomason III
4             #Basset::Object is distributed under the terms of the Perl Artistic License.
5              
6             =pod
7              
8             =head1 NAME
9              
10             Basset::Object - used to create objects
11              
12             =head1 AUTHOR
13              
14             Jim Thomason, jim@jimandkoka.com
15              
16             =head1 DESCRIPTION
17              
18             This is my ultimate object creation toolset to date. It has roots in Mail::Bulkmail, Text::Flowchart, and the
19             unreleased abstract object constructors that I've tooled around with in the past.
20              
21             If you want an object to be compatible with anything else I've written, then subclass it off of here.
22              
23             Of course, you don't have to use this to create subclasses, but you'll run the risk of making something with an inconsistent
24             interface vs. the rest of the system. That'll confuse people and make them unhappy. So I recommend subclassing off of here
25             to be consistent. Of course, you may not like these objects, but they do work well and are consistent. Consistency is
26             very important in interface design, IMHO.
27              
28             Please read the tutorials at L.
29              
30             =cut
31              
32             $VERSION = '1.03';
33              
34 714     714   6336 sub _conf_class {return 'Basset::Object::Conf'};
35 10     10   38282 BEGIN {eval 'use ' . _conf_class()};
  9     9   5289  
  9         52  
  9         739  
36              
37 8     9   15717 use Data::Dumper ();
  8         111702  
  9         1350  
38 8     8   79 use Carp;
  8         16  
  8         841  
39              
40 8     8   10563 use Basset::Container::Hash;
  8         20  
  8         260  
41              
42 8     8   50 use strict;
  8         17  
  8         234  
43 8     8   39 use warnings;
  8         18  
  8         918  
44              
45             =pod
46              
47             =head1 METHODS
48              
49             =over
50              
51             =item add_attr
52              
53             add_attr adds object attributes to the class.
54              
55             Okay, now we're going to get into some philosophy. First of all, let me state that I *love* Perl's OO implementation.
56             I usually get smacked upside the head when I say that, but I find it really easy to use, work with, manipulate, and so
57             on. And there are things that you can do in Perl's OO that you can't in Java or C++ or the like. Perl, for example, can
58             have *totally* private values that are completely inaccessible (lexicals, natch). private vars in the other languages
59             can be redefined or tweaked or subclassed or otherwise gotten around in some form. Not Perl.
60              
61             And I obviously just adore Perl anyway. I get funny looks when I tell people that I like perl so much because it works
62             the way I think. That bothers people for some reason.
63              
64             Anyway, as much as I like how it works, I don't like the fact that there's no consistent object type. An object is,
65             of course, a blessed ((thingie)) (scalar, array, code, hash, etc) reference. And there are merits to using any of those
66             things, depending upon the situation. Hashes are easy to work with and most similar to traditional objects.
67              
68             $object->{$attribute} = $value;
69              
70             And whatnot. Arrays are much faster (typically 33% in tests I've done), but they suck to work with.
71              
72             $object->[15] = $value; #the hell is '15'?
73              
74             (
75             by the way, you can make this easier with variables defined to return the value, i.e.
76             $object->[$attribute] = $value; #assuming $attribute == 15
77             )
78              
79             Scalars are speciality and coderefs are left to the magicians. Don't get me wrong, coderefs as objects are nifty, but
80             they can be tricky to work with.
81              
82             So, I wanted a consistent interface. I'm not going to claim credit for this idea, since I think I originally read it
83             in Object Oriented Programming in Perl (Damien's book). In fact, I think the error reporting method I use was also
84             originally detailed in there. Anyway, I liked it a lot and decided I'd implement my own version of it. Besides, it's
85             not like I'm the first guy to say that all attributes should be hidden behind mutators and accessors.
86              
87             Basically, attributes are accessed and mutated via methods.
88              
89             $object->attribute($value);
90              
91             For all attributes. This way, the internal object can be whatever you'd like. I used to use mainly arrays for the speed
92             boost, but lately I use hashes a lot because of the ease of dumping and reading the structure for debugging purposes.
93             But, with this consistent interface of using methods to wrapper the attributes, I can change the implementation of
94             the object (scalar, array, hash, code, whatever) up in this module and *nothing* else needs to change.
95              
96             Say you implemented a giant system in OO perl. And you chose hashrefs as your "object". But then you needed a big
97             speed boost later, which you could easily get by going to arrays. You'd have to go through your code and change all
98             instances of $object->{$attribute} to $object->[15] or whatever. That's an awful lot of work.
99              
100             With everything wrappered up this way, changes can be made in the super object class and then automagically populate
101             out everywhere with no code changes.
102              
103             Enough with the philosophy, though. You need to know how this works.
104              
105             It's easy enough:
106              
107             package Some::Class;
108              
109             Some::Class->add_attr('foo');
110              
111             Now your Some::Class objects have a foo attribute, which can be accessed as above. If called with a value, it's the mutator
112             which sets the attribute to the new value and returns the new value. If called without one, it's the accessor which
113             returns the value.
114              
115             my $obj = Some::Class->new();
116             $obj->foo('bar');
117             print $obj->foo(); #prints bar
118             print $obj->foo('boo'); #prints boo
119             print $obj->foo(); #prints boo
120             print $obj->foo('bang'); #prints bang
121             print $obj->foo; #prings bang
122              
123             add_attr calls should only be in your module. B. And they really should be defined up at the top.
124              
125             Internally, an add_attr call creates a function inside your package of the name of the attribute which reflects through
126             to the internal _isa_accessor method which handles the mutating and accessing.
127              
128             You may alternatively pass in a list of attributes, if you don't want to do so much typing.
129              
130             __PACKAGE__->add_attr( qw( foo bar baz ) );
131              
132             Gives you foo, bar, and baz attributes.
133              
134             There is another syntax for add_attr, to define a different internal accessor:
135              
136             Some::Class->add_attr(['foo', 'accessor_creator']);
137              
138             This creates method called 'foo' which talks to a separate accessor, in this case the closure returned by "accessor_creator" instead of a closure
139             returned by _isa_accessor. This is useful if you want to create a validating method on your attribute.
140              
141             Additionally, it creates a normal method going to _isa_accessor called '__b_foo', which is assumed to be the internal attribute
142             slot your other accessor with use. In general, for a given "attribute", "__b_attribute" will be created for internal use. Also please
143             note that you shouldn't ever create a method that starts with '__b_' (double underscore) since Basset reserves the right to automatically
144             create methods named in that fashion. You've been warned.
145              
146             "other_accessor" will get the object as the first arg (as always) and the name of the internal method as the second.
147              
148             A sample accessor_creator could look like this:
149              
150             Some::Class->add_attr(['foo', 'accessor_creator']);
151              
152             sub accessor_creator {
153             my $self = shift;
154             my $attribute = shift; #the external method name
155             my $prop = shift; #the internal "slot" that is a normal attribute
156              
157             #now we make our closure:
158             return sub {
159             my $self = shift;
160             if (@_) {
161             my $val = shift;
162             if ($val == 7) {
163             return $self->$prop($val);
164             }
165             else {
166             return $self->error("Cannot store value...must be 7!", "not_7");
167             }
168             }
169             else {
170             return $self->$prop();
171             }
172             }
173             }
174              
175             And, finally, you can also pass in additional arguments as static args if desired.
176              
177             Some::Class->add_attr(['foo', 'accessor_creator'], 'bar');
178              
179             $obj->foo('bee');
180              
181             sub accessor_creator {
182             my $self = shift;
183             my $method = shift;
184             my $static = shift; #'bar' in our example
185              
186             return sub {
187             #do something with static argument
188             .
189             .
190             }
191             };
192              
193             All easy enough. Refer to any subclasses of this class for further examples.
194              
195             Basset::Object includes two other alternate accessors for you - regex and private.
196              
197             Some::Class->add_attr(['user_id', '_isa_regex_accessor', qr{^\d+$}, "Error - user_id must be a number", "NaN"]);
198              
199             The arguments to it are, respectively, the name of the attribute, the internal accessor used, the regex used to validate, the error message to return, and the error code to return.
200             If you try to mutate with a value that doesn't match the regex, it'll fail.
201              
202             Some::Class->add_attr(['secret', '_isa_private_accessor']);
203              
204             private accessors add a slight degree of security. All they do is simply restrict access to the attribute unless you are within the class of the object. Note, that this causes
205             access to automatically trickle down into subclasses.
206              
207             =cut
208              
209             sub add_attr {
210 239     239 1 6890 my $pkg = shift;
211              
212 8     8   45 no strict 'refs';
  8         15  
  8         5754  
213              
214 239         508 foreach my $record (@_) {
215 240         264 my ($attribute, $adding_method, $internal_attribute, @args);
216 240 100       511 if (ref $record eq 'ARRAY') {
217 43         102 ($attribute, $adding_method, @args) = @$record;
218 43         158 $internal_attribute = $pkg->privatize($attribute);
219 42         168 *{$pkg . "::$internal_attribute"} = $pkg->_isa_accessor($internal_attribute, $attribute)
  43         1905  
220 43 100       57 unless *{$pkg . "::$internal_attribute"}{'CODE'};
221 42         199 *{$pkg . "::$attribute"} = $pkg->$adding_method($attribute, $internal_attribute, @args)
  43         375  
222 43 100       58 unless *{$pkg . "::$attribute"}{'CODE'};
223             }
224             else {
225 197         264 $attribute = $record;
226 197 100       216 *{$pkg . "::$record"} = $pkg->_isa_accessor($record) unless *{$pkg . "::$record"}{'CODE'};
  193         788  
  197         1712  
227             }
228              
229 240         714 $pkg->_instance_attributes->{$attribute}++;
230              
231             }
232              
233 239         602 return 1;
234              
235             }
236              
237             sub _isa_accessor {
238 235     235   333 my $pkg = shift;
239 235         279 my $attribute = shift;
240 235   66     814 my $prop = shift || $attribute;
241              
242             return sub {
243 1545     1545   42450 my $self = shift;
244              
245 1545 100       3565 return $self->error("Not a class attribute", "BO-08") unless ref $self;
246              
247 1507 100       4706 $self->{$prop} = shift if @_;
248              
249 1507         8362 $self->{$prop};
250 235         1214 };
251             }
252              
253             # _accessor is the main accessor method used in the system. It defines the most simple behavior as to how objects are supposed
254             # to work. If it's called with no arguments, it returns the value of that attribute. If it's called with arguments,
255             # it sets the object attribute value to the FIRST argument passed and ignores the rest
256             #
257             # example:
258             # my $object;
259             # print $object->attribute7(); #prints out the value of attribute7
260             # print $object->attribute7('foo'); #sets the value of attribute7 to 'foo', and prints 'foo'
261             # print $object->attribute7(); #prints out the value of attribute7, which is now known to be foo
262             #
263             # All internal accessor methods should behave similarly, read the documentation for add_attr for more information
264              
265             #tested w/ add_attr, above
266              
267             sub _isa_regex_accessor {
268 2     2   3 my $pkg = shift;
269 2         4 my $attribute = shift;
270 2         4 my $prop = shift;
271 2         3 my $regex = shift;
272 2         5 my $error = shift;
273 2         3 my $code = shift;
274              
275             return sub {
276 23     23   41 my $self = shift;
277 23 100       67 if (@_) {
278 19         30 my $val = shift;
279 19 100 100     194 return $self->error($error, $code) if defined $val && $val !~ /$regex/;
280              
281 9         29 return $self->$prop($val);
282             }
283             else {
284 4         12 return $self->$prop();
285             }
286 2         9 };
287             }
288              
289             sub _isa_private_accessor {
290 1     1   3 my $pkg = shift;
291 1         1 my $attribute = shift;
292 1         3 my $prop = shift;
293              
294             return sub {
295 3     3   5 my $self = shift;
296 3         12 my @caller = caller;
297 3 100       8 return $self->error("Cannot access $prop : private method", "BO-27") unless $caller[0] eq $self->pkg;
298              
299 2         8 $self->$prop(@_);
300 1         5 };
301              
302             }
303              
304             =pod
305              
306             =begin btest(add_attr)
307              
308             sub add_test_accessor {
309             my $pkg = shift;
310             my $attr = shift;
311             my $prop = shift;
312             my $extra = shift;
313              
314             no strict 'refs';
315              
316             return sub {
317             my $self = shift;
318             return $self->error("Not a class attribute", "BO-08") unless ref $self;
319             $extra;
320             };
321             }
322              
323             $test->ok(\&__PACKAGE__::test_accessor, "Added test accessor");
324              
325             my $o = __PACKAGE__->new();
326             $test->ok($o, "Object created");
327              
328             $test->ok(__PACKAGE__->add_attr('test_attribute1'), "Added attribute for _accessor");
329             $test->ok(__PACKAGE__->add_attr('test_attribute1'), "Re-added attribute for _accessor");
330             $test->ok($o->can('test_attribute1'), "Object sees attribute");
331             $test->ok(__PACKAGE__->can('test_attribute1'), "Class sees attribute");
332              
333             $test->is($o->test_attribute1('testval1'), 'testval1', "Method test_attribute1 mutates");
334             $test->is($o->test_attribute1(), 'testval1', "Method test_attribute1 accesses");
335             $test->is($o->test_attribute1(undef), undef, "Method test_attribute1 deletes");
336              
337             $test->is(scalar __PACKAGE__->test_attribute1('testval17'), undef, "Class fails to mutate");
338             $test->is(scalar __PACKAGE__->test_attribute1(), undef, "Class fails to access");
339             $test->is(scalar __PACKAGE__->test_attribute1(undef), undef, "Class fails to delete");
340              
341             $test->ok(__PACKAGE__->add_attr(['test_attribute2', 'add_test_accessor', 'excess']), "Added attribute for test_accessor");
342             $test->ok(__PACKAGE__->add_attr(['test_attribute2', 'add_test_accessor', 'excess']), "Re-added attribute for test_accessor");
343             $test->ok($o->can('test_attribute2'), "Object sees attribute");
344             $test->ok(__PACKAGE__->can('test_attribute2'), "Class sees attribute");
345              
346             $test->is($o->test_attribute2('testval2'), 'excess', "Method test_attribute2 mutates");
347             $test->is($o->test_attribute2(), 'excess', "Method test_attribute2 accesses");
348             $test->is($o->test_attribute2(undef), 'excess', "Method test_attribute2 deletes");
349              
350             $test->is(scalar __PACKAGE__->test_attribute2('testval18'), undef, "Class fails to mutate");
351             $test->is(scalar __PACKAGE__->test_attribute2(), undef, "Class fails to access");
352             $test->is(scalar __PACKAGE__->test_attribute2(undef), undef, "Class fails to delete");
353              
354             $test->ok(__PACKAGE__->add_attr('test_attribute3', 'static'), "Added static attribute");
355             $test->ok($o->can('test_attribute3'), "Object sees attribute");
356             $test->ok(__PACKAGE__->can('test_attribute3'), "Class sees attribute");
357              
358             $test->is($o->test_attribute3('status'), 'status', "Method test_attribute3 mutates");
359             $test->is($o->test_attribute3(), 'status', "Method test_attribute3 accesses");
360             $test->is($o->test_attribute3(undef), undef, "Method test_attribute3 deletes");
361              
362             $test->is(scalar __PACKAGE__->test_attribute3('testval19'), undef, "Class fails to mutate");
363             $test->is(scalar __PACKAGE__->test_attribute3(), undef, "Class fails to access");
364             $test->is(scalar __PACKAGE__->test_attribute3(undef), undef, "Class fails to delete");
365              
366             $test->ok(__PACKAGE__->add_attr(['test_attribute4', '_isa_regex_accessor', '^\d+$', 'Numbers only', 'test code']), "Added numeric only regex attribute");
367             $test->ok($o->can('test_attribute4'), "Object sees attribute");
368             $test->ok(__PACKAGE__->can('test_attribute4'), "Class sees attribute");
369              
370             $test->isnt(scalar $o->test_attribute4('foo'), 'foo', "Method test_attribute4 fails to set non-numeric");
371             $test->is($o->error, "Numbers only", "Proper object error message");
372             $test->is($o->errcode, "test code", "Proper object error code");
373             $test->isnt(scalar $o->test_attribute4('1234567890a'), '1234567890a', "Method test_attribute4 fails to set non-numeric");
374             $test->is($o->error, "Numbers only", "Proper object error message");
375             $test->is($o->errcode, "test code", "Proper object error code");
376             $test->isnt(scalar $o->test_attribute4('a1234567890'), 'a1234567890', "Method test_attribute4 fails to set non-numeric");
377             $test->is($o->error, "Numbers only", "Proper object error message");
378             $test->is($o->errcode, "test code", "Proper object error code");
379             $test->isnt(scalar $o->test_attribute4('123456a7890'), '123456a7890', "Method test_attribute4 fails to set non-numeric");
380             $test->is($o->error, "Numbers only", "Proper object error message");
381             $test->is($o->errcode, "test code", "Proper object error code");
382             $test->is(scalar $o->test_attribute4('12345'), '12345', "Method test_attribute4 mutates");
383             $test->is(scalar $o->test_attribute4(), '12345', "Method test_attribute4 accesses");
384             $test->is(scalar $o->test_attribute4(undef), undef, "Method test_attribute4 deletes");
385              
386             $test->is(scalar __PACKAGE__->test_attribute4('testval20'), undef, "Class fails to mutate");
387             $test->is(scalar __PACKAGE__->test_attribute4(), undef, "Class fails to access");
388             $test->is(scalar __PACKAGE__->test_attribute4(undef), undef, "Class fails to delete");
389              
390             $test->ok(__PACKAGE__->add_attr(['test_attribute5', '_isa_regex_accessor', 'abcD', 'Must contain abcD', 'test code2']), "Added abcD only regex attribute");
391             $test->ok($o->can('test_attribute5'), "Object sees attribute");
392             $test->ok(__PACKAGE__->can('test_attribute5'), "Class sees attribute");
393              
394             $test->isnt(scalar $o->test_attribute5('foo'), 'foo', "Method test_attribute4 fails to set non-abcD");
395             $test->is($o->error, "Must contain abcD", "Proper object error message");
396             $test->is($o->errcode, "test code2", "Proper object error code");
397             $test->isnt(scalar $o->test_attribute5('abc'), 'abc', "Method test_attribute4 fails to set non-abcD");
398             $test->is($o->error, "Must contain abcD", "Proper object error message");
399             $test->is($o->errcode, "test code2", "Proper object error code");
400             $test->isnt(scalar $o->test_attribute5('bcD'), 'bcD', "Method test_attribute4 fails to set non-abcD");
401             $test->is($o->error, "Must contain abcD", "Proper object error message");
402             $test->is($o->errcode, "test code2", "Proper object error code");
403             $test->isnt(scalar $o->test_attribute5('abD'), 'abD', "Method test_attribute4 fails to set non-abcD");
404             $test->is($o->error, "Must contain abcD", "Proper object error message");
405             $test->is($o->errcode, "test code2", "Proper object error code");
406             $test->is(scalar $o->test_attribute5('abcD'), 'abcD', "Method test_attribute5 mutates");
407             $test->is(scalar $o->test_attribute5('abcDE'), 'abcDE', "Method test_attribute5 mutates");
408             $test->is(scalar $o->test_attribute5('1abcD'), '1abcD', "Method test_attribute5 mutates");
409             $test->is(scalar $o->test_attribute5('zabcDz'), 'zabcDz', "Method test_attribute5 mutates");
410             $test->is(scalar $o->test_attribute5(), 'zabcDz', "Method test_attribute5 accesses");
411             $test->is(scalar $o->test_attribute5(undef), undef, "Method test_attribute5 deletes");
412              
413             $test->is(scalar __PACKAGE__->test_attribute5('testval20'), undef, "Class fails to mutate");
414             $test->is(scalar __PACKAGE__->test_attribute5(), undef, "Class fails to access");
415             $test->is(scalar __PACKAGE__->test_attribute5(undef), undef, "Class fails to delete");
416              
417             package Basset::Test::Testing::__PACKAGE__::add_attr::Subclass1;
418             our @ISA = qw(__PACKAGE__);
419              
420             my $sub_class = "Basset::Test::Testing::__PACKAGE__::add_attr::Subclass1";
421              
422             my $so = $sub_class->new();
423              
424             $test->ok(scalar $sub_class->add_attr(['secret', '_isa_private_accessor']), 'added secret accessor');
425             $test->ok($so->can('secret'), "Object sees secret attribute");
426             $test->is($so->secret('foobar'), 'foobar', 'Object sets secret attribute');
427             $test->is($so->secret(), 'foobar', 'Object sees secret attribute');
428              
429             package __PACKAGE__;
430              
431             $test->is(scalar $so->secret(), undef, 'Object cannot see secret attribute outside');
432             $test->is($so->errcode, 'BO-27', 'proper error code');
433              
434             =end btest(add_attr)
435              
436             =cut
437              
438             =pod
439              
440             =item add_class_attr
441              
442             This is similar to add_attr, but instead of adding object attributes, it adds class attributes. You B have
443             object and class attributes with the same name. This is by design. (error is a special case)
444              
445             Some::Class->add_attr('foo'); #object attribute foo
446             Some::Class->add_class_attr('bar'): #class attribute bar
447              
448             print $obj->foo();
449             print Some::Class->bar();
450              
451             Behaves the same as an object method added with add_attr, mutating with a value, accessing without one. Note
452             that add_class_attr does not have the capability for additional internal methods or static values. If you want
453             those on a class method, you'll have to wrapper the class attribute yourself on a per case basis.
454              
455             Note that you can access class attributes via an object (as expected), but it's frowned upon since it may be
456             confusing.
457              
458             class attributes are automatically initialized to any values in the conf file upon adding, if present.
459              
460             =cut
461              
462             =pod
463              
464             =begin btest(add_class_attr)
465              
466             my $o = __PACKAGE__->new();
467             $test->ok($o, "Object created");
468              
469             $test->ok(__PACKAGE__->add_class_attr('test_class_attribute_1'), "Added test class attribute");
470             $test->ok(__PACKAGE__->add_class_attr('test_class_attribute_1'), "Re-added test class attribute");
471             $test->ok($o->can("test_class_attribute_1"), "object can see class attribute");
472             $test->ok(__PACKAGE__->can("test_class_attribute_1"), "class can see class attribute");
473              
474             $test->is(__PACKAGE__->test_class_attribute_1('test value 1'), 'test value 1', 'class method call mutates');
475             $test->is(__PACKAGE__->test_class_attribute_1(), 'test value 1', 'class method call accesses');
476             $test->is(__PACKAGE__->test_class_attribute_1(undef), undef, 'class method call deletes');
477              
478             $test->is($o->test_class_attribute_1('test value 2'), 'test value 2', 'object method call mutates');
479             $test->is($o->test_class_attribute_1(), 'test value 2', 'object method call accesses');
480             $test->is($o->test_class_attribute_1(undef), undef, 'object method call deletes');
481              
482             $test->ok(__PACKAGE__->add_class_attr('test_class_attribute_2', 14), "Added test class attribute 2");
483             $test->ok($o->can("test_class_attribute_2"), "object can see class attribute");
484             $test->ok(__PACKAGE__->can("test_class_attribute_2"), "class can see class attribute");
485              
486             $test->is(__PACKAGE__->test_class_attribute_2(), 14, "Class has default arg");
487             $test->is(__PACKAGE__->test_class_attribute_2(), 14, "Object has default arg");
488              
489             $test->is(__PACKAGE__->test_class_attribute_2('test value 3'), 'test value 3', 'class method call mutates');
490             $test->is(__PACKAGE__->test_class_attribute_2(), 'test value 3', 'class method call accesses');
491             $test->is(__PACKAGE__->test_class_attribute_2(undef), undef, 'class method call deletes');
492              
493             $test->is($o->test_class_attribute_1('test value 4'), 'test value 4', 'class method call mutates');
494             $test->is($o->test_class_attribute_1(), 'test value 4', 'object method call accesses');
495             $test->is($o->test_class_attribute_1(undef), undef, 'object method call deletes');
496              
497             package Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1;
498             our @ISA = qw(__PACKAGE__);
499              
500             package __PACKAGE__;
501              
502             my $so = Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->new();
503             $test->ok($so, "Sub-Object created");
504              
505             $test->is(scalar __PACKAGE__->test_class_attribute_1("newer test val"), "newer test val", "trickle method class re-mutates");
506              
507             $test->is(scalar $so->test_class_attribute_1(), "newer test val", "trickle method sub-object accesses super");
508              
509             $test->is(scalar $so->test_class_attribute_1("testval3"), "testval3", "trickle method sub-object mutates");
510             $test->is(scalar $so->test_class_attribute_1(), "testval3", "trickle method sub-object accesses");
511             $test->is(scalar $so->test_class_attribute_1(undef), undef, "trickle method sub-object deletes");
512              
513             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->test_class_attribute_1("testval4"), "testval4", "trickle method class mutates");
514             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->test_class_attribute_1(), "testval4", "trickle method subclass accesses");
515             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->test_class_attribute_1(undef), undef, "trickle method subclass deletes");
516              
517             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->test_class_attribute_1("sub value"), "sub value", "Subclass re-mutates");
518             $test->is(scalar __PACKAGE__->test_class_attribute_1(), "sub value", "Super class affected on access");
519              
520             $test->is(scalar __PACKAGE__->test_class_attribute_1("super value"), "super value", "Super class re-mutates");
521             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->test_class_attribute_1(), "super value", "Sub class affected on access");
522              
523             package Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass5;
524             our @ISA = qw(__PACKAGE__);
525              
526             sub conf {
527             return undef;
528             };
529              
530             package __PACKAGE__;
531              
532             {
533              
534             local $@ = undef;
535              
536             eval {
537             Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass5->add_class_attr('test_class_attr');
538             };
539              
540             $test->like($@, qr/^Conf file error :/, 'could not add class attr w/o conf file');
541             }
542              
543             my $conf = __PACKAGE__->conf();
544             $conf->{'__PACKAGE__'}->{'_test_attribute'} = 'test value';
545              
546             $test->ok(__PACKAGE__->add_class_attr('_test_attribute'), 'added test attribute');
547             $test->is(__PACKAGE__->_test_attribute, 'test value', 'populated with value from conf fiel');
548              
549             =end btest(add_class_attr)
550              
551             =cut
552              
553             sub add_class_attr {
554 53     53 1 1550 my $pkg = shift;
555 53         90 my $method = shift;
556              
557 8     8   56 no strict 'refs';
  8         14  
  8         2428  
558              
559 53 100       66 return $method if *{$pkg . "::$method"}{'CODE'};
  53         402  
560              
561             #Slick. We'll use a proper closure here.
562 52         168 my $attr = undef;
563 52         789 *{$pkg . "::$method"} = sub {
564 271     271   4497 my $pkg = shift;
565 271 100       645 $attr = shift if @_;
566 271         2861 return $attr;
567 52         240 };
568              
569             #see if there's anything in the conf file
570              
571 52 100       277 my $conf = $pkg->conf or die "Conf file error : could not read conf file";
572              
573              
574 49 100       420 if (exists $conf->{$pkg}->{$method}){
    100          
575 1         5 $pkg->$method($conf->{$pkg}->{$method});
576             }
577             elsif (@_){
578 13         50 $pkg->$method(@_);
579             }
580              
581 49         143 $pkg->_class_attributes->{$method}++;
582              
583 49         152 return $method;
584             };
585              
586             =pod
587              
588             =item add_trickle_class_attr
589              
590             It's things like this why I really love Perl.
591              
592             add_trickle_class_attr behaves the same as add_class_attr with the addition that it will trickle the attribute down
593             into any class as it is called. This is useful for subclasses.
594              
595             Watch:
596              
597             package SuperClass;
598              
599             SuperClass->add_class_attr('foo');
600             SuperClass->foo('bar');
601              
602             package SubClass;
603             @ISA = qw(SuperClass);
604              
605             print SubClass->foo(); #prints bar
606             print SuperClass->foo(); #prints bar
607              
608             print SuperClass->foo('baz'); #prints baz
609             print SubClass->foo(); #prints baz
610              
611             print SubClass->foo('dee'); #prints dee
612             print SuperClass->foo(); #prints dee
613              
614             See? The attribute is still stored in the super class, so changing it in a subclass changes it in the super class as well.
615             Usually, this behavior is fine, but sometimes you don't want that to happen. That's where add_trickle_class_attr comes
616             in. Its first call will snag the value from the SuperClass, but then it will have its own attribute that's separate.
617              
618             Again, watch:
619              
620             package SuperClass;
621              
622             SuperClass->add_trickle_class_attr('foo');
623             SuperClass->foo('bar');
624              
625             package SubClass;
626             @ISA = qw(SuperClass);
627              
628             print SubClass->foo(); #prints bar
629             print SuperClass->foo(); #prints bar
630              
631             print SuperClass->foo('baz'); #prints baz
632             print SubClass->foo(); #prints bar
633              
634             print SubClass->foo('dee'); #prints dee (note we're setting the subclass here)
635             print SuperClass->foo(); #prints baz
636              
637             This is useful if you have an attribute that should be unique to a class and all subclasses. These are equivalent:
638              
639             package SuperClass;
640             SuperClass->add_class_attr('foo');
641              
642             package SubClass
643             SubClass->add_class_attr('foo');
644              
645             and
646              
647             package SuperClass;
648             SuperClass->add_trickle_class_attr('foo');
649              
650             You'll usually just use add_class_attr. Only use trickle_class_attr if you know you need to, since you rarely would.
651             There is a *slight* bit of additional processing required for trickled accessors.
652              
653             trickled class attributes are automatically initialized to any values in the conf file upon adding, if present.
654              
655             References are a special case. If you add a hashref, that hashref will automatically be tied to a Basset::Container::Hash.
656             Do not do this tying yourself, since bad things would occur. Once tied to Basset::Container::Hash, the hashref is now
657             effectively layered so that subclasses may directly add to the hash without affecting parent values. Subclasses may not delete
658             keys from the hash, only delete values they have added. Arrays are not tied.
659              
660             Sometimes, you may be required to access the attribute via a wrapper method.
661             For example:
662              
663             sub wrapper {
664             my $self = shift;
665              
666             my $existing = $self->trickled_ref();
667              
668             if (@_) {
669             my $dumped = $self->dump($existing); #take a dump of the ref
670             no strict; no warnings; #make sure nothing complains
671             $self->trickled_ref(eval $dump); #stick in a copy of it
672             }
673              
674             return $self->trickled_ref(@_);
675             }
676              
677             Then you need to access the trickled method through the wrapper you've created. I don't want to
678             add functionality like that into the add_trickle_class_attr method because I won't know when
679             the value needs to be changed. You're getting back a reference, but then manipulating the value
680             of the reference. So once you have a ref back, you immediately start changing the super class's
681             value. The only way that I could fix it up here is to constantly re-copy the reference on
682             every single access. But, of course, that then stops it from seeing changes in the super class,
683             which is inconsistent.
684              
685             Realistically, if you're using a ref and modifying it, you'll want wrapper methods to do things
686             like add values within the ref, delete values within the ref, etc, you'll rarely (if ever) access
687             the actual value of the ref directly. That is to say, you'll rarely change the hash pointed at,
688             you'll change keys within the hash. So add_foo, delete_foo, change_foo, etc. wrappers that properly
689             copy the hash as appropriate are the way to go. You can then still properly read the ref by
690             just using the trickled attribute as always.
691              
692             See the add_restrictions method below for an example of a wrapper like this.
693              
694             =cut
695              
696             sub add_trickle_class_attr {
697 249     249 1 969 my $internalpkg = shift;
698 249         362 my $method = shift;
699              
700 8     8   55 no strict 'refs';
  8         17  
  8         4479  
701              
702 249 100       344 return $method if *{$internalpkg . "::$method"}{'CODE'};
  249         2545  
703              
704 248         340 my $attr = undef;
705 248         635 my $initialized = {$internalpkg => 1};
706              
707 248         1173 *{$internalpkg . "::$method"} = sub {
708              
709 2149     2149   18181 my $class = shift->pkg;
710              
711 2149 100       5791 unless ($initialized->{$class}) {
712 161         377 $initialized->{$class}++;
713 161         640 my $local_conf = $class->conf('local');
714 161 50       553 if (defined (my $confval = $local_conf->{$method})) {
715 0         0 return $class->$method($confval);
716             };
717             }
718              
719 2149 100       4231 if (@_) {
720 517 100       1112 if ($class ne $internalpkg) {
721 56         325 $class->add_trickle_class_attr($method);
722 56         102 my $val = shift;
723              
724 56 50 66     189 if (ref $val eq 'HASH' && ref $attr eq 'HASH') {
725             #the tie blows away the values, so we need to keep a copy.
726 0         0 my %tmp;
727 0         0 @tmp{keys %$val} = values %$val;
728 0         0 tie %$val, 'Basset::Container::Hash', $attr;
729 0         0 $class->add_trickle_class_attr($method);
730 0         0 @$val{keys %tmp} = values %tmp;
731             }
732              
733 56         233 return $class->$method($val, @_);
734             }
735 461         765 $attr = shift;
736             }
737              
738 2093 100 100     25172 if (ref $attr eq 'HASH' && $class ne $internalpkg) {
739 100         706 tie my %empty, 'Basset::Container::Hash', $attr;
740 100         536 $class->add_trickle_class_attr($method, \%empty);
741 100         558 return $class->$method();
742             }
743              
744 1993         10653 return $attr;
745 248         1931 };
746              
747 248         720 my $conf = $internalpkg->conf;
748              
749 248 100       1014 if (defined (my $confval = $conf->{$internalpkg}->{$method})) {
    100          
750 8         31 $internalpkg->$method($confval);
751             }
752             elsif (@_) {
753 143         486 $internalpkg->$method(@_);
754             }
755              
756 248         1218 $internalpkg->_class_attributes->{$method}++;
757              
758 248         834 return $method;
759              
760             }
761              
762             =pod
763              
764             =begin btest(add_trickle_class_attr)
765              
766             my $o = __PACKAGE__->new();
767             $test->ok($o, "Object created");
768              
769             $test->ok(__PACKAGE__->add_trickle_class_attr('trick_attr1'), "Added test trickle class attribute");
770             $test->ok(__PACKAGE__->add_trickle_class_attr('trick_attr1'), "Re-added test trickle class attribute");
771             $test->ok($o->can("trick_attr1"), "object can see trickle class attribute");
772             $test->ok(__PACKAGE__->can("trick_attr1"), "class can see trickle class attribute");
773              
774             package Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1;
775             our @ISA = qw(__PACKAGE__);
776              
777             package __PACKAGE__;
778              
779             my $so = Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->new();
780             $test->ok($so, "Sub-Object created");
781              
782             $test->is(scalar $o->trick_attr1("testval1"), "testval1", "trickle method object mutates");
783             $test->is(scalar $o->trick_attr1(), "testval1", "trickle method object accesses");
784             $test->is(scalar $o->trick_attr1(undef), undef, "trickle method object deletes");
785              
786             $test->is(scalar __PACKAGE__->trick_attr1("testval2"), "testval2", "trickle method class mutates");
787             $test->is(scalar __PACKAGE__->trick_attr1(), "testval2", "trickle method class accesses");
788             $test->is(scalar __PACKAGE__->trick_attr1(undef), undef, "trickle method class deletes");
789             $test->is(scalar __PACKAGE__->trick_attr1("newer test val"), "newer test val", "trickle method class re-mutates");
790              
791             $test->is(scalar $so->trick_attr1(), "newer test val", "trickle method sub-object accesses super");
792              
793             $test->is(scalar $so->trick_attr1("testval3"), "testval3", "trickle method sub-object mutates");
794             $test->is(scalar $so->trick_attr1(), "testval3", "trickle method sub-object accesses");
795             $test->is(scalar $so->trick_attr1(undef), undef, "trickle method sub-object deletes");
796              
797             $test->is(scalar __PACKAGE__->trick_attr1("supertestval"), "supertestval", "super trickle method class mutates");
798             $test->is(__PACKAGE__->trick_attr1(), "supertestval", "trickle method class accesses");
799             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->trick_attr1("testval4"), "testval4", "trickle method class mutates");
800             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->trick_attr1(), "testval4", "trickle method subclass accesses");
801             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->trick_attr1(undef), undef, "trickle method subclass deletes");
802             $test->is(Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->trick_attr1(), undef, "subclass still sees undef as value");
803              
804             $test->is(scalar __PACKAGE__->trick_attr1("super value"), "super value", "Super class re-mutates");
805             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->trick_attr1("sub value"), "sub value", "Subclass re-mutates");
806              
807             $test->is(scalar __PACKAGE__->trick_attr1(), "super value", "Super class unaffected on access");
808             $test->is(scalar __PACKAGE__->trick_attr1("new super value"), "new super value", "Super class re-mutates");
809             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_class_attr::Subclass1->trick_attr1(), "sub value", "Sub class unaffected on access");
810              
811             package Basset::Test::Testing::__PACKAGE__::add_trickle_class_attr::Subclass5;
812             our @ISA = qw(__PACKAGE__);
813              
814             sub conf {
815             return undef;
816             };
817              
818             package __PACKAGE__;
819              
820             {
821             local $@ = undef;
822             eval {
823             Basset::Test::Testing::__PACKAGE__::add_trickle_class_attr::Subclass5->add_class_attr('test_trickle_attr');
824             };
825             $test->like($@, qr/^Conf file error :/, 'could not add trickle class attr w/o conf file');
826             }
827              
828             =end btest(add_trickle_class_attr)
829              
830             =cut
831              
832             =pod
833              
834             =item add_default_class_attr
835              
836             This adds a class attribute that is considered to be 'read-only' - it gets its value exclusively
837             and utterly only from the conf file. Any modifications to this value are discarded in favor of the
838             conf file value
839              
840             =cut
841              
842             sub add_default_class_attr {
843              
844 23     23 1 697 my $pkg = shift;
845 23         35 my $method = shift;
846              
847 8     8   55 no strict 'refs';
  8         18  
  8         5806  
848              
849 23 100       39 return $method if *{$pkg . "::$method"}{'CODE'};
  23         187  
850              
851             #Slick. We'll use a proper closure here.
852 22         42 my $attr = undef;
853 22         104 *{$pkg . "::$method"} = sub {
854              
855 169     169   710 my $class = shift;
856              
857 169 50       548 my $conf = $pkg->conf or die "Conf file error : could not read conf file";
858              
859 169 100       393 $conf->{$pkg}->{$method} = shift if @_;
860              
861 169         853 return $conf->{$pkg}->{$method};
862 22         102 };
863              
864 22         69 $pkg->_class_attributes->{$method}++;
865              
866 22         60 return $method;
867              
868             }
869              
870             =pod
871              
872             =begin btest(add_default_attr)
873              
874             package Basset::Test::Testing::__PACKAGE__::add_default_class_attr::subclass;
875             our @ISA = qw(__PACKAGE__);
876              
877             package __PACKAGE__;
878              
879             $test->ok(Basset::Test::Testing::__PACKAGE__::add_default_class_attr::subclass->add_default_class_attr('some_test_attr'), "Added default class attribute");
880             $test->ok(Basset::Test::Testing::__PACKAGE__::add_default_class_attr::subclass->add_default_class_attr('some_test_attr'), "Re-added default class attribute");
881              
882             package Basset::Test::Testing::__PACKAGE__::add_default_class_attr::Subclass5;
883             our @ISA = qw(__PACKAGE__);
884              
885             sub conf {
886             return undef;
887             };
888              
889             package __PACKAGE__;
890              
891             {
892             local $@ = undef;
893             eval {
894             Basset::Test::Testing::__PACKAGE__::add_default_class_attr::Subclass5->add_class_attr('test_default_attr');
895             };
896             $test->like($@, qr/^Conf file error :/, 'could not add default class attr w/o conf file');
897             }
898              
899             =end btest(add_default_attr)
900              
901             =cut
902              
903             =pod
904              
905             =item attributes
906              
907             Returns the attributes available to this object, based off of the flag passed in - "instance", "class", or "both".
908             defaults to "instance".
909              
910             Note - this method will not return attributes that begin with a leading underscore, as a courtesy.
911              
912             =cut
913              
914             sub attributes {
915 8     8 1 5896 my $class = shift->pkg;
916 8   100     30 my $type = shift || 'instance';
917              
918 8         13 my @attributes = ();
919              
920 8 100       28 if ($type eq 'instance') {
    100          
    100          
921 3         5 @attributes = keys %{$class->_instance_attributes};
  3         10  
922             }
923             elsif ($type eq 'class') {
924 2         5 @attributes = keys %{$class->_class_attributes};
  2         6  
925             }
926             elsif ($type eq 'both') {
927 2         4 @attributes = (keys %{$class->_instance_attributes}, keys %{$class->_class_attributes});
  2         10  
  2         7  
928             }
929             else {
930 1         15 return $class->error("Cannot get attributes - don't know how to get '$type'", "BO-37");
931             }
932              
933 7         31 return [sort grep {! /^_/} @attributes];
  127         296  
934             }
935              
936             =pod
937              
938             =begin btest(attributes)
939              
940             package Basset::Test::Testing::__PACKAGE__::attributes::Subclass1;
941             our @ISA = qw(__PACKAGE__);
942             my $subclass = "Basset::Test::Testing::__PACKAGE__::attributes::Subclass1";
943              
944             $subclass->add_attr('foo');
945             $subclass->add_attr('bar');
946             $subclass->add_class_attr('baz');
947             $subclass->add_trickle_class_attr('trick');
948              
949             $test->is(ref $subclass->attributes('instance'), 'ARRAY', 'instance attributes is array');
950             $test->is(ref $subclass->attributes('class'), 'ARRAY', 'class attributes is array');
951             $test->is(ref $subclass->attributes('both'), 'ARRAY', 'both attributes is array');
952             $test->is(scalar $subclass->attributes('invalid'), undef, 'non token attributes is error');
953             $test->is($subclass->errcode, 'BO-37', 'proper error code');
954              
955             my $instance = { map {$_ => 1} @{$subclass->attributes} };
956             $test->is($instance->{'foo'}, 1, 'foo is instance attribute from anon');
957             $test->is($instance->{'bar'}, 1, 'bar is instance attribute from anon');
958             $test->is($instance->{'baz'}, undef, 'baz is not instance attribute from anon');
959             $test->is($instance->{'trick'}, undef, 'trick is not instance attribute from anon');
960              
961             my $instance_ex = { map {$_ => 1} @{$subclass->attributes('instance')} };
962             $test->is($instance_ex->{'foo'}, 1, 'foo is instance attribute from explicit');
963             $test->is($instance_ex->{'bar'}, 1, 'bar is instance attribute from explicit');
964             $test->is($instance_ex->{'baz'}, undef, 'baz is not instance attribute from explicit');
965             $test->is($instance_ex->{'trick'}, undef, 'trick is not instance attribute from explicit');
966              
967             my $both = { map {$_ => 1} @{$subclass->attributes('both')} };
968             $test->is($both->{'foo'}, 1, 'foo is instance attribute from both');
969             $test->is($both->{'bar'}, 1, 'bar is instance attribute from both');
970             $test->is($both->{'baz'}, 1, 'baz is class attribute from both');
971             $test->is($both->{'trick'}, 1, 'trick is class attribute from both');
972              
973             my $class = { map {$_ => 1} @{$subclass->attributes('class')} };
974             $test->is($class->{'foo'}, undef, 'foo is not instance attribute from class');
975             $test->is($class->{'bar'}, undef, 'bar is not instance attribute from class');
976             $test->is($class->{'baz'}, 1, 'baz is class attribute from both');
977             $test->is($class->{'trick'}, 1, 'trick is class attribute from class');
978              
979             =end btest(attributes)
980              
981             =cut
982              
983             =pod
984              
985             =item is_attribute
986              
987             =cut
988              
989             sub is_attribute {
990 28     28 1 105 my $class = shift->pkg;
991 28         43 my $attribute = shift;
992 28   100     68 my $type = shift || 'instance';
993              
994 28 100       62 if ($type eq 'both') {
995 16   100     46 return $class->_instance_attributes->{$attribute} || $class->_class_attributes->{$attribute} || 0;
996             }
997 12 100       30 if ($type eq 'instance') {
    100          
998 8   100     18 return $class->_instance_attributes->{$attribute} || 0;
999             }
1000             elsif ($type eq 'class') {
1001 3   100     11 return $class->_class_attributes->{$attribute} || 0;
1002             }
1003             else {
1004 1         14 return $class->error("Cannot determine is_attribute for flag : $type", "BO-38");
1005             }
1006              
1007             }
1008              
1009             =pod
1010              
1011             =begin btest(is_attribute)
1012              
1013             package Basset::Test::Testing::__PACKAGE__::is_attribute::Subclass1;
1014             our @ISA = qw(__PACKAGE__);
1015             my $subclass = "Basset::Test::Testing::__PACKAGE__::is_attribute::Subclass1";
1016              
1017             $subclass->add_attr('ins1');
1018             $subclass->add_attr('ins2');
1019             $subclass->add_class_attr('class');
1020             $subclass->add_trickle_class_attr('trick');
1021              
1022             $test->ok($subclass->is_attribute('ins1') != 0, 'ins1 is instance by default');
1023             $test->ok($subclass->is_attribute('ins2') != 0, 'ins2 is instance by default');
1024              
1025             $test->ok($subclass->is_attribute('ins1', 'instance') != 0, 'ins1 is instance by explicitly');
1026             $test->ok($subclass->is_attribute('ins2', 'instance') != 0, 'ins2 is instance by explicitly');
1027              
1028             $test->ok($subclass->is_attribute('class') == 0, 'class is not attribute by default');
1029             $test->ok($subclass->is_attribute('class', 'class') != 0, 'class is class attribute by default');
1030              
1031             $test->ok($subclass->is_attribute('trick') == 0, 'trick is not attribute by default');
1032             $test->ok($subclass->is_attribute('trick', 'class') != 0, 'trick is class attribute by default');
1033              
1034             $test->ok($subclass->is_attribute('ins1', 'both') != 0, 'ins1 is instance by both');
1035             $test->ok($subclass->is_attribute('ins2', 'both') != 0, 'ins2 is instance by both');
1036             $test->ok($subclass->is_attribute('trick', 'both') != 0, 'trick is class attribute by both');
1037             $test->ok($subclass->is_attribute('class', 'both') != 0, 'class is class attribute by both');
1038              
1039             $test->ok($subclass->is_attribute('fake_instance') == 0, 'fake_instance is not attribute by default');
1040             $test->ok($subclass->is_attribute('fake_instance','both') == 0, 'fake_instance is not attribute by both');
1041             $test->ok($subclass->is_attribute('fake_instance','instance') == 0, 'fake_instance is not attribute by instance');
1042             $test->ok($subclass->is_attribute('fake_instance','class') == 0, 'fake_instance is not attribute by class');
1043              
1044             $test->is(scalar $subclass->is_attribute('ins1', 'invalid'), undef, "invalid is_attribute flag is error condition");
1045             $test->is($subclass->errcode, "BO-38", "proper error code");
1046              
1047             =end btest(is_attribute)
1048              
1049             =cut
1050              
1051             =pod
1052              
1053             =item add_wrapper
1054              
1055             You can now wrapper methods with before and after hooks that will get executed before or after the method, as desired. Syntax is:
1056              
1057             $class->add_wrapper('(before|after)', 'method_name', 'wrapper_name');
1058              
1059             That is, either before or after method_name is called, call wrapper_name first. Before wrappers are good to change the
1060             values going into a method, after wrappers are good to change the values coming back out.
1061              
1062             For example,
1063              
1064             sub foo_wrapper {
1065             my $self = shift;
1066             my @args = @_; # (whatever was passed in to foo)
1067             print "I am executing foo!\n";
1068             return 1;
1069             }
1070              
1071             $class->add_wrapper('before', 'foo', 'foo_wrapper');
1072              
1073             Now, $class->foo() is functionally the same as:
1074              
1075             if ($class->foo_wrapper) {
1076             $class->foo();
1077             }
1078              
1079             Ditto for the after wrapper.
1080              
1081             if ($class->foo) {
1082             $class->after_foo_wrapper;
1083             }
1084              
1085             Wrappers are run in reverse add order. That is, wrappers added later are executed before wrappers added earlier.
1086             Wrappers are inherited in subclasses. Subclasses run all of their wrappers in reverse add order, then run all
1087             super class wrappers in reverse add order.
1088              
1089             Wrapper functions should return a true value upon success, or set an error upon failure.
1090              
1091             Performance hit is fairly negligible, since add_wrapper re-wires the symbol table. So be careful using this
1092             functionality with other methods that may re-wire the symbol table (such as Basset::Object::Persistent's _instantiating_accessor)
1093              
1094             See also the extended syntax for add_attr, and Basset::Object::Persistent's import_from_db and export_to_db methods
1095             for different places to add in hooks, as well as the delegate attribute, below, for another way to extend code.
1096              
1097             The performance hit for wrappers is reasonably small, but if a wrappered method is constantly being hit and the
1098             wrapping code isn't always used (for example, wrapping an attribute. If your wrapper only does anything
1099             upon mutation, it's wasteful, since the wrapper will still -always- be called), you can suffer badly. In those
1100             cases, an extended attribute or an explicit wrapper function of your own may be more useful. Please note that wrappers
1101             can only be defined on a per-method basis. If you want to re-use wrappers across multiple methods, you'll need your
1102             own wrapping mechanism. For example, using the extended attribute syntax to use a different accessor method.
1103              
1104             There is an optional fourth argument - the conditional operator. This is a method (or coderef called as a method) that
1105             is executed before the wrapper is called. If the conditional returns true, the wrapper is then executed. If the conditional
1106             returns false, the wrapper is not executed.
1107              
1108             Some::Class->add_wrapper('after', 'name', 'validation', sub {
1109             my $self = shift;
1110             return @_;
1111             } );
1112              
1113             That wrapper will only call the 'validation' method upon mutation (that is, when there are arguments passed) and
1114             not upon simple access.
1115              
1116             Subclasses may define additional wrapper types.
1117              
1118             Please don't wrapper attributes. Things may break if the attribute value is legitimately undef (normally an error condition). Instead,
1119             use the extended add_attr syntax to define a new accessor method for the attribute you wish to wrap. Or simply write your own subroutine
1120             and directly call a separately added attribute yourself.
1121              
1122             =cut
1123              
1124             sub add_wrapper {
1125              
1126 15     15 1 496 my $class = shift;
1127 15 100       49 my $type = shift or return $class->error("Cannot add wrapper w/o type", "BO-31");
1128 14 100       37 my $method = shift or return $class->error("Cannot add wrapper w/o attribute", "BO-32");
1129 13 100       33 my $wrapper = shift or return $class->error("Cannot add wrapper w/o wrapper", "BO-33");
1130 12   100     60 my $conditional = shift || 'no_op';
1131              
1132 12 100       115 return $class->error("Cannot add wrapper : class does not know how to $method", "BO-34")
1133             unless $class->can($method);
1134              
1135 11 100       92 return $class->error("Cannot add wrapper : $method is an attribute. Explicitly wrapper or use a new accessor method", "BO-39")
1136             if $class->is_attribute($method, 'both');
1137              
1138 10         66 my $private = $class->privatize("privately_wrappered_$method");
1139              
1140 8     8   62 no strict 'refs';
  8         27  
  8         408  
1141 8     8   42 no warnings;
  8         14  
  8         17730  
1142              
1143 10         17 my $ptr;
1144              
1145 10 100       9 if (*{$class . "::$method"}{'CODE'}) {
  10         54  
1146              
1147 9         12 *{$class . "::$private"} = *{$class . "::$method"}{'CODE'};
  9         75  
  9         31  
1148             #if it's local to us, we're carefully hiding the function, so we need to look
1149             #at an actual reference to the original
1150 9         11 $ptr = *{$class . "::$private"}{'CODE'};
  9         37  
1151             } else {
1152             #otherwise, we need to find out who owns it, and keep a soft pointer to it.
1153 1         2 my @parents = reverse @{$class->isa_path};
  1         5  
1154 1         3 foreach my $parent (@parents) {
1155 2 100       3 if (*{$parent . "::$method"}{'CODE'}) {
  2         12  
1156             # but, if it's the parent's, then we need to only point to the name of the method
1157             # in the parent's class. This allows the parent to add a wrapper on this method
1158             # after we do, and we still get it.
1159 1         3 $ptr = "${parent}::$method";
1160 1         4 last;
1161             }
1162             }
1163             }
1164              
1165             #of course, we can't do anything unless our wrapper is something the class can do, or it's an anonymous method
1166 10 100 100     138 return $class->error("Cannot add wrapper: Class cannot $wrapper", "BO-35")
1167             unless $class->can($wrapper) || ref $wrapper eq 'CODE';
1168              
1169 9 100       32 if ($type eq 'before') {
    100          
1170              
1171 7         46 *{$class . "::$method"} = sub {
1172 24     24   38 my $self = shift;
1173              
1174 24 100       85 if ($self->$conditional(@_)) {
1175 23 50       107 $self->$wrapper($ptr, @_) or return;
1176             }
1177              
1178 24         75 return $self->$ptr(@_);
1179              
1180             }
1181 7         41 }
1182             elsif ($type eq 'after') {
1183 1         7 *{$class . "::$method"} = sub {
1184 6     6   12 my $self = shift;
1185              
1186 6 50       19 my $rc = $self->$ptr(@_) or return;
1187              
1188 6 50       58 return $self->$conditional(@_) ? $self->$wrapper($ptr, $rc, @_) : $rc;
1189             }
1190 1         7 } else {
1191 1         6 return $class->error("Cannot add wrapper: unknown type $type", "BO-36");
1192             }
1193              
1194 8         41 return 1;
1195              
1196             }
1197              
1198             =pod
1199              
1200             =begin btest(add_wrapper)
1201              
1202             my $subclass = "Basset::Test::Testing::__PACKAGE__::add_wrapper";
1203             my $subclass2 = "Basset::Test::Testing::__PACKAGE__::add_wrapper2";
1204              
1205             package Basset::Test::Testing::__PACKAGE__::add_wrapper;
1206             our @ISA = qw(__PACKAGE__);
1207              
1208             $subclass->add_attr('attr1');
1209             $subclass->add_attr('attr2');
1210             $subclass->add_attr('before_wrapper');
1211             $subclass->add_attr('before_wrapper2');
1212             $subclass->add_attr('after_wrapper');
1213             $subclass->add_attr('after_wrapper2');
1214             $subclass->add_attr('code_wrapper');
1215              
1216             my ($meth1, $meth2, $meth3, $meth4);
1217              
1218             sub meth1 {
1219             my $self = shift;
1220             $meth1 = shift if @_;
1221             return $meth1;
1222             }
1223              
1224             sub meth2 {
1225             my $self = shift;
1226             $meth2 = shift if @_;
1227             return $meth2;
1228             }
1229              
1230             sub meth3 {
1231             my $self = shift;
1232             $meth3 = shift if @_;
1233             return $meth3;
1234             }
1235              
1236             sub meth4 {
1237             my $self = shift;
1238             $meth4 = shift if @_;
1239             return $meth4;
1240             }
1241              
1242             sub wrapper1 {shift->before_wrapper('set')};
1243              
1244             sub wrapper2 {
1245             $_[0]->before_wrapper('B4SET');
1246             $_[0]->before_wrapper2('set2');
1247             }
1248              
1249             sub wrapper3 {
1250             $_[0]->before_wrapper('ASET1');
1251             $_[0]->before_wrapper2('ASET2');
1252             return $_[2];
1253             }
1254              
1255             sub wrapper5 {
1256             $_[0]->before_wrapper('5-BSET1');
1257             $_[0]->before_wrapper2('5-BSET2');
1258             $_[0]->after_wrapper('5-ASET1');
1259             $_[0]->after_wrapper2('5-ASET2');
1260             }
1261              
1262             sub conditional_true {
1263             return 1;
1264             }
1265              
1266             sub conditional_false {
1267             my $self = shift;
1268             return $self->error("failed false condition", "conditional_false_error_code");
1269             }
1270              
1271             package Basset::Test::Testing::__PACKAGE__::add_wrapper2;
1272             our @ISA = ($subclass);
1273              
1274             sub wrapper4 {
1275             shift->after_wrapper('AWRAPPER');
1276             }
1277              
1278             package __PACKAGE__;
1279              
1280             $test->ok(! $subclass->add_wrapper, "Cannot add wrapper w/o type");
1281             $test->is($subclass->errcode, "BO-31", "proper error code");
1282              
1283             $test->ok(! $subclass->add_wrapper('before'), "Cannot add wrapper w/o attribute");
1284             $test->is($subclass->errcode, "BO-32", "proper error code");
1285              
1286             $test->ok(! $subclass->add_wrapper('before', 'bogus_wrapper'), "Cannot add wrapper w/o wrapper");
1287             $test->is($subclass->errcode, "BO-33", "proper error code");
1288              
1289             $test->ok(! $subclass->add_wrapper('before', 'bogus_attribute', 'bogus_wrapper'), "Cannot add wrapper: bogus attribute");
1290             $test->is($subclass->errcode, "BO-34", "proper error code");
1291              
1292             $test->ok(! $subclass->add_wrapper('before', 'attr2', 'bogus_wrapper'), "Cannot add wrapper: cannot wrapper attributes");
1293             $test->is($subclass->errcode, "BO-39", "proper error code");
1294              
1295             $test->ok(! $subclass->add_wrapper('before', 'meth2', 'bogus_wrapper'), "Cannot add wrapper: bogus wrapper");
1296             $test->is($subclass->errcode, "BO-35", "proper error code");
1297              
1298             $test->ok(! $subclass->add_wrapper('junk', 'meth2', 'wrapper1'), "Cannot add wrapper: bogus type");
1299             $test->is($subclass->errcode, "BO-36", "proper error code");
1300              
1301             $test->ok(scalar $subclass->add_wrapper('before', 'meth1', 'wrapper1'), "added wrapper to ref");
1302              
1303             my $o = $subclass->new();
1304             $test->ok($o, "got object");
1305              
1306             $test->is($o->before_wrapper, undef, "before_wrapper is undef");
1307             $test->is($o->meth1('foo'), 'foo', 'set meth1 to foo');
1308             $test->is($o->before_wrapper, 'set', 'before_wrapper is set');
1309              
1310             $test->is($o->before_wrapper(undef), undef, "before_wrapper is undef");
1311              
1312             $test->ok(scalar $subclass->add_wrapper('before', 'meth1', 'wrapper2'), "added wrapper to ref");
1313              
1314             $test->is($o->before_wrapper, undef, "before_wrapper is undef");
1315             $test->is($o->meth1('bar'), 'bar', 'set meth1 to baz');
1316             $test->is($o->before_wrapper, 'set', 'before_wrapper is set');
1317             $test->is($o->before_wrapper2, 'set2', 'before_wrapper2 is set2');
1318             $test->is($o->after_wrapper, undef, 'after_wrapper is undef');
1319             $test->is($o->after_wrapper2, undef, 'after_wrapper2 is undef');
1320              
1321             $test->is($o->before_wrapper(undef), undef, "before_wrapper is undef");
1322             $test->is($o->before_wrapper2(undef), undef, "before_wrapper2 is undef");
1323              
1324             $test->ok(scalar $subclass->add_wrapper('after', 'meth1', 'wrapper3'), "added after wrapper to ref");
1325              
1326             $test->is($o->before_wrapper, undef, "before_wrapper is undef");
1327             $test->is($o->meth1('baz'), 'baz', 'set meth1 to baz');
1328             $test->is($o->before_wrapper, 'ASET1', 'before_wrapper is ASET1');
1329             $test->is($o->before_wrapper2, 'ASET2', 'before_wrapper2 is ASET2');
1330              
1331             my $o2 = $subclass2->new();
1332             $test->ok($o2, "got sub object");
1333              
1334             $test->ok(scalar $subclass2->add_wrapper('before', 'meth1', 'wrapper4'), "added after wrapper to ref");
1335              
1336             $test->is($o2->before_wrapper, undef, "before_wrapper is undef");
1337             $test->is($o2->meth1('baz'), 'baz', 'set meth1 to baz');
1338             $test->is($o2->before_wrapper, 'ASET1', 'before_wrapper is ASET1');
1339             $test->is($o2->before_wrapper2, 'ASET2', 'before_wrapper2 is ASET2');
1340             $test->is($o2->after_wrapper, 'AWRAPPER', 'after_wrapper is AWRAPPER');
1341              
1342             $test->is($o->before_wrapper(undef), undef, "before_wrapper is undef");
1343             $test->is($o->before_wrapper2(undef), undef, "before_wrapper2 is undef");
1344             $test->is($o->after_wrapper(undef), undef, "after_wrapper2 is undef");
1345             $test->is($o->after_wrapper2(undef), undef, "after_wrapper2 is undef");
1346              
1347             $test->ok(scalar $subclass->add_wrapper('before', 'meth1', 'wrapper5'), "added before wrapper to ref");
1348              
1349             $test->is($o->before_wrapper, undef, "before_wrapper is undef");
1350             $test->is($o->meth1('bar'), 'bar', 'set meth1 to baz');
1351             $test->is($o->before_wrapper, 'ASET1', 'before_wrapper is set ASET1');
1352             $test->is($o->before_wrapper2, 'ASET2', 'before_wrapper2 is ASET2');
1353             $test->is($o->after_wrapper, '5-ASET1', 'after_wrapper is 5-ASET1');
1354             $test->is($o->after_wrapper2, '5-ASET2', 'after_wrapper2 is 5-ASET2');
1355              
1356             $test->is($o2->before_wrapper(undef), undef, "before_wrapper is undef");
1357             $test->is($o2->before_wrapper2(undef), undef, "before_wrapper2 is undef");
1358             $test->is($o2->after_wrapper(undef), undef, "after_wrapper2 is undef");
1359             $test->is($o2->after_wrapper2(undef), undef, "after_wrapper2 is undef");
1360              
1361             $test->is($o2->before_wrapper, undef, "before_wrapper is undef");
1362             $test->is($o2->meth1('bar'), 'bar', 'set meth1 to baz');
1363             $test->is($o2->before_wrapper, 'ASET1', 'before_wrapper is set ASET1');
1364             $test->is($o2->before_wrapper2, 'ASET2', 'before_wrapper2 is ASET2');
1365             $test->is($o2->after_wrapper, '5-ASET1', 'after_wrapper is 5-ASET1');
1366             $test->is($o2->after_wrapper2, '5-ASET2', 'after_wrapper2 is 5-ASET2');
1367              
1368             $test->is($o->before_wrapper(undef), undef, "before_wrapper is undef");
1369             $test->is($o->before_wrapper2(undef), undef, "before_wrapper2 is undef");
1370             $test->is($o->after_wrapper(undef), undef, "after_wrapper2 is undef");
1371             $test->is($o->after_wrapper2(undef), undef, "after_wrapper2 is undef");
1372              
1373             $test->is($o->before_wrapper, undef, "before_wrapper is undef");
1374             $test->is($o->meth1('bar'), 'bar', 'set meth1 to baz');
1375             $test->is($o->before_wrapper, 'ASET1', 'before_wrapper is set ASET1');
1376             $test->is($o->before_wrapper2, 'ASET2', 'before_wrapper2 is ASET2');
1377             $test->is($o->after_wrapper, '5-ASET1', 'after_wrapper is 5-ASET1');
1378             $test->is($o->after_wrapper2, '5-ASET2', 'after_wrapper2 is 5-ASET2');
1379              
1380             $test->ok(scalar $subclass->add_wrapper('before', 'meth1', sub {$_[0]->code_wrapper('SET CODE WRAP'); return 1}), 'added coderef wrapper');
1381             $test->is($o->meth1('code'), 'code', 'set meth1 to code');
1382             $test->is($o->code_wrapper, 'SET CODE WRAP', 'properly used coderef wrapper');
1383              
1384             $test->ok(scalar $subclass->add_wrapper('before', 'meth3', 'wrapper1', 'conditional_true'), "added conditional_true wrapper");
1385             $test->is($o->before_wrapper(undef), undef, "wiped out before_wrapper");
1386             $test->is($o->meth3('meth 3 val'), 'meth 3 val', 'properly set method 3 value');
1387             $test->is($o->before_wrapper, 'set', 'set before_wrapper');
1388              
1389             $test->ok(scalar $subclass->add_wrapper('before', 'meth4', 'wrapper1', 'conditional_false'), "added conditional_false wrapper");
1390             $test->is($o->before_wrapper(undef), undef, "wiped out before_wrapper");
1391             $test->is($o->meth4('meth 4 val'), 'meth 4 val', 'could not set method 4 value');
1392             $test->is($o->errcode, 'conditional_false_error_code', 'proper error code');
1393             $test->is($o->before_wrapper, undef, 'could not set before_wrapper');
1394              
1395             =end btest(add_wrapper)
1396              
1397             =cut
1398              
1399             =pod
1400              
1401             =item error and errcode
1402              
1403             error rocks. All error reporting is set and relayed through error. It's a standard accessor, and an *almost*
1404             standard mutator. The difference is that when used as a mutator, it returns undef instead of the value
1405             mutated to.
1406              
1407             If a method fails, it is expected to return undef and set error.
1408              
1409             example:
1410              
1411             sub someMethod {
1412             my $self = shift;
1413             my $value = shift;
1414              
1415             if ($value > 10){
1416             return 1; #success
1417             }
1418             else {
1419             return $self->error("Values must be greater than 10");
1420             };
1421             };
1422              
1423             $object->someMethod(15) || die $object->error; #succeeds
1424             $object->someMethod(5) || die $object->error; #dies with an error..."Values must be greater than 10"
1425              
1426             Be warned if your method can return '0', this is a valid successful return and shouldn't give an error.
1427             But most of the time, you're fine with "true is success, false is failure"
1428              
1429             As you can see in the example, we mutate the error attribute to the value passed, but it returns undef.
1430              
1431             However, error messages can change and can be difficult to parse. So we also have an error code, accessed
1432             by errcode. This is expected to be consistent and machine parseable. It is mutated by the second argument
1433             to ->error
1434              
1435             example:
1436              
1437             sub someMethod {
1438             my $self = shift;
1439             my $value = shift;
1440              
1441             if ($value > 10){
1442             return 1; #success
1443             }
1444             else {
1445             return $self->error("Values must be greater than 10", "ERR77");
1446             };
1447             };
1448              
1449             $object->someMethod(15) || die $object->error; #succeeds
1450             $object->someMethod(5) || die $object->errcode; #dies with an error code ... "ERR77"
1451              
1452             If your code is looking for an error, read the errcode. if a human is looking at it, display the error.
1453             Easy as pie.
1454              
1455             Both classes and objects have error methods.
1456              
1457             my $obj = Some::Class->new() || die Some::Class->error();
1458             $obj->foo() || die $obj->error();
1459              
1460             Note that error is a special method, and not just a normal accessor or class attribute. As such:
1461              
1462             my $obj = Some::Class->new();
1463             Some::Class->error('foo');
1464             print $obj->error(); #prints undef
1465             print Some::Class->error(); #prints foo
1466              
1467             i.e., you will B get a class error message by calling ->error on an object.
1468              
1469             error also posts an 'error' notification to the notification center. See Basset::NotificationCenter for more information.
1470             The notification will not be posted if the optional third "silently" parameter is passed.
1471              
1472             Some::Class->error('foo', 'foo_code', 'silently');
1473              
1474             ->error can (and will) die if an error occurs very very early in the compilation process, namely if an error
1475             occurs before the 'exceptions' attribute is defined. It is assumed that if an error occurs that early on, it's a very
1476             bad thing, and you should bail out.
1477              
1478             You may also always cause an exception by passing in the double plus secret fourth parameter - "throw anyway".
1479              
1480             Some::Class->error('foo', 'foo_code', 0, 'HOLY COW BAIL OUT NOW!');
1481              
1482             Use the throw anyway parameter with care. It should be reserved to cover coding errors. An issue that if it occurs, there
1483             is no way to continue and the programmer needs to fix it in advance. For example, _accessor throws an exception if you
1484             try to call it as a class method, and with good reason.
1485              
1486             =cut
1487              
1488             =pod
1489              
1490             =begin btest(error)
1491              
1492             my $notes = 0;
1493              
1494             sub notifier {
1495             my $self = shift;
1496             my $note = shift;
1497             $notes++;
1498             };
1499              
1500             my $center = __PACKAGE__->pkg_for_type('notificationcenter');
1501             $test->ok($center, "Got notification center class");
1502              
1503             $test->ok(
1504             scalar
1505             $center->addObserver(
1506             'observer' => '__PACKAGE__',
1507             'notification' => 'error',
1508             'object' => 'all',
1509             'method' => 'notifier'
1510             ), "Added observer for error notifications"
1511             );
1512              
1513             my $o = __PACKAGE__->new();
1514             $test->ok($o, "Object created");
1515              
1516             $test->is(scalar __PACKAGE__->error("classerr"), undef, "Class error set and returns undef");
1517             $test->is($notes, 1, "Posted a notification");
1518             $test->is(scalar __PACKAGE__->error(), 'classerr', "Class error accesses");
1519             $test->is($notes, 1, "No notification");
1520              
1521             $test->is(scalar __PACKAGE__->error("classerr2", "classcode2"), undef, "Class error and errcode set and returns undef");
1522             $test->is($notes, 2, "Posted a notification");
1523             $test->is(scalar __PACKAGE__->error(), 'classerr2', "Class error accesses");
1524             $test->is($notes, 2, "No notification");
1525             $test->is(scalar __PACKAGE__->errcode(), 'classcode2', "Class Class errcode accesses");
1526             $test->is($notes, 2, "No notification");
1527              
1528             $test->is(scalar $o->error("objerr"), undef, "Object error set and returns undef");
1529             $test->is($notes, 3, "Posted a notification");
1530             $test->is(scalar $o->error(), 'objerr', "Object error accesses");
1531             $test->is($notes, 3, "No notification");
1532             $test->is(scalar __PACKAGE__->error(), 'classerr2', "Class error unaffected");
1533             $test->is($notes, 3, "No notification");
1534              
1535             $test->is(scalar $o->error("objerr2", "objcode2"), undef, "Object error and errcode set and returns undef");
1536             $test->is($notes, 4, "Posted a notification");
1537             $test->is(scalar $o->error(), 'objerr2', "Object error accesses");
1538             $test->is($notes, 4, "No notification");
1539             $test->is(scalar $o->errcode(), 'objcode2', "Object errcode accesses");
1540             $test->is($notes, 4, "No notification");
1541             $test->is(scalar __PACKAGE__->error(), 'classerr2', "Class error unaffected");
1542             $test->is($notes, 4, "No notification");
1543             $test->is(scalar __PACKAGE__->errcode(), 'classcode2', "Class errcode unaffected");
1544             $test->is($notes, 4, "No notification");
1545              
1546             $test->is(scalar __PACKAGE__->error("classerr3", "clscode3"), undef, "Re-set class error");
1547             $test->is($notes, 5, "Posted notification");
1548             $test->is(scalar $o->error(), 'objerr2', "Object error unchanged");
1549             $test->is($notes, 5, "No notification");
1550             $test->is(scalar $o->errcode(), 'objcode2', "Object errcode unchanged");
1551             $test->is($notes, 5, "No notification");
1552              
1553             $test->is(scalar $o->error("objerr3", "objcode3", "silently"), undef, "Silently set error");
1554             $test->is($notes, 5, "No notification");
1555             $test->is(scalar $o->error(), 'objerr3', "Object error accesses");
1556             $test->is($notes, 5, "No notification");
1557             $test->is(scalar $o->errcode(), 'objcode3', "Object errcode accesses");
1558             $test->is($notes, 5, "No notification");
1559             $test->is(scalar __PACKAGE__->error(), 'classerr3', "Class error unaffected");
1560             $test->is($notes, 5, "No notification");
1561             $test->is(scalar __PACKAGE__->errcode(), 'clscode3', "Class errcode unaffected");
1562             $test->is($notes, 5, "No notification");
1563              
1564             $test->is(scalar $o->error(["formatted error %d %.2f %s", 13, 3.14, "data"], "ec", "silently"), undef, "Object set formatted error");
1565             $test->is(scalar $o->error, "formatted error 13 3.14 data", "Formatted error accesses");
1566             $test->is(scalar $o->errcode, "ec", "Formatted errcode accesses");
1567             $test->is(scalar __PACKAGE__->error(), 'classerr3', "Class error unaffected");
1568             $test->is($notes, 5, "No notification");
1569             $test->is(scalar __PACKAGE__->errcode(), 'clscode3', "Class errcode unaffected");
1570             $test->is($notes, 5, "No notification");
1571              
1572             my $confClass = __PACKAGE__->pkg_for_type('conf');
1573             $test->ok($confClass, "Got conf");
1574              
1575             my $cfg = $confClass->conf;
1576             $test->ok($cfg, "Got configuration");
1577              
1578             $test->ok($cfg->{"Basset::Object"}->{'exceptions'} = 1, "enables exceptions");
1579              
1580             eval {
1581             $o->error("exception error", "excpcode");
1582             };
1583             $test->ok($@ =~ /^excpcode /, "Caught object exception code");
1584             $test->is($o->last_exception, "exception error", "Caught object exception");
1585             $test->is(__PACKAGE__->last_exception, "exception error", "Caught class exception");
1586             $test->is($notes, 6, "Posted a notification");
1587              
1588             eval {
1589             __PACKAGE__->error("exception error 2", "excpcode2");
1590             };
1591              
1592             $test->ok($@ =~ /^excpcode2 /, "Caught object exception code2");
1593             $test->is($o->last_exception, "exception error 2", "Caught object exception");
1594             $test->is(__PACKAGE__->last_exception, "exception error 2", "Caught class exception");
1595             $test->is($notes, 7, "Posted a notification");
1596              
1597             eval {
1598             __PACKAGE__->error("exception error 3", "excpcode3", "silently");
1599             };
1600             $test->ok($@ =~ /^excpcode3/, "Caught object exception code3");
1601             $test->is($o->last_exception, "exception error 3", "Caught object exception");
1602             $test->is(__PACKAGE__->last_exception, "exception error 3", "Caught class exception");
1603             $test->is($notes, 7, "No notification");
1604              
1605             $test->is($cfg->{"Basset::Object"}->{'exceptions'} = 0, 0,"shut off exceptions");
1606              
1607             $test->ok(
1608             scalar
1609             $center->removeObserver(
1610             'observer' => '__PACKAGE__',
1611             'notification' => 'error',
1612             ), "Removed observer for error notifications"
1613             );
1614              
1615             package Basset::Test::Testing::__PACKAGE__::error::Subclass1;
1616             our @ISA = qw(__PACKAGE__);
1617              
1618             sub can {
1619             my $self = shift;
1620             my $method = shift;
1621             return 0 if $method =~ /_..._error/;
1622             return $self->SUPER::can($method);
1623             };
1624              
1625             package __PACKAGE__;
1626             {
1627             local $@ = undef;
1628              
1629             eval {
1630             Basset::Test::Testing::__PACKAGE__::error::Subclass1->error("some error");
1631             };
1632             $test->like($@, qr/^System start up failure/, 'Could not start system when cannot error');
1633             }
1634              
1635             package Basset::Test::Testing::__PACKAGE__::error::Subclass2;
1636             our @ISA = qw(__PACKAGE__);
1637              
1638             sub can {
1639             my $self = shift;
1640             my $method = shift;
1641             return 0 if $method =~ /_..._errcode/;
1642             return $self->SUPER::can($method);
1643             };
1644              
1645             package __PACKAGE__;
1646              
1647             {
1648             local $@ = undef;
1649              
1650             eval {
1651             Basset::Test::Testing::__PACKAGE__::error::Subclass2->error("some error");
1652             };
1653              
1654             $test->like($@, qr/^System start up failure/, 'Could not start system when cannot errcode');
1655              
1656             $test->is(scalar(Basset::Test::Testing::__PACKAGE__::error::Subclass2->error), undef, 'accessing error merely returns');
1657              
1658             }
1659              
1660             =end btest(error)
1661              
1662             =cut
1663              
1664             sub error {
1665 362     362 1 23423 my $self = shift;
1666              
1667 362 100       921 my $errormethod = ref $self ? "_obj_error" : "_pkg_error";
1668 362 100       843 my $codemethod = ref $self ? "_obj_errcode" : "_pkg_errcode";
1669              
1670             # just in case we have an error very early on, we have our escape pod here. If something bad has happened,
1671             # then just die. We cannot continue.
1672 362 100 100     4016 unless ($self->can($errormethod) && $self->can($codemethod)) {
1673 3 100       88 if (@_) {
1674 2         593 croak("System start up failure : @_");
1675             } else {
1676 1         6 return;
1677             }
1678             }
1679              
1680 359 100       1244 if (@_){
1681              
1682 152         541 $self->$errormethod(shift);
1683 152 100       630 $self->$codemethod(@_ ? shift : undef);
1684              
1685 152 100       413 if (defined $self->$errormethod()) {
1686              
1687 146         646 my $center = $self->pkg_for_type('notificationcenter', 'errorless');
1688              
1689 146   100     705 my $silently = shift || 0;
1690 146   50     581 my $throw_anyway = shift || 0;
1691 146 100       367 unless ($silently) {
1692 136 100 66     1527 if (defined $center && $center->can('postNotification')) {
1693 135         579 $center->postNotification(
1694             'notification' => 'error',
1695             'object' => $self,
1696             'args' => [$self->errvals],
1697             );
1698             }
1699             }
1700              
1701 146 50 33     1203 if ($self->can('exceptions') || $throw_anyway) {
1702 146 100 66     574 if ($self->exceptions && defined $self->$codemethod()) {
1703 5         37 $self->last_exception($self->$errormethod());
1704 5         15 croak($self->$codemethod());
1705             };
1706             #something went horribly wrong very early on. Die with something useful.
1707             } else {
1708 0         0 die $self->errstring;
1709             };
1710             }
1711              
1712 147         901 return;
1713             }
1714             else {
1715 207         543 my $err = $self->$errormethod();
1716 207 100 100     983 if (defined $err && ref $err eq 'ARRAY') {
1717 16         31 my $format = $err->[0];
1718 16 100       32 if (@$err > 1) {
1719 14         30 $err = sprintf($format, @{$err}[1..$#$err]);
  14         74  
1720             } else {
1721 2         8 $err = $format;
1722             };
1723             }
1724 207         932 return $err;
1725             #return $self->$errormethod();
1726             };
1727             };
1728              
1729             =pod
1730              
1731             =item rawerror
1732              
1733             If you're using a formatted error string, ->error will always return the formatted value to you.
1734             ->rawerror will return the formattable data.
1735              
1736             $obj->error('foo');
1737             print $obj->error(); #prints 'foo'
1738             print $obj->rawerror(); #prints 'foo'
1739              
1740             $obj->error(['foo %d', 77]);
1741             print $obj->error(); #prints 'foo 77'
1742             print $obj->rawerror(); #prints ARRAY0x1341 (etc.)
1743              
1744             =cut
1745              
1746             =pod
1747              
1748             =begin btest(rawerror)
1749              
1750             my $o = __PACKAGE__->new();
1751             $test->ok($o, "Object created");
1752              
1753             $test->is(scalar __PACKAGE__->error("raw class error", "roe"), undef, "Set class error");
1754             $test->is(scalar __PACKAGE__->rawerror(), "raw class error", "Class raw error accesses");
1755             $test->is(scalar __PACKAGE__->error(["raw class error %d"], "roe"), undef, "Set formatted class error");
1756             $test->is(ref __PACKAGE__->rawerror(), 'ARRAY', "Class formatted raw error accesses");
1757             $test->is(__PACKAGE__->rawerror()->[0], "raw class error %d", "Class formatted raw error accesses");
1758              
1759             $test->is(scalar $o->error("raw object error", "roe"), undef, "Set object error");
1760             $test->is(scalar $o->rawerror(), "raw object error", "Object raw error accesses");
1761             $test->is(scalar $o->error(["raw object error %d"], "roe"), undef, "Set formatted object error");
1762             $test->is(ref $o->rawerror(), 'ARRAY', "Object formatted raw error accesses");
1763             $test->is($o->rawerror()->[0], 'raw object error %d', "Object formatted raw error accesses");
1764             $test->ok(ref $o->rawerror() eq 'ARRAY', "Class formatted raw error unaffected");
1765             $test->is(__PACKAGE__->rawerror()->[0], "raw class error %d", "Class formatted raw error unaffected");
1766              
1767             =end btest(rawerror)
1768              
1769             =cut
1770              
1771             sub rawerror {
1772 15     15 1 28 my $self = shift;
1773 15 100       44 my $errormethod = ref $self ? "_obj_error" : "_pkg_error";
1774              
1775 15         46 return $self->$errormethod();
1776             }
1777              
1778             =pod
1779              
1780             =item errcode
1781              
1782             errcode is an accessor ONLY. You can only mutate the errcode via error, see above.
1783              
1784             print $obj->errcode;
1785              
1786             Both objects and classes have errcode methods.
1787              
1788             my $obj = Some::Class->new() || die Some::Class->errcode();
1789             $obj->foo() || die $obj->errcode
1790              
1791             Do not ever ever B define an error code that starts with "B". Those are reserved for framework
1792             error codes. Otherwise, standard C-style "namespace" conventions apply - give it a reasonably unique
1793             prefix. Preferrably one that helps people identify where the error was. I like to use the the initials
1794             of the module name.
1795              
1796             package Basset::Object::Persistent; #returns BOP-## error codes.
1797              
1798             =cut
1799              
1800             =pod
1801              
1802             =begin btest(errcode)
1803              
1804             $test->is(scalar __PACKAGE__->error("test error", "test code", "silently"), undef, "Class sets errcode");
1805             $test->is(scalar __PACKAGE__->errcode(), "test code", "Class accesses");
1806              
1807             =end btest(errcode)
1808              
1809             =cut
1810              
1811             sub errcode {
1812 254     254 1 2312 my $self = shift;
1813 254 100       559 my $method = ref $self ? "_obj_errcode" : "_pkg_errcode";
1814              
1815 254         799 return $self->$method(@_);
1816             };
1817              
1818             =pod
1819              
1820             =item errstring
1821              
1822             errstring is a convenience accessor, it returns the error and code concatenated.
1823              
1824             $obj->someMethod() || die $obj->errstring; #dies "Values must be greater than 10...with code(ERR77)"
1825              
1826             =cut
1827              
1828             =pod
1829              
1830             =begin btest(errstring)
1831              
1832             $test->is(scalar __PACKAGE__->error("test error", "test code"), undef, "Class sets error & errcode");
1833             $test->is(__PACKAGE__->errstring(), "test error...with code (test code)", "Class accesses errstring");
1834              
1835             $test->is(scalar __PACKAGE__->error("test error2", "test code2", "silently"), undef, "Class silently sets error & errcode");
1836             $test->is(__PACKAGE__->errstring(), "test error2...with code (test code2)", "Class accesses errstring");
1837              
1838             $test->is(scalar __PACKAGE__->error("test error3"), undef, "Class sets error & no errcode");
1839             $test->is(__PACKAGE__->errstring(), "test error3...with code (code undefined)", "Class accesses errstring");
1840              
1841             $test->is(scalar __PACKAGE__->error("test error4", undef, "silently"), undef, "Class silently sets error & no errcode");
1842             $test->is(__PACKAGE__->errstring(), "test error4...with code (code undefined)", "Class accesses errstring");
1843              
1844             __PACKAGE__->wipe_errors();
1845              
1846             $test->is(scalar(__PACKAGE__->errstring), undef, 'errcode returns nothing w/o error and errcode');
1847             __PACKAGE__->errcode('test code');
1848             $test->is(__PACKAGE__->errstring, 'error undefined...with code (test code)', 'errcode returns undefined w/o error');
1849              
1850             =end btest(errstring)
1851              
1852             =cut
1853              
1854             sub errstring {
1855 13     13 1 33 my $self = shift;
1856              
1857 13 100       49 if (defined $self->error) {
    100          
1858             return
1859 10 100       26 $self->error
1860             . "...with code (" .
1861             (defined $self->errcode ? $self->errcode : 'code undefined')
1862             . ")";
1863             } elsif (defined $self->errcode) {
1864 1         4 return 'error undefined...with code (' . $self->errcode . ')';
1865             } else {
1866 2         13 return;
1867             };
1868             };
1869              
1870             =pod
1871              
1872             =item errvals
1873              
1874             similar to errstring, but returns the error and errcode in an array. This is great for bubbling
1875             up error messages. Note that errvals will also include the extra 'silently' parameter to prevent
1876             bubbled errors from posting notifications.
1877              
1878             $attribute = $obj->foo() or return $self->error($obj->errvals);
1879              
1880             =cut
1881              
1882             =pod
1883              
1884             =begin btest(errvals)
1885              
1886             my $notes = 0;
1887              
1888             sub notifier2 {
1889             my $self = shift;
1890             my $note = shift;
1891             $notes++;
1892             };
1893              
1894             my $center = __PACKAGE__->pkg_for_type('notificationcenter');
1895             $test->ok($center, "Got notification center class");
1896              
1897             $test->ok(
1898             scalar
1899             $center->addObserver(
1900             'observer' => '__PACKAGE__',
1901             'notification' => 'error',
1902             'object' => 'all',
1903             'method' => 'notifier2'
1904             ), "Added observer for error notifications"
1905             );
1906              
1907             my $o = __PACKAGE__->new();
1908             $test->ok($o, "Object created");
1909              
1910             $test->is(scalar $o->error("test error", "test code"), undef, "Object set error");
1911             $test->is($notes, 1, "Posted notification");
1912              
1913             my @errvals = $o->errvals;
1914             $test->is($notes, 1, "No notification");
1915             $test->is($errvals[0], "test error", "Object accesses error");
1916             $test->is($notes, 1, "No notification");
1917             $test->is($errvals[1], "test code", "Object accesses error");
1918             $test->is($notes, 1, "No notification");
1919             $test->is($errvals[2], "silently", "errvals always silent");
1920             $test->is($notes, 1, "No notification");
1921              
1922             $test->ok(
1923             scalar
1924             $center->removeObserver(
1925             'observer' => '__PACKAGE__',
1926             'notification' => 'error',
1927             ), "Removed observer for error notifications"
1928             );
1929              
1930             =end btest(errvals)
1931              
1932             =cut
1933              
1934             sub errvals {
1935 139     139 1 232 my $self = shift;
1936              
1937 139         539 return ($self->error, $self->errcode, 'silently');
1938              
1939             };
1940              
1941             =pod
1942              
1943             =item usererror
1944              
1945             errors are great, but they can be a bit cryptic. usererror takes the last error message
1946             and re-formats it into a more end user friendly syntax. If there's no way to re-format it, it
1947             just returns the actual error.
1948              
1949             Alternatively, you can also use the error translator to change an error code into something
1950             more user friendly
1951              
1952             See "errortranslator", below, for more info.
1953              
1954             =cut
1955              
1956             =pod
1957              
1958             =begin btest(usererror)
1959              
1960             my $translator = __PACKAGE__->errortranslator();
1961             $test->ok(
1962             scalar
1963             __PACKAGE__->errortranslator(
1964             {
1965             'test code' => "friendly test message",
1966             'formatted test error %d' => "friendlier test message",
1967             'formatted test error 7' => 'friendliest test message',
1968             'extra error' => 'friendliest test message 2'
1969             }),
1970             'Class set error translator'
1971             );
1972              
1973             my $uses_real = __PACKAGE__->use_real_errors();
1974             $test->is(__PACKAGE__->use_real_errors(0), 0, "Uses real errors");
1975              
1976             $test->is(scalar __PACKAGE__->error("extra error", "test code"), undef, "Class sets error");
1977             $test->is(__PACKAGE__->usererror(), "friendliest test message 2", "Class gets user error for literal");
1978              
1979             $test->is(scalar __PACKAGE__->error(["formatted test error %d", 7], "test code"), undef, "Class sets formatted error");
1980             $test->is(__PACKAGE__->usererror(), "friendliest test message", "Class gets user error for formatted string");
1981              
1982             $test->is(scalar __PACKAGE__->error(["formatted test error %d", 9], "test code"), undef, "Class sets formatted error");
1983             $test->is(__PACKAGE__->usererror(), "friendlier test message", "Class gets user error for string format");
1984              
1985             $test->is(scalar __PACKAGE__->error("Some test error", "test code"), undef, "Class sets standard error");
1986             $test->is(__PACKAGE__->usererror(), "friendly test message", "Class gets user error for error code");
1987              
1988             $test->is(scalar __PACKAGE__->error("Some unknown error", "unknown code"), undef, "Class sets standard error w/o translation");
1989             $test->is(__PACKAGE__->usererror(), "Some unknown error", "Class gets no user error");
1990              
1991             $test->ok(
1992             scalar
1993             __PACKAGE__->errortranslator(
1994             {
1995             'test code' => "friendly test message",
1996             'formatted test error %d' => "friendlier test message",
1997             'formatted test error 7' => 'friendliest test message',
1998             'extra error' => 'friendliest test message 2',
1999             '*' => 'star error',
2000             }),
2001             'Class changed error translator'
2002             );
2003              
2004             $test->is(scalar __PACKAGE__->error("Some unknown error", "unknown code"), undef, "Class sets standard error w/o translation");
2005             $test->is(__PACKAGE__->usererror(), "star error", "Class gets star error");
2006              
2007             $test->is(__PACKAGE__->errortranslator($translator), $translator, 'Class reset error translator');
2008             $test->is(__PACKAGE__->use_real_errors($uses_real), $uses_real, "resets uses real errors");
2009              
2010             =end btest(usererror)
2011              
2012             =cut
2013              
2014             sub usererror {
2015 13     13 1 37 my $self = shift;
2016              
2017 13 100       83 return $self->errstring if $self->use_real_errors;
2018              
2019 7         9 my $usererror;
2020 7         23 my $rawerror = $self->rawerror;
2021 7         13 my $error;
2022              
2023 7 100       20 if (ref $rawerror) {
2024 2         5 $error = $rawerror->[0];
2025             } else {
2026             #the variable name doesn't make sense here, but hey, we'll recycle it.
2027 5         9 $error = $rawerror;
2028             }
2029              
2030 7 100 33     21 if (defined $self->errortranslator && defined $self->error && exists $self->errortranslator->{$self->error}) {
    100 66        
    100 33        
    100 66        
      33        
      66        
      66        
2031 3         8 $usererror = $self->errortranslator->{$self->error};
2032             }
2033             elsif (defined $self->errortranslator && defined $error && exists $self->errortranslator->{$error}) {
2034 1         4 $usererror = $self->errortranslator->{$error};
2035             }
2036             elsif (defined $self->errortranslator && defined $self->errcode && exists $self->errortranslator->{$self->errcode}) {
2037 1         3 $usererror = $self->errortranslator->{$self->errcode};
2038             }
2039             elsif (defined $self->errortranslator && exists $self->errortranslator->{'*'}) {
2040 1         4 $usererror = $self->errortranslator->{'*'};
2041             }
2042             else {
2043 1         6 $usererror = $error;
2044             }
2045              
2046 7 100       25 if (ref $rawerror) {
2047 2         5 return sprintf($usererror, @{$rawerror}[1..$#$rawerror]);
  2         14  
2048             } else {
2049 5         25 return $usererror;
2050             }
2051              
2052             };
2053              
2054             =pod
2055              
2056             =item wipe_errors
2057              
2058             Wipes out the current error message and error code.
2059              
2060             =cut
2061              
2062             =pod
2063              
2064             =begin btest(wipe_errors)
2065              
2066             $test->is(scalar __PACKAGE__->error("test error", "error code"), undef, "Class set error and errcode");
2067             $test->is(__PACKAGE__->error(), "test error", "Class accesses error");
2068             $test->is(__PACKAGE__->errcode(), "error code", "Class accesses errcode");
2069             $test->ok(scalar __PACKAGE__->wipe_errors(), "Class wiped errors");
2070             $test->is(scalar __PACKAGE__->error(), undef, "Class error wiped out");
2071             $test->is(scalar __PACKAGE__->errcode(), undef, "Class errcode wiped out");
2072              
2073             my $confClass = __PACKAGE__->pkg_for_type('conf');
2074             $test->ok($confClass, "Got conf");
2075              
2076             my $cfg = $confClass->conf;
2077             $test->ok($cfg, "Got configuration");
2078              
2079             $test->ok($cfg->{"Basset::Object"}->{'exceptions'} = 1, "enables exceptions");
2080              
2081             eval {
2082             __PACKAGE__->error("test exception", "test exception code");
2083             };
2084             $test->ok($@, "Caught exception");
2085             $test->like($@, qr/test exception code/, "Exception matches");
2086             $test->like(__PACKAGE__->last_exception, qr/test exception/, "Exception is present");
2087             $test->ok(scalar __PACKAGE__->wipe_errors(), "Class wiped errors");
2088             $test->is(__PACKAGE__->last_exception, undef, "last exception wiped out");
2089             $test->is($cfg->{"Basset::Object"}->{'exceptions'} = 0, 0,"disables exceptions");
2090              
2091             =end btest(wipe_errors)
2092              
2093             =cut
2094              
2095             sub wipe_errors {
2096 5     5 1 14081 my $self = shift;
2097              
2098 5         22 $self->error(undef);
2099 5         19 $self->errcode(undef);
2100 5 50       42 $self->last_exception(undef) if $self->can('exceptions');
2101              
2102 5         23 return 1;
2103             };
2104              
2105             =pod
2106              
2107             =item notify
2108              
2109             Used for non-fatal messages, usually an error message that shouldn't cause things to abort. Expects at least one argument,
2110             the notification being posted. Additional arguments will be passed through to any handlers.
2111              
2112             sub lockThing {
2113             my $self = shift;
2114             my $thing = shift;
2115              
2116             if ($thing->locked) {
2117             $self->notify("info", "Cannot lock - thing is already locked");
2118             } else {
2119             $thing->lock();
2120             };
2121              
2122             return 1;
2123             }
2124              
2125             In this example, we have a method called "lockThing" that locks a thing (whatever that means). But it only locks the thing
2126             if it is not already locked. If it is locked, it sends an informational message that the thing is already locked. But that's not
2127             fatal - we still end up with a locked thing, so we're happy no matter what. No need to kick back an error.
2128              
2129             notify is a wrapper around the notification center.
2130              
2131             $obj->notify('foo') == Basset::NotificationCenter->postNotification('object' => $obj, 'notification' => 'foo');
2132              
2133             =cut
2134              
2135             =pod
2136              
2137             =begin btest(notify)
2138              
2139             my $test1notes = undef;
2140             my $test2notes = undef;
2141              
2142             sub test1notifier {
2143             my $self = shift;
2144             my $note = shift;
2145             $test1notes = $note->{'args'}->[0];
2146             };
2147              
2148             sub test2notifier {
2149             my $self = shift;
2150             my $note = shift;
2151             $test2notes = $note->{'args'}->[0];
2152             };
2153              
2154             my $center = __PACKAGE__->pkg_for_type('notificationcenter');
2155             $test->ok($center, "Got notification center class");
2156              
2157             $test->ok(
2158             scalar
2159             $center->addObserver(
2160             'observer' => '__PACKAGE__',
2161             'notification' => 'test1',
2162             'object' => 'all',
2163             'method' => 'test1notifier'
2164             ), "Added observer for test1 notifications"
2165             );
2166              
2167             $test->ok(
2168             scalar
2169             $center->addObserver(
2170             'observer' => '__PACKAGE__',
2171             'notification' => 'test2',
2172             'object' => 'all',
2173             'method' => 'test2notifier'
2174             ), "Added observer for test2 notifications"
2175             );
2176              
2177             my $o = __PACKAGE__->new();
2178             $test->ok($o, "Object created");
2179              
2180             $test->ok(scalar __PACKAGE__->notify('test1', "Test 1 note 1"), "Class posted notification");
2181             $test->is($test1notes, "Test 1 note 1", "Received note");
2182             $test->is($test2notes, undef, "No note for test 2");
2183              
2184             $test->ok(scalar __PACKAGE__->notify('test2', "Test 2 note 2"), "Class posted notification");
2185             $test->is($test2notes, "Test 2 note 2", "Received note");
2186             $test->is($test1notes, "Test 1 note 1", "Test 1 note unchanged");
2187              
2188             $test->ok(
2189             scalar
2190             $center->removeObserver(
2191             'observer' => '__PACKAGE__',
2192             'notification' => 'test1',
2193             ), "Removed observer for test1 notifications"
2194             );
2195              
2196             $test->ok(
2197             scalar
2198             $center->addObserver(
2199             'observer' => '__PACKAGE__',
2200             'notification' => 'test1',
2201             'object' => $o,
2202             'method' => 'test1notifier'
2203             ), "Added specific observer for test1 notifications"
2204             );
2205              
2206             $test->ok(scalar __PACKAGE__->notify('test1', 'Test 1 note 2'), "Class posted notification");
2207             $test->is($test1notes, "Test 1 note 1", "Test 1 note unchanged");
2208             $test->is($test2notes, "Test 2 note 2", "Test 2 note unchanged");
2209              
2210             $test->ok(scalar $o->notify('test1', 'Test 1 note 3'), "Object posted notification");
2211             $test->is($test1notes, "Test 1 note 3", "Recieved note");
2212              
2213             $test->is($test2notes, "Test 2 note 2", "Test 2 note unchanged");
2214              
2215             $test->ok(
2216             scalar
2217             $center->removeObserver(
2218             'observer' => '__PACKAGE__',
2219             'notification' => 'test1',
2220             ), "Removed observer for test1 notifications"
2221             );
2222              
2223             $test->ok(
2224             scalar
2225             $center->removeObserver(
2226             'observer' => '__PACKAGE__',
2227             'notification' => 'test2',
2228             ), "Removed observer for test2 notifications"
2229             );
2230              
2231             =end btest(notify)
2232              
2233             =cut
2234              
2235             sub notify {
2236 4     4 1 493 my $self = shift;
2237 4         8 my $notification = shift;
2238              
2239 4         14 my $center = $self->pkg_for_type('notificationcenter');
2240              
2241 4 50 33     41 if (defined $center && $center->can('postNotification')) {
2242 4         24 $center->postNotification(
2243             'notification' => $notification,
2244             'object' => $self,
2245             'args' => [@_],
2246             );
2247             }
2248              
2249 4         24 return 1;
2250             };
2251              
2252             =pod
2253              
2254             =item add_restrictions
2255              
2256             Class method. Expects a hash of arrayrefs, listing permissions and method re-maps.
2257              
2258             Some::Package->add_restrictions(
2259             'readonly' => [
2260             'commit' => 'failed_restricted_method',
2261             'write' => 'failed_restricted_method',
2262             ],
2263             'writeonly' => [
2264             'load' => 'failed_restricted_method',
2265             ],
2266             'subuser' => [
2267             'commit' => 'validating_commit'
2268             ]
2269             );
2270              
2271             We require a hash of arrayrefs so that we can guarantee the order in which the methods will be
2272             re-mapped.
2273              
2274             This specifies that Some::Package can be restricted in several ways, with a 'readonly' restriction,
2275             a 'writeonly' restriction, and a 'subuser' restriction. If the package is restricted, then the methods
2276             are re-mapped as defined. i.e., if the 'readonly' restriction is in place, then calling 'commit'
2277             actually calls "failed_restricted_method" Add restrictions by calling either add_restricted_method
2278             or (better!) by calling restrict.
2279              
2280             my $inline_class = Some::Package->restrict('readonly');
2281              
2282             my $o = Some::Package->new();
2283             $o->commit() || die $o->errstring; #succeeds!
2284              
2285             my $o2 = $inline_class->new();
2286             $o2->commit() || die $o2->errstring; #fails. access to commit is restricted.
2287              
2288             see add_restricted_method and restrict, below.
2289              
2290             =cut
2291              
2292             =pod
2293              
2294             =begin btest(add_restrictions)
2295              
2296             package Basset::Test::Testing::__PACKAGE__::add_restrictions::Subclass1;
2297             our @ISA = qw(__PACKAGE__);
2298              
2299             my %restrictions = (
2300             'specialerror' => [
2301             'error' => 'error2',
2302             'errcode' => 'errcode2'
2303             ],
2304             'invalidrestriction' => [
2305             'junkymethod' => 'otherjunkymethod'
2306             ]
2307             );
2308              
2309             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::add_restrictions::Subclass1->add_restrictions(%restrictions), "Added restrictions to subclass");
2310              
2311             =end btest(add_restrictions)
2312              
2313             =cut
2314              
2315             sub add_restrictions {
2316 5     5 1 927 my $self = shift;
2317 5 50       51 my %newrestrictions = @_ or return $self->error("Cannot add restriction w/o restrictions", "BO-17");
2318              
2319 5         44 my $restrictions = $self->restrictions();
2320              
2321             # @$restrictions{keys %newrestrictions} = values %newrestrictions;
2322              
2323             #this is a nuisance. We're here, so we know that we're adding restrictions.
2324             #if there's already a restrictions hash, we need to duplicate it here. See the
2325             #docs for add_trickle_class_attr above for more info on dealing with trickled class attributes
2326             #that contain references
2327 5 50       16 if ($restrictions) {
2328 0         0 my $val = $self->dump($restrictions);
2329 0         0 $val =~ /^(\$\w+)/;
2330 0         0 local $@ = undef;
2331 0         0 $restrictions = eval qq{
2332             my $1;
2333             eval \$val;
2334             };
2335             }
2336             #otherwise, we create a new hash
2337             else {
2338 5         10 $restrictions = {};
2339             };
2340              
2341 5         25 @$restrictions{keys %newrestrictions} = values %newrestrictions;
2342              
2343             #finally, we can properly set the new hash because we're guaranteed that it's always a copy
2344             #that we want to operate on.
2345 5         21 $self->restrictions($restrictions);
2346              
2347 5         34 return 1;
2348             }
2349              
2350             =pod
2351              
2352             =item add_restricted_method
2353              
2354             Given a restriction and a method, restricts only that method to that restriction.
2355              
2356             Some::Package->add_restricted_method('writeonly', 'commit');
2357              
2358             This applies the writeonly restriction to the commit method (as defined above in the add_restrictions
2359             pod). Note that this does not apply the restriction to the 'write' method, only to 'commit'.
2360              
2361             You will rarely (if ever) use this method, use 'restrict' instead.
2362              
2363             =cut
2364              
2365             =begin btest(add_restricted_method)
2366              
2367             package Basset::Test::Testing::__PACKAGE__::add_restricted_method::Subclass1;
2368             our @ISA = qw(__PACKAGE__);
2369              
2370             my %restrictions = (
2371             'specialerror' => [
2372             'error' => 'error2',
2373             'errcode' => 'errcode2'
2374             ],
2375             'invalidrestriction' => [
2376             'junkymethod' => 'otherjunkymethod'
2377             ]
2378             );
2379              
2380             __PACKAGE__->add_class_attr('e2');
2381             __PACKAGE__->add_class_attr('c2');
2382              
2383             $test->is(__PACKAGE__->e2(0), 0, "set e2 to 0");
2384             $test->is(__PACKAGE__->c2(0), 0, "set c2 to 0");
2385              
2386             sub error2 {
2387             my $self = shift;
2388             $self->e2($self->e2 + 1);
2389             return $self->SUPER::error(@_);
2390             }
2391              
2392             sub errcode2 {
2393             my $self = shift;
2394             $self->c2($self->c2 + 1);
2395             return $self->SUPER::errcode(@_);
2396             }
2397              
2398             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::add_restricted_method::Subclass1->add_restrictions(%restrictions), "Added restrictions to subclass");
2399              
2400             package __PACKAGE__;
2401              
2402             $test->ok(Basset::Test::Testing::__PACKAGE__::add_restricted_method::Subclass1->isa('__PACKAGE__'), 'Proper subclass');
2403              
2404             my $subclass = Basset::Test::Testing::__PACKAGE__::add_restricted_method::Subclass1->inline_class();
2405             $test->ok(scalar $subclass, "Got restricted class");
2406             $test->ok($subclass->restricted, "Subclass is restricted");
2407             $test->ok(scalar $subclass->isa('Basset::Test::Testing::__PACKAGE__::add_restricted_method::Subclass1'), "Is subclass");
2408             $test->ok(scalar $subclass->isa('__PACKAGE__'), "Is subclass");
2409              
2410             $test->ok(scalar $subclass->add_restricted_method('specialerror', 'error'), "Restricted error");
2411             $test->ok(scalar $subclass->add_restricted_method('specialerror', 'errcode'), "Restricted errcode");
2412             $test->ok(! scalar $subclass->add_restricted_method('invalidrestriction', 'junkymethod'), "Could not add invalid restriction");
2413              
2414             $test->ok(! scalar $subclass->add_restricted_method('specialerror', 'error2'), "Could not add invalid restricted method");
2415             $test->ok(! scalar $subclass->add_restricted_method('specialerror', 'errcode2'), "Could not add invalid restricted method");
2416             $test->ok(! scalar $subclass->add_restricted_method('specialerror', 'junkymethod2'), "Could not add invalid restricted method");
2417              
2418             my $e2 = $subclass->e2;
2419             my $c2 = $subclass->c2;
2420              
2421             #we post silently or else error and errcode would be called when it posts the error notification.
2422             $test->is(scalar $subclass->error("test error", "test code", "silently"), undef, "Set error for subclass");
2423              
2424             $test->is($subclass->e2, $e2 + 1, "Subclass restricted error incremented");
2425             $test->is($subclass->c2, $c2, "Subclass restricted errcode unchanged");
2426             $test->is($subclass->error(), "test error", "Subclass accesses error method");
2427             $test->is($subclass->e2, $e2 + 2, "Subclass restricted error incremented");
2428             $test->is($subclass->c2, $c2, "Subclass restricted errcode unchanged");
2429             $test->is($subclass->errcode(), "test code", "Subclass accesses errcode method");
2430             $test->is($subclass->e2, $e2 + 2, "Subclass restricted error unchanged");
2431             $test->is($subclass->c2, $c2 + 1, "Subclass restricted errcode incremented");
2432              
2433             $test->is(scalar Basset::Test::Testing::__PACKAGE__::add_restricted_method::Subclass1->error("super test error", "super test code", "silently"), undef, "Superclass sets error");
2434             $test->is($subclass->e2, $e2 + 2, "Subclass restricted error unchanged");
2435             $test->is($subclass->c2, $c2 + 1, "Subclass restricted errcode unchanged");
2436              
2437             =end btest(add_restricted_method)
2438              
2439             =cut
2440              
2441             sub add_restricted_method {
2442 11     11 1 935 my $pkg = shift;
2443 11         18 my $restriction = shift;
2444 11         15 my $method = shift;
2445              
2446 11         44 my $restrictions = $pkg->restrictions;
2447              
2448 11         59 my $restriction_set = $restrictions->{$restriction};
2449              
2450 11         24 my $restricted_method = undef;
2451              
2452 11 50       22 if (defined $restriction_set) {
2453 11         45 my $map = {@$restriction_set};
2454              
2455 11 100       50 $restricted_method = $map->{$method}
2456             or return $pkg->error("No method for restriction ($restriction) on method ($method)", "BO-14");
2457              
2458             } else {
2459 0         0 return $pkg->error("Cannot add restricted method ($method) w/o restriction set ($restriction)", "BO-19");
2460             };
2461              
2462             # my $restricted_method = $restrictions->{$restriction}->{$method}
2463             # or return $pkg->error("No method for restriction ($restriction) on method ($method)", "BO-14");
2464              
2465 8     8   61 no strict 'refs';
  8         15  
  8         2440  
2466              
2467 8 50       24 if (ref $restricted_method eq 'CODE') {
2468 0         0 *{$pkg . "::$method"} = $restricted_method;
  0         0  
2469 0         0 return $method;
2470             };
2471              
2472 8         59 my $parents = $pkg->isa_path;
2473              
2474             #remember the isa path is most distant -> closest. Here we want to look at the closest
2475             #ancestor that is not restricted.
2476             #
2477             #We march up the tree. Once we find a parent (or ourselves) that can perform the method
2478             #we're looking for, we stop and are happy.
2479 8         24 foreach my $parent (reverse @$parents) {
2480              
2481 20         23 my $code = *{$parent . '::' . $restricted_method}{'CODE'};
  20         118  
2482 20 100       54 if (defined $code ) {
2483 6         10 *{$pkg . "::$method"} = $code;
  6         43  
2484 6         46 return $method;
2485 0         0 last;
2486             };
2487             };
2488              
2489 2         14 return $pkg->error("could not restrict method - no super class defines $restricted_method", "BO-15");
2490             };
2491              
2492             =pod
2493              
2494             =item failed_restricted_method
2495              
2496             Simple convenience method. Always fails with a known error and errorcode - "Access to this method is
2497             restricted", "BO-16"
2498              
2499             =cut
2500              
2501             =pod
2502              
2503             =begin btest(failed_restricted_method)
2504              
2505             package Basset::Test::Testing::__PACKAGE__::failed_restricted_method::Subclass2;
2506             our @ISA = qw(__PACKAGE__);
2507              
2508             sub successful {
2509             return 1;
2510             };
2511              
2512             my %restrictions = (
2513             'failure' => [
2514             'successful' => 'failed_restricted_method',
2515             ],
2516             );
2517              
2518             package __PACKAGE__;
2519              
2520             my $subclass = Basset::Test::Testing::__PACKAGE__::failed_restricted_method::Subclass2->inline_class;
2521             $test->ok($subclass, "Got restricted subclass");
2522             $test->ok(scalar $subclass->restricted, "Subclass is restricted");
2523             $test->ok(scalar $subclass->add_restrictions(%restrictions), "Subclass added restrictions");
2524              
2525             $test->ok(! scalar __PACKAGE__->failed_restricted_method, "Failed restricted method always fails");
2526             $test->ok(! scalar Basset::Test::Testing::__PACKAGE__::failed_restricted_method::Subclass2->failed_restricted_method, "Failed restricted method always fails");
2527             $test->ok(! scalar $subclass->failed_restricted_method, "Failed restricted method always fails");
2528              
2529             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::failed_restricted_method::Subclass2->successful, "Super Success is successful");
2530             $test->ok(scalar $subclass->successful, "Subclass success is successful");
2531             $test->ok(scalar $subclass->add_restricted_method('failure', 'successful'), "Restricted subclass to fail upon success");
2532             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::failed_restricted_method::Subclass2->successful, "Super Success is successful");
2533             $test->ok(! scalar $subclass->successful, "Subclass success fails");
2534              
2535             =end btest(failed_restricted_method)
2536              
2537             =cut
2538              
2539             sub failed_restricted_method {
2540 7     7 1 61 return shift->error("Access to this method is restricted", "BO-16");
2541             };
2542              
2543             =pod
2544              
2545             =item inline_class
2546              
2547             Another internal method that you will rarely, if ever call.
2548              
2549             my $inline_class = Some::Package->inline_class();
2550              
2551             This creates a new class, which is a subclass of Some::Package. The only difference is that
2552             it has its restricted flag turned on. To apply restrictions, use the restrict method instead.
2553              
2554             =cut
2555              
2556             =pod
2557              
2558             =begin btest(inline_class)
2559              
2560             my $class = __PACKAGE__->inline_class();
2561             $test->ok($class, "Got restricted class");
2562             $test->ok($class->restricted(), "Class is restricted");
2563             $test->ok(! __PACKAGE__->restricted(), "Superclass is not restricted");
2564              
2565             =end btest(inline_class)
2566              
2567             =cut
2568              
2569             our $restrict_counter = 0;
2570             our %inlined = ();
2571              
2572             sub inline_class {
2573 12     12 1 1946 my $pkg = shift;
2574              
2575 8     8   47 no strict 'refs';
  8         14  
  8         2607  
2576 12         47 my $class = $pkg . '::BASSETINLINE::R' . $restrict_counter++;
2577 12         28 @{$class . "::ISA"} = ($pkg);
  12         358  
2578 12         138 $class->restricted(1);
2579              
2580 12         39 $inlined{$class}++;
2581              
2582 12         39 return $class;
2583             };
2584              
2585             sub load_pkg {
2586 190     190 0 515 my $class = shift;
2587              
2588 190 50       527 my $newclass = shift or return $class->error("Cannot load_pkg w/o class", "BO-28");
2589 190   100     563 my $errorless = shift || 0;
2590              
2591 190         388 local $@ = undef;
2592 190 100 100 4   1101 eval "use $newclass" unless $inlined{$newclass} || $INC{$class->module_for_class($newclass)};
  4         11955  
  4         10  
  4         80  
2593              
2594 190 100       571 if ($@) {
2595 3 100       30 return $errorless ? undef : $class->error("Cannot load class ($newclass) : $@", "BO-29");
2596             }
2597              
2598 187         547 return $newclass;
2599             }
2600              
2601             =pod
2602              
2603             =begin btest(load_pkg)
2604              
2605             my $iclass = __PACKAGE__->inline_class;
2606             $test->ok(scalar __PACKAGE__->load_pkg($iclass), "Can load inline class");
2607              
2608             =end btest(load_pkg)
2609              
2610             =cut
2611              
2612             =pod
2613              
2614             =item restrict
2615              
2616             Called on a class, this creates a new subclass with restrictions in place.
2617              
2618             my $inline_class = Some::Package->restrict('readonly', 'writeonly', 'subuser');
2619              
2620             Will return a new class which is a subclass of Some::Package that has the readonly, writeonly,
2621             and subuser restrictions applied. Note that restrictions are applied in order, so that a later
2622             one may wipe out an earlier one. In this case, the re-defined commit method from subuser wins over
2623             the one defined in writeonly.
2624              
2625             This is used to restrict access to class methods, probably depending upon some sort of user permission
2626             scheme.
2627              
2628             =cut
2629              
2630             =pod
2631              
2632             =begin btest(restrict)
2633              
2634             package Basset::Test::Testing::__PACKAGE__::restrict::Subclass1;
2635             our @ISA = qw(__PACKAGE__);
2636              
2637             sub successful {
2638             return 1;
2639             };
2640              
2641             my %restrictions = (
2642             'failure' => [
2643             'successful' => 'failed_restricted_method',
2644             ],
2645             );
2646              
2647             $test->ok(Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->add_restrictions(%restrictions), "Subclass added restrictions");
2648              
2649             package __PACKAGE__;
2650              
2651             $test->ok(scalar __PACKAGE__->can('failed_restricted_method'), "__PACKAGE__ has failed_restricted_method");
2652             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->can('failed_restricted_method'), "Subclass has failed_restricted_method");
2653              
2654             $test->ok(Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->isa('__PACKAGE__'), 'Proper subclass');
2655             $test->ok(! scalar __PACKAGE__->failed_restricted_method, "Method properly fails");
2656             $test->ok(! scalar Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->failed_restricted_method, "Method properly fails");
2657              
2658             my $subclass = Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->restrict('failure');
2659              
2660             $test->ok($subclass, "Got restricted subclass");
2661              
2662             $test->ok($subclass->restricted, "Subclass is restricted");
2663             $test->ok(! Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->restricted, "Superclass unaffected");
2664             $test->ok(! __PACKAGE__->restricted, "Superclass unaffected");
2665              
2666             $test->ok(! scalar $subclass->successful, "Subclass restricted");
2667             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->successful, "Superclass unaffected");
2668              
2669             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::restrict::Subclass1->restrict('worthless restriction'), "Added unknown restriction");
2670              
2671             =end btest(restrict)
2672              
2673             =cut
2674              
2675             our $prior = {};
2676              
2677             sub restrict {
2678 3     3 1 11 my $pkg = shift;
2679              
2680 3 50       14 my @restrictions = @_ or return $pkg->error("Cannot restrict package w/o restrictions", "BO-13");
2681              
2682 3         12 my $key = join(',', $pkg, @restrictions);
2683              
2684 3 50       13 return $prior->{$key} if defined $prior->{$key};
2685              
2686 8     8   47 no strict 'refs';
  8         19  
  8         10823  
2687              
2688 3         23 my $class = $pkg->inline_class();
2689              
2690 3         164 my $pkgrestrictions = $pkg->restrictions();
2691              
2692 3         10 my @applied = @{$pkg->applied_restrictions()};
  3         30  
2693              
2694 3         10 foreach my $restriction (@restrictions) {
2695              
2696             #keep track of the restrictions we've applied
2697 3         11 push @applied, $restriction;
2698              
2699             #grab our restriction map
2700 3 100       7 my @map = @{$pkgrestrictions->{$restriction} || []};
  3         21  
2701              
2702             #iterate through it. It's a hash masquerading as an arrayref, so the first
2703             #element is our key, the second is the value (which we don't need right now)
2704 3         14 while (@map) {
2705 3         6 my $method = shift @map;
2706 3         8 my $restricted_method = shift @map;
2707              
2708 3 50       35 $class->add_restricted_method($restriction, $method)
2709             or return $pkg->error($class->errvals);
2710             }
2711             };
2712              
2713 3         21 $prior->{$key} = $class;
2714              
2715 3         31 $class->applied_restrictions(\@applied);
2716              
2717 3         17 return $class;
2718             }
2719              
2720             =pod
2721              
2722             =item nonrestricted_parent
2723              
2724             Called on a class, returns the first non-restricted parent of that class
2725              
2726             =cut
2727              
2728             =pod
2729              
2730             =begin btest(nonrestricted_parent)
2731              
2732             package Basset::Test::Testing::__PACKAGE__::nonrestricted_parent::Subclass1;
2733             our @ISA = qw(__PACKAGE__);
2734              
2735             package __PACKAGE__;
2736              
2737             $test->is(__PACKAGE__->nonrestricted_parent, "__PACKAGE__", "__PACKAGE__ own nonrestricted parent");
2738             $test->is(Basset::Test::Testing::__PACKAGE__::nonrestricted_parent::Subclass1->nonrestricted_parent, "Basset::Test::Testing::__PACKAGE__::nonrestricted_parent::Subclass1", "Subclass own nonrestricted parent");
2739              
2740             my $subclass = Basset::Test::Testing::__PACKAGE__::nonrestricted_parent::Subclass1->inline_class;
2741             $test->ok($subclass, "Got restricted class");
2742             $test->is($subclass->nonrestricted_parent, "Basset::Test::Testing::__PACKAGE__::nonrestricted_parent::Subclass1", "Restricted class has proper non restricted parent");
2743              
2744             my $subclass2 = $subclass->inline_class;
2745             $test->ok($subclass2, "Got restricted class of restricted class");
2746             $test->is($subclass2->nonrestricted_parent, "Basset::Test::Testing::__PACKAGE__::nonrestricted_parent::Subclass1", "Restricted class has proper non restricted parent");
2747              
2748             my $subclass3 = __PACKAGE__->inline_class;
2749             $test->ok($subclass3, "Got restricted class");
2750             $test->is($subclass3->nonrestricted_parent, "__PACKAGE__", "Restricted class has proper non restricted parent");
2751              
2752             =end btest(nonrestricted_parent)
2753              
2754             =cut
2755              
2756             sub nonrestricted_parent {
2757 5     5 1 1994 my $self = shift;
2758              
2759 5         34 my $parents = $self->isa_path;
2760              
2761             #remember the isa path is most distant -> closest. Here we want to look at the closest
2762             #ancestor that is not restricted.
2763             #
2764             #We march up the tree. Once we find a parent (or ourselves) that can perform the method
2765             #we're looking for, we stop and are happy.
2766 5         11 foreach my $parent (reverse @$parents) {
2767 9 100       33 return $parent unless $parent->restricted();
2768             };
2769              
2770 0         0 return $self->error("class ($self) has no non-restricted parents", "BO-18");
2771             }
2772              
2773             =pod
2774              
2775             =item dump
2776              
2777             ->dump dumps out the object (using Data::Dumper internally), this is useful to show you what an object looks like.
2778              
2779             print $obj->dump
2780              
2781             Alternatively, you can hand in something to dump.
2782              
2783             print $obj->dump($something_else);
2784              
2785             =cut
2786              
2787             =pod
2788              
2789             =begin btest(dump)
2790              
2791             my $o = __PACKAGE__->new();
2792             $test->ok($o, "Created object");
2793             my $o2 = __PACKAGE__->new();
2794             $test->ok($o2, "Created object");
2795              
2796             $test->ok($o->dump, "Dumped object");
2797             $test->ok($o->dump(['a']), "Dumped array");
2798             $test->ok($o->dump({'k' => 'v'}), "Dumped hash");
2799             $test->ok($o2->dump, "Dumped other object");
2800             $test->is($o->dump($o2), $o2->dump, "Dumps equal");
2801             $test->is($o->dump, $o2->dump($o), "Dumps equal");
2802              
2803             =end btest(dump)
2804              
2805             =cut
2806              
2807             sub dump {
2808 17     17 1 3581 my $self = shift;
2809              
2810 17 100       87 return Data::Dumper::Dumper(@_ ? shift : $self);
2811             };
2812              
2813             =pod
2814              
2815             =item new
2816              
2817             Finally! The B. It's very easy, for a minimalist object, do this:
2818              
2819             my $obj = Class->new() || die Class->error();
2820              
2821             Ta da! You have an object. Any attributes specified in the conf file will be loaded into your object. So if your
2822             conf file defines 'foo' as 'bar', then $obj->foo will now equal 'bar'.
2823              
2824             If you'd like, you can also pass in method/value pairs to the constructor.
2825              
2826             my $obj = Class->new(
2827             'attribute' => '17',
2828             'foo' => 'baz',
2829             'method' => '88'
2830             ) || die Class->error();
2831              
2832             This is (roughly) the same as:
2833              
2834             my $obj = Class->new() || die Class->error();
2835              
2836             $obj->attribute(17) || die $obj->error();
2837             $obj->foo('baz') || die $obj->error();
2838             $obj->method(88) || die $obj->error();
2839              
2840             Any accessors or methods you'd like may be passed to the constructor. Any unknown pairs will be silently ignored.
2841             If you pass a method/value pair to the constructor, it will override any equivalent method/value pair in the
2842             conf file.
2843              
2844             Also note that any methods that return undef are assumed to be errors and will cause your construction to fail. But, if you explicitly pass
2845             in an 'undef' parameter and your method/mutator fails, then we will assume you know what you're doing and it's allowed. You only fail
2846             if you pass in a value other than undef, but the result of the method call is an undef.
2847              
2848             $obj = Class->new(
2849             'attr' => undef
2850             ) || die Class->error;
2851              
2852             If you really really need to to explicitly set something to undef, you'll need to do it afterwards:
2853              
2854             $obj = Class->new();
2855             $obj->method(undef);
2856              
2857             Note that in this case, setting 'method' to undef isn't actually an error, since that's what you want to do. But,
2858             the constructor has no way to know when an accessor returning undef is an error, or when you explicitly set the accessor
2859             to undef.
2860              
2861             =cut
2862              
2863             =pod
2864              
2865             =begin btest(new)
2866              
2867             my $o = __PACKAGE__->new();
2868              
2869             $test->ok($o, "created a new object");
2870              
2871             package Basset::Test::Testing::__PACKAGE__::new::Subclass1;
2872             our @ISA = qw(__PACKAGE__);
2873              
2874             Basset::Test::Testing::__PACKAGE__::new::Subclass1->add_attr('attr1');
2875             Basset::Test::Testing::__PACKAGE__::new::Subclass1->add_attr('attr2');
2876             Basset::Test::Testing::__PACKAGE__::new::Subclass1->add_attr('attr3');
2877             Basset::Test::Testing::__PACKAGE__::new::Subclass1->add_class_attr('class_attr');
2878              
2879             package __PACKAGE__;
2880              
2881             $test->ok(Basset::Test::Testing::__PACKAGE__::new::Subclass1->isa('__PACKAGE__'), "Subclass is subclass");
2882             $test->ok(Basset::Test::Testing::__PACKAGE__::new::Subclass1->can('attr1'), 'class can attr1');
2883             $test->ok(Basset::Test::Testing::__PACKAGE__::new::Subclass1->can('attr2'), 'class can attr2');
2884             $test->ok(Basset::Test::Testing::__PACKAGE__::new::Subclass1->can('attr3'), 'class can attr3');
2885             $test->ok(Basset::Test::Testing::__PACKAGE__::new::Subclass1->can('class_attr'), 'class can class_attr');
2886              
2887             my $o2 = Basset::Test::Testing::__PACKAGE__::new::Subclass1->new();
2888             $test->ok($o2, "created a subclass object");
2889              
2890             my $o3 = Basset::Test::Testing::__PACKAGE__::new::Subclass1->new(
2891             'attr1' => 'attr1val',
2892             );
2893              
2894             $test->ok($o3, "Created a subclass object");
2895             $test->is(scalar $o3->attr1, 'attr1val', 'subclass object has attribute from constructor');
2896              
2897             my $o4 = Basset::Test::Testing::__PACKAGE__::new::Subclass1->new(
2898             'attr1' => 'attr1val',
2899             'attr2' => 'attr2val',
2900             );
2901              
2902             $test->ok($o4, "Created a subclass object");
2903             $test->is(scalar $o4->attr1, 'attr1val', 'subclass object has attribute from constructor');
2904             $test->is(scalar $o4->attr2, 'attr2val', 'subclass object has attribute from constructor');
2905              
2906             my $o5 = Basset::Test::Testing::__PACKAGE__::new::Subclass1->new(
2907             'attr1' => 'attr1val',
2908             'attr2' => 'attr2val',
2909             'attr7' => 'attr7val',
2910             'attr8' => 'attr8val',
2911             );
2912              
2913             $test->ok($o5, "Created a subclass object w/junk values");
2914             $test->is(scalar $o5->attr1, 'attr1val', 'subclass object has attribute from constructor');
2915             $test->is(scalar $o5->attr2, 'attr2val', 'subclass object has attribute from constructor');
2916              
2917             #these tests would now pass.
2918             #my $o6 = Basset::Test::Testing::__PACKAGE__::new::Subclass1->new(
2919             # 'attr1' => undef,
2920             #);
2921             #
2922             #$test->ok(! $o6, "Failed to create object w/undef value");
2923              
2924             my $o7 = Basset::Test::Testing::__PACKAGE__::new::Subclass1->new(
2925             'attr1' => 7,
2926             'attr2' => 0,
2927             );
2928              
2929             $test->ok($o7, "Created object w/0 value");
2930             $test->is($o7->attr1, 7, 'attr1 value set');
2931             $test->is($o7->attr2, 0, 'attr2 value set');
2932              
2933             my $o8 = Basset::Test::Testing::__PACKAGE__::new::Subclass1->new(
2934             {
2935             'attr1' => 8,
2936             'attr2' => 9
2937             },
2938             'attr1' => 7
2939             );
2940              
2941             $test->ok($o8, "Created object w/0 value");
2942             $test->is($o8->attr1, 7, 'attr1 value set');
2943             $test->is($o8->attr2, 9, 'attr2 value set');
2944              
2945             =end btest(new)
2946              
2947             =cut
2948              
2949             sub new {
2950 83     83 1 92108 my $class = shift->pkg;
2951 83         746 my $self = bless {}, $class;
2952              
2953 83   66     545 return $self->init(
2954             @_
2955             ) || $class->error($self->errvals);
2956             };
2957              
2958             =pod
2959              
2960             =item init
2961              
2962             The object initializer. Arguably more important than the constructor, but not something you need to worry about.
2963             The constructor calls it internally, and you really shouldn't touch it or override it. But I wanted it here so
2964             you know what it does.
2965              
2966             Simply, it iterates through the conf file and mutates any of your object attributes to the value specified in the conf
2967             file. It then iterates through the hash you passed to ->new() and does the same thing, overriding any conf values, if
2968             necessary.
2969              
2970             init is smart enough to use all super class values defined in the conf file, in hierarchy order. So if your conf file
2971             contains:
2972              
2973             define package SuperClass
2974              
2975             foo = 'bar'
2976              
2977             And you're creating a new SubClass object, then it will get the default of foo = 'bar' as in the conf file, despite
2978             the fact that it was not defined for your own package. Naturally, the more significant definition is used.
2979              
2980             define package SuperClass
2981              
2982             foo = 'bar'
2983              
2984             define package SubClass
2985              
2986             foo = 'baz'
2987              
2988             SuperClass objects will default foo to 'bar', SubClass objects will default foo to 'baz'
2989              
2990             If the initializer is given a hashref as its first argument, then it will use those values first. Note that
2991             values passed in via a hashref like this may be overridden by defaults AND by passed in arguments.
2992              
2993             For example:
2994              
2995             #in your conf file
2996             define package Some::Class
2997             foo = bar
2998             one = two
2999             alpha = beta
3000              
3001             #in your code
3002              
3003             my $x = Some::Class->new(
3004             {
3005             'foo' => 'fnar',
3006             'mister' => 'peepers',
3007             'alpha' => 'kappa',
3008             },
3009             'alpha' => 'gamma'
3010             );
3011              
3012             print $x->foo; #prints 'bar' (from conf file)
3013             print $x->one; #prints 'two' (from conf file)
3014             print $x->mister; #prints 'peepers' (from initial hash)
3015             print $x->alpha; #prints 'gamma' (passed argument)
3016              
3017             =cut
3018              
3019             =pod
3020              
3021             =begin btest(init)
3022              
3023             package Basset::Test::Testing::__PACKAGE__::init::Subclass2;
3024             our @ISA = qw(__PACKAGE__);
3025              
3026             sub conf {
3027             return undef;
3028             };
3029              
3030             package __PACKAGE__;
3031              
3032             {
3033             my $o = undef;
3034             local $@ = undef;
3035             $o = Basset::Test::Testing::__PACKAGE__::init::Subclass2->new();
3036             $test->is($o, undef, 'could not create object w/o conf file');
3037             }
3038              
3039             {
3040             my $o = __PACKAGE__->new('__j_known_junk_method' => 'a');
3041             $test->ok($o, 'created object');
3042             }
3043              
3044             package Basset::Test::Testing::__PACKAGE__::init::Subclass3;
3045             our @ISA = qw(__PACKAGE__);
3046             my $subclass = 'Basset::Test::Testing::__PACKAGE__::init::Subclass3';
3047              
3048             sub known_failure {
3049             my $self = shift;
3050             return $self->error("I failed", "known_error_code");
3051             }
3052              
3053             sub known_failure_2 {
3054             my $self = shift;
3055             return;
3056             }
3057              
3058             my $obj1 = $subclass->new();
3059             $test->ok($obj1, "Got empty object w/o known failure");
3060              
3061             my $obj2 = $subclass->new(
3062             'known_failure' => 1
3063             );
3064              
3065             $test->is($obj2, undef, "obj2 not created because of known_failure");
3066             $test->is($subclass->errcode, 'known_error_code', 'proper error code');
3067              
3068             my $obj3 = $subclass->new(
3069             'known_failure_2' => 1
3070             );
3071              
3072             $test->is($obj3, undef, "obj3 not created because of known_failure_2");
3073             $test->is($subclass->errcode, 'BO-03', 'proper error code');
3074              
3075             =end btest(init)
3076              
3077             =cut
3078              
3079             sub init {
3080 83     83 1 166 my $self = shift;
3081              
3082 83 100       473 my $conf = $self->conf or return;
3083              
3084 82         439 my $parents = $self->isa_path();
3085              
3086 82         212 my %defaults = ();
3087              
3088 82 100       268 if (ref $_[0] eq 'HASH') {
3089 1         2 my $defhash = shift @_;
3090 1         5 @defaults{keys %$defhash} = values %$defhash;
3091             }
3092              
3093             #initialize our values brought in from the conf file
3094 82         201 foreach my $pkg (@$parents){
3095              
3096 144         206 my %pkgdef = map {substr($_,1), $conf->{$pkg}->{$_}} grep {/^-/} keys %{$conf->{$pkg}};
  16         102  
  197         628  
  144         484  
3097              
3098 144         453 @defaults{keys %pkgdef} = values %pkgdef;
3099              
3100             }
3101              
3102 82         331 my @init = (%defaults, @_);
3103              
3104             #initialize our values passed in to the constructor
3105             # foreach my $method (keys %init){
3106             # my $value = $init{$method};
3107 82         259 while (@init) {
3108 419         789 my ($method, $value) = splice(@init, 0, 2);
3109             #my $method = shift @init;
3110             #my $value = shift @init;
3111              
3112 419 100       1712 if ($self->can($method)){
3113             # $self->wipe_errors();
3114 416         1029 my $return = $self->$method($value);
3115              
3116 416 100 100     1672 return $self->error("Could not initilize method ($method) to value ($value)"
    100 66        
3117             . (defined $self->error ? " : " . $self->error : ' ')
3118             , ($self->errcode || "BO-03")
3119             ) unless defined $return || ! defined $value;
3120             };
3121             };
3122              
3123 80         643 return $self;
3124             };
3125              
3126             =pod
3127              
3128             =item pkg
3129              
3130             Returns the package (class) of the object. Note that this is not necessarily the same as ref $object. This is
3131             because of some wackiness in how perl handles some internal things that I don't quite understand.
3132             Suffice to say that even if you bless an object into a class Foo, ref $object may not always be 'Foo'.
3133             Sometimes it may be 'main::Foo' and sometimes it may be '::Foo'. I'll leave the reasons why for
3134             others to document. This method is just here to keep that from biting you.
3135              
3136             =cut
3137              
3138             =pod
3139              
3140             =begin btest(pkg)
3141              
3142             package main::Basset::Test::Testing::__PACKAGE__::MainSubClass;
3143             our @ISA = qw(__PACKAGE__);
3144              
3145             package Basset::Test::Testing::__PACKAGE__::MainSubClass2;
3146             our @ISA = qw(__PACKAGE__);
3147              
3148             package ::Basset::Test::Testing::__PACKAGE__::MainSubClass3;
3149             our @ISA = qw(__PACKAGE__);
3150              
3151             package __PACKAGE__;
3152              
3153             $test->ok(main::Basset::Test::Testing::__PACKAGE__::MainSubClass->isa('__PACKAGE__'), "Created subclass");
3154             $test->ok(Basset::Test::Testing::__PACKAGE__::MainSubClass2->isa('__PACKAGE__'), "Created subclass");
3155             $test->ok(Basset::Test::Testing::__PACKAGE__::MainSubClass3->isa('__PACKAGE__'), "Created subclass");
3156              
3157             my $o = __PACKAGE__->new();
3158             $test->ok($o, "Created object");
3159              
3160             my $so1 = main::Basset::Test::Testing::__PACKAGE__::MainSubClass->new();
3161             $test->ok($so1, "Created sub-object");
3162              
3163             my $so2 = Basset::Test::Testing::__PACKAGE__::MainSubClass2->new();
3164             $test->ok($so2, "Created sub-object");
3165              
3166             my $so3 = Basset::Test::Testing::__PACKAGE__::MainSubClass3->new();
3167             $test->ok($so3, "Created sub-object");
3168              
3169             $test->is($o->pkg, "__PACKAGE__", "Superclass works");
3170             $test->is($so1->pkg, "Basset::Test::Testing::__PACKAGE__::MainSubClass", "Subclass works");
3171             $test->is($so2->pkg, "Basset::Test::Testing::__PACKAGE__::MainSubClass2", "Subclass works");
3172             $test->is($so3->pkg, "Basset::Test::Testing::__PACKAGE__::MainSubClass3", "Subclass works");
3173              
3174             =end btest(pkg)
3175              
3176             =cut
3177              
3178             sub pkg {
3179 3123   66 3123 1 11075 my $class = ref($_[0]) || $_[0];
3180 3123 100       16526 if (index($class, '::') == 0) {
    100          
3181 3         8 $class = substr($class, 2);
3182             } elsif (index($class, 'main::') == 0) {
3183 4         10 $class = substr($class, 6);
3184             };
3185              
3186 3123         5945 return $class;
3187             };
3188              
3189             =pod
3190              
3191             =item factory
3192              
3193             Abstract factory constructor. Works just like ->new() except it expects to receive a type. The types are listed in the conf
3194             file to determine which type of object to instantiate.
3195              
3196             In conf file:
3197              
3198             define package Basset::Object
3199             types @= user=Basset::User
3200             types @= group=Basset::Group
3201              
3202             And then, in your program:
3203              
3204             my $user = Basset::Object->factory(
3205             'type' => 'user'
3206             );
3207              
3208             $user is a Basset::User object. Use for objects that are supposed to be used in multiple applications. This allows you to swap
3209             out particular objects for different (but similar!) ones by just changing the conf file, not all your code.
3210              
3211             =cut
3212              
3213             =pod
3214              
3215             =begin btest(factory)
3216              
3217             package Basset::Test::Testing::__PACKAGE__::factory::Subclass;
3218             our @ISA = qw(__PACKAGE__);
3219              
3220             package __PACKAGE__;
3221              
3222             my $oldtypes = __PACKAGE__->types();
3223             $test->ok($oldtypes, "Saved old types");
3224             my $newtypes = {%$oldtypes, 'factory_test_type' => '__PACKAGE__'};
3225             $test->is(__PACKAGE__->types($newtypes), $newtypes, "Set new types");
3226             $test->is(__PACKAGE__->pkg_for_type('factory_test_type'), '__PACKAGE__', 'can get class for type');
3227             my $o = __PACKAGE__->new();
3228             $test->ok($o, "Created new object");
3229             my $o2 = __PACKAGE__->factory('type' => 'factory_test_type');
3230             $test->ok($o2, "Factoried new object");
3231             $test->ok($o2->isa('__PACKAGE__'), "Factory object isa class object");
3232             $test->is(__PACKAGE__->types($oldtypes), $oldtypes, "reset old types");
3233              
3234             =end btest(factory)
3235              
3236             =cut
3237              
3238             sub factory {
3239 4     4 1 416 my $class = shift;
3240              
3241 4         20 my %init = @_;
3242              
3243 4 50       20 if ($init{'type'}) {
3244              
3245 4         9 my $abstype = $init{'type'};
3246 4         10 delete $init{'type'};
3247              
3248 4 50       23 my $typeClass = $class->pkg_for_type($abstype) or return;
3249              
3250 4   33     28 return $typeClass->new(%init) || $class->error($typeClass->errvals);
3251             }
3252             else {
3253 0         0 return $class->new(@_);
3254             };
3255             }
3256              
3257             =pod
3258              
3259             =item copy
3260              
3261             Copies the object. B! Copy does a B copy of the object. So any objects/references/etc
3262             pointed to by the original object will also be copied.
3263              
3264             You may optionally pass in a different object/structure and copy that instead.
3265              
3266             my $backupBoard = $game->copy($game->board);
3267              
3268             =cut
3269              
3270             =pod
3271              
3272             =begin btest(copy)
3273              
3274             package Basset::Test::Testing::__PACKAGE__::copy::subclass;
3275             our @ISA = qw(__PACKAGE__);
3276              
3277             Basset::Test::Testing::__PACKAGE__::copy::subclass->add_attr('attr1');
3278             Basset::Test::Testing::__PACKAGE__::copy::subclass->add_attr('attr2');
3279             Basset::Test::Testing::__PACKAGE__::copy::subclass->add_attr('attr3');
3280              
3281             package __PACKAGE__;
3282              
3283             my $o = __PACKAGE__->new();
3284             $test->ok($o, "Instantiated object");
3285             my $o2 = $o->copy;
3286             $test->ok($o2, "Copied object");
3287             $test->is(length $o->dump, length $o2->dump, "dumps are same size");
3288              
3289             my $o3 = Basset::Test::Testing::__PACKAGE__::copy::subclass->new(
3290             'attr1' => 'first attribute',
3291             'attr2' => 'second attribute',
3292             'attr3' => 'third attribute'
3293             );
3294              
3295             $test->ok($o3, "Instantiated sub-object");
3296              
3297             $test->is($o3->attr1, 'first attribute', 'Subobject attr1 matches');
3298             $test->is($o3->attr2, 'second attribute', 'Subobject attr2 matches');
3299             $test->is($o3->attr3, 'third attribute', 'Subobject attr3 matches');
3300              
3301             my $o4 = $o3->copy;
3302              
3303             $test->ok($o4, "Copied sub-object");
3304              
3305             $test->is($o4->attr1, 'first attribute', 'Copied subobject attr1 matches');
3306             $test->is($o4->attr2, 'second attribute', 'Copied subobject attr2 matches');
3307             $test->is($o4->attr3, 'third attribute', 'Copied subobject attr3 matches');
3308              
3309             $test->is(length $o3->dump, length $o4->dump, "Sub object dumps are same size");
3310              
3311             my $array = ['a', 2, {'foo' => 'bar'}];
3312              
3313             $test->ok($array, "Got array");
3314              
3315             my $array2 = __PACKAGE__->copy($array);
3316              
3317             $test->ok($array2, "Copied array");
3318             $test->is($array->[0], $array2->[0], "First element matches");
3319             $test->is($array->[1], $array2->[1], "Second element matches");
3320             $test->is($array->[2]->{'foo'}, $array2->[2]->{'foo'}, "Third element matches");
3321              
3322             =end btest(copy)
3323              
3324             =cut
3325              
3326             sub copy {
3327 5     5 1 1454 my $self = shift;
3328 5   66     28 my $obj = shift || $self;
3329              
3330 5         28 my $objdump = $self->dump($obj);
3331 5         503 $objdump =~ /^(\$\w+)/;
3332              
3333 5         12 local $@ = undef;
3334 5         355 return eval qq{
3335             my $1;
3336             eval \$objdump;
3337             };
3338             }
3339              
3340             =pod
3341              
3342             =item pkg_for_type
3343              
3344             Use internally by factory(), also sometimes useful in code. Given a type, returns the class as defined in the conf file.
3345              
3346             my $class = Basset::Object->pkg_for_type('user'); #returns Basset::User (for example)
3347              
3348             =cut
3349              
3350             =pod
3351              
3352             =begin btest(pkg_for_type)
3353              
3354             $test->ok(__PACKAGE__->types, "Got types out of the conf file");
3355             my $typesbkp = __PACKAGE__->types();
3356             my $newtypes = {%$typesbkp, 'testtype1' => '__PACKAGE__', 'testtype2' => 'boguspkg'};
3357             $test->ok($typesbkp, "Backed up the types");
3358             $test->is(__PACKAGE__->types($newtypes), $newtypes, "Set new types");
3359             $test->is(__PACKAGE__->pkg_for_type('testtype1'), '__PACKAGE__', "Got class for new type");
3360             $test->ok(! scalar __PACKAGE__->pkg_for_type('testtype2'), "Could not access invalid type");
3361             $test->is(__PACKAGE__->errcode, 'BO-29', 'proper error code');
3362              
3363             __PACKAGE__->wipe_errors;
3364             $test->is(scalar(__PACKAGE__->pkg_for_type('testtype2', 'errorless')), undef, "Could not access invalid type w/ second arg");
3365             $test->is(scalar(__PACKAGE__->errcode), undef, 'no error code set w/second arg');
3366             $test->is(scalar(__PACKAGE__->errstring), undef, 'no error string set w/second arg');
3367              
3368             my $h = {};
3369              
3370             $test->is(__PACKAGE__->types($h), $h, 'wiped out types');
3371             $test->is(scalar(__PACKAGE__->pkg_for_type('testtype3')), undef, 'could not get type w/o types');
3372             $test->is(__PACKAGE__->errcode, 'BO-09', 'proper error code for no types');
3373              
3374             $test->is(__PACKAGE__->types($typesbkp), $typesbkp, "Re-set original types");
3375              
3376             =end btest(pkg_for_type)
3377              
3378             =cut
3379              
3380             sub pkg_for_type {
3381 189     189 1 2343 my $class = shift;
3382 189         284 my $abstype = shift;
3383             #this is a hack and not publically accessible. If you pass in a second parameter for pkg_for_type,
3384             #it won't report an error if it doesn't find the class. This should be used in one and only one place -
3385             #inside of the error method itself. error requests a notification center, and if there is no notification
3386             #center, then it needs to be able to continue. If pkg_for_type spit back an error, it'd fall into an infinite
3387             #recursion. So we take the 2nd parameter to prevent that from happening.
3388 189 100       599 my $errorless = @_ ? shift : 0;
3389              
3390 189         1420 my $types = $class->types;
3391              
3392 189         1138 my $pkg = $types->{$abstype};
3393              
3394 189 100       525 if (defined $pkg) {
3395              
3396 187 100       778 return unless $class->load_pkg($pkg, $errorless);
3397              
3398 184         1035 return $pkg;
3399              
3400             } else {
3401 2 100       12 return $errorless ? undef : $class->error("No class for type ($abstype)", "BO-09");
3402             }
3403              
3404             };
3405              
3406             =pod
3407              
3408             =item inherits
3409              
3410             This method is deprecated and b be removed in Basset 1.0.4. The concept remains the same, but I, like an idiot, overlooked a
3411             much simpler syntax. Just push the result of pkg_for_type onto @ISA as normal.
3412              
3413             use Basset::Object;
3414             our @ISA = Basset::Object->pkg_for_type('object');
3415              
3416             Voila! Same effect. You may now proceed to read the long expository explanation here as to why you would do that. This exposition is going
3417             to slide over into the pkg_for_type method.
3418              
3419             Basset is a nice framework. It kicks all sorts of ass. But, it's entirely possible that it's not quite functional enough for you.
3420             Let's say you work for some company, WidgetTech.
3421              
3422             WidgetTech has information in a database, it's mostly fairly object-relational in nature, you can certainly use Basset::Object::Persistent.
3423             So you go through and write up 50 modules that all inherit from Basset::Object::Persistent. All is right with the world.
3424              
3425             3 months later, someone decides that instead of deleting old records from the database, as you'd been doing, you need to instead
3426             leave them there and change their status flag to 'D'. The status flag is already there (you use it for other things, active, pending
3427             suspended, etc.). So you don't need to change anything in your modules - just add the drop down to your interface and all is good.
3428              
3429             2 days later, you're getting angry phonecalls from users saying that deleted data is showing up in the system. This is bad. You
3430             forgot that Basset::Object::Persistent doesn't know anything about status flags and just loads up everything. Very bad.
3431              
3432             Options? Well, you could go into every single module (50 of 'em) and override their load_all and delete methods.
3433             But man, that's gonna take forever. And probably get out of sync. And be a maintenance disaster. And it's just not the Basset way.
3434              
3435             So what do you do instead? You hack up Basset::Object::Persistent. You modify the load_all method so that it tacks on a where
3436             clause to exclude status of 'D'. You modify delete so that it just changes the status and re-commits. All is right with the world.
3437              
3438             A month later, I release a new version of Basset, you forget about the modifications, upgrade, and start getting calls from angry
3439             users. You need to re-hack the system.
3440              
3441             So, you realize, this isn't the best way to go. Instead, you write a new object - WidgetTech::Object::Persistent.
3442             WidgetTech::Object::Persistent inherits from Basset::Object::Persistent. You then do a search and replace on your 50 modules to
3443             change occurances of Basset::Object::Persistent to WidgetTech::Object::Persistent. You put your modified load_all and delete methods
3444             in WidgetTech::Object::Persistent and all is right with the world. I release a new version of Basset a week later, you drop it into
3445             place, there are no issues.
3446              
3447             Two months later, you decide that you need to override a method in Basset::Object. Or, you want a new method accessible to all of
3448             your objects. Easy - put it in the root class. Now, you've learned enough not to hack up Basset::Object, so you create WidgetTech::Object
3449             and add in your new method to there. Anything that did inherit from Basset::Object should now inherit WidgetTech::Object and everything's
3450             fine.
3451              
3452             Whoops. Except for WidgetTech::Object::Persistent. You have an inheritance tree like this:
3453              
3454             Basset::Object
3455             ^ ^
3456             | |
3457             | WidgetTech::Object
3458             |
3459             Basset::Object::Persistent
3460             ^
3461             |
3462             WidgetTech::Object::Persistent
3463              
3464             But you need this:
3465              
3466             Basset::Object
3467             ^
3468             |
3469             WidgetTech::Object
3470             ^
3471             |
3472             Basset::Object::Persistent
3473             ^
3474             |
3475             WidgetTech::Object::Persistent
3476              
3477             Your W::O::P inherit B::O::P which inherits B::O. And this all bypasses WidgetTech::Object. You don't want to stick the methods
3478             into WidgetTech::Object::Persistent, since they need to be accessible to all classes, not just persistent ones. You (obviously)
3479             know better than to hack Basset::Object::Persistent to inherit from WidgetTech::Object instead of Basset::Object. So what do you
3480             do?
3481              
3482             And all of this long expository setup brings us to the inherits method. Inheritance in Basset does not usually directly use @ISA.
3483             Instead, it uses the inherits class method and a classtype.
3484              
3485             package Basset::Object::Persistent;
3486              
3487             use Basset::Object;
3488             #deprecated old way:
3489             #Basset::Object->inherits(__PACKAGE__, 'object');
3490             #fancy new way:
3491             @ISA = ( Basset::Object->pkg_for_type('object') );
3492              
3493             Voila! That's basically equivalent to:
3494              
3495             package Basset::Object::Persistent;
3496              
3497             use Basset::Object;
3498             @ISA = qw(Basset::Object);
3499              
3500             Now, everybody knows that familiar @ISA = ... syntax, so why change it? If you read that story up above, you already know. This
3501             moves inheritance out of the module tree and into B. So now if you want to use WidgetTech::Objects as your root
3502             object, you just change your conf file:
3503              
3504             types %= object=WidgetTech::Object
3505              
3506             And blam-o. You have a new root class. Now, of course, Basset::Object will B be the top level root object in a Basset system.
3507             But you can now pretend that you have a different object instead. This new object sits in between Basset::Object and the rest of the
3508             world. Anything you want to change in Basset::Object is fair game. The only thing that B always be in Basset::Object is the
3509             inherits method. Other modules will expect Basset::Object to call inherits at their start to set up their @ISA for them, so you can't
3510             do away with it entirely.
3511              
3512             B. It's a compilation error, so it's not going to let you off the hook if it can't set up a relationship.
3513              
3514             You'll mostly be fine with using @ISA in your code.
3515              
3516             package WidgetTech::Widget;
3517             @ISA = qw(WidgetTech::Object::Persistent);
3518              
3519             You have control over WidgetTech::Widget and WidgetTech::Object::Persistent, and it's highly unlikely that you'll need to
3520             change your inheritance tree. Modifications can go in your super class or your subclass as needed and nobody cares about re-wiring
3521             it.
3522              
3523             =cut
3524              
3525             sub inherits {
3526 1     1 1 547 my $self = shift;
3527 1         2 my $pkg = shift;
3528 1         3 my @types = @_;
3529              
3530 8     8   66 no strict 'refs';
  8         22  
  8         9336  
3531              
3532 1         3 foreach my $type (@types) {
3533 1   50     5 my $parent = $self->pkg_for_type($type) || die $self->errstring;
3534              
3535 1         4 push @{$pkg . "::ISA"}, $parent;
  1         22  
3536             }
3537              
3538 1         4 return 1;
3539             }
3540              
3541             =pod
3542              
3543             =begin btest(inherits)
3544              
3545             package Basset::Test::Testing::__PACKAGE__::inherits::Subclass1;
3546             __PACKAGE__->inherits('Basset::Test::Testing::__PACKAGE__::inherits::Subclass1', 'object');
3547              
3548             package __PACKAGE__;
3549              
3550             $test->ok(Basset::Test::Testing::__PACKAGE__::inherits::Subclass1->isa('Basset::Object'), 'subclass inherits from root');
3551              
3552             =end btest(inherits)
3553              
3554             =cut
3555              
3556             =pod
3557              
3558             =item isa_path
3559              
3560             This is mainly used by the conf reader, but I wanted to make it publicly accessible. Given a class, it
3561             will return an arrayref containing all of the superclasses of that class, in inheritence order.
3562              
3563             Note that once a path is looked up for a class, it is cached. So if you dynamically change @ISA, it won't be reflected in the return of isa_path.
3564             Obviously, dynamically changing @ISA is frowned upon as a result.
3565              
3566             =cut
3567              
3568             =pod
3569              
3570             =begin btest(isa_path)
3571              
3572             $test->ok(__PACKAGE__->isa_path, "Can get an isa_path for root");
3573             my $path = __PACKAGE__->isa_path;
3574             $test->is($path->[-1], '__PACKAGE__', 'Class has self at end of path');
3575              
3576             package Basset::Test::Testing::__PACKAGE__::isa_path::subclass1;
3577             our @ISA = qw(__PACKAGE__);
3578              
3579             package Basset::Test::Testing::__PACKAGE__::isa_path::subclass2;
3580             our @ISA = qw(Basset::Test::Testing::__PACKAGE__::isa_path::subclass1);
3581              
3582             package __PACKAGE__;
3583              
3584             $test->ok(Basset::Test::Testing::__PACKAGE__::isa_path::subclass1->isa('__PACKAGE__'), 'Subclass of __PACKAGE__');
3585             $test->ok(Basset::Test::Testing::__PACKAGE__::isa_path::subclass2->isa('__PACKAGE__'), 'Sub-subclass of __PACKAGE__');
3586             $test->ok(Basset::Test::Testing::__PACKAGE__::isa_path::subclass1->isa('Basset::Test::Testing::__PACKAGE__::isa_path::subclass1'), 'Sub-subclass of subclass');
3587              
3588             $test->ok(Basset::Test::Testing::__PACKAGE__::isa_path::subclass1->isa_path, "We have a path");
3589             my $subpath = Basset::Test::Testing::__PACKAGE__::isa_path::subclass1->isa_path;
3590             $test->is($subpath->[-2], '__PACKAGE__', 'Next to last entry is parent');
3591             $test->is($subpath->[-1], 'Basset::Test::Testing::__PACKAGE__::isa_path::subclass1', 'Last entry is self');
3592              
3593             $test->ok(Basset::Test::Testing::__PACKAGE__::isa_path::subclass2->isa_path, "We have a sub path");
3594             my $subsubpath = Basset::Test::Testing::__PACKAGE__::isa_path::subclass2->isa_path;
3595              
3596             $test->is($subsubpath->[-3], '__PACKAGE__', 'Third to last entry is grandparent');
3597             $test->is($subsubpath->[-2], 'Basset::Test::Testing::__PACKAGE__::isa_path::subclass1', 'Second to last entry is parent');
3598             $test->is($subsubpath->[-1], 'Basset::Test::Testing::__PACKAGE__::isa_path::subclass2', 'Last entry is self');
3599              
3600             package Basset::Test::Testing::__PACKAGE__::isa_path::Subclass3;
3601              
3602             our @ISA = qw(__PACKAGE__ __PACKAGE__);
3603              
3604             package __PACKAGE__;
3605              
3606             my $isa = Basset::Test::Testing::__PACKAGE__::isa_path::Subclass3->isa_path;
3607             $test->ok($isa, "Got isa path");
3608              
3609             #$test->is(scalar(@$isa), 2, 'two entries in isa_path');
3610             $test->is($isa->[-2], '__PACKAGE__', 'Second to last entry is parent');
3611             $test->is($isa->[-1], 'Basset::Test::Testing::__PACKAGE__::isa_path::Subclass3', 'Last entry is self');
3612              
3613             =end btest(isa_path)
3614              
3615             =cut
3616              
3617             our $paths = {};
3618              
3619             sub isa_path {
3620              
3621 138 50   138 1 8676 my $class = $_[0]->can('pkg') ? shift->pkg() : shift;
3622 138   33     653 $class = ref $class || $class;
3623 138   100     19801 my $seen = shift || {};
3624              
3625 138 50       674 return if $seen->{$class}++;
3626              
3627 138 100       666 return $paths->{$class} if defined $paths->{$class};
3628              
3629 8     8   53 no strict 'refs';
  8         18  
  8         4680  
3630 39         59 my @i = @{$class . "::ISA"};
  39         252  
3631              
3632 39         83 my @s = ();
3633              
3634 39         82 foreach my $super (@i){
3635              
3636 36 100       135 next if $seen->{$super};
3637              
3638             #the method invocation is more consistent, but bonks on modules that aren't
3639             #subclasses of Basset::Object. So we call it as a function to display all modules
3640             #my $super_isa = $super->can('isa_path') ? $super->isa_path($seen) : [];
3641              
3642 35         143 my $super_isa = isa_path($super, $seen);
3643 35         148 push @s, @$super_isa;
3644             };
3645              
3646 39         90 push @s, $class;
3647              
3648 39         104 $paths->{$class} = \@s;
3649              
3650 39         152 return \@s;
3651              
3652             };
3653              
3654             =pod
3655              
3656             =item module_for_class
3657              
3658             Used mainly internally. Converts a perl package name to its file system equivalent. So,
3659             Basset::Object -> Basset/Object.pm and so on.
3660              
3661             =cut
3662              
3663             =pod
3664              
3665             =begin btest(module_for_class)
3666              
3667             $test->is(scalar(__PACKAGE__->module_for_class), undef, "Could not get module_for_class w/o package");
3668             $test->is(__PACKAGE__->errcode, "BO-20", 'proper error code');
3669             $test->is(__PACKAGE__->module_for_class('Basset::Object'), 'Basset/Object.pm', 'proper pkg -> file name');
3670             $test->is(__PACKAGE__->module_for_class('Basset::Object::Persistent'), 'Basset/Object/Persistent.pm', 'proper pkg -> file name');
3671             $test->is(__PACKAGE__->module_for_class('Basset::DB::Table'), 'Basset/DB/Table.pm', 'proper pkg -> file name');
3672              
3673             =end btest(module_for_class)
3674              
3675             =cut
3676              
3677             sub module_for_class {
3678 194     194 1 3459 my $self = shift;
3679 194 100       582 my $pkg = shift or return $self->error("Cannot check for included-ness w/o package", "BO-20");
3680              
3681 193         936 $pkg =~ s!::!/!g;
3682 193         396 $pkg .= '.pm';
3683              
3684 193         2108 return $pkg;
3685             };
3686              
3687             =pod
3688              
3689             =item conf
3690              
3691             conf is just a convenience wrapper around read_conf_file.
3692              
3693             $obj->conf === Basset::Object::Conf->read_conf_file;
3694              
3695             =cut
3696              
3697             =pod
3698              
3699             =begin btest(conf)
3700              
3701             $test->ok(scalar __PACKAGE__->conf, "Class accessed conf file");
3702             my $o = __PACKAGE__->new();
3703             $test->ok(scalar $o, "Got object");
3704             $test->ok(scalar $o->conf, "Object accessed conf file");
3705              
3706             =end btest(conf)
3707              
3708             =cut
3709              
3710             sub conf {
3711 706     706 1 3416 my $self = shift->pkg;
3712 706   100     2434 my $local = shift || 0;
3713              
3714 706 50       10127 my $conf = $self->_conf_class->read_conf_file
3715             or return $self->error($self->_conf_class->errvals);
3716              
3717 706 100 100     3107 if ($local && defined $conf->{$self}) {
    100          
3718 118         354 return $conf->{$self};
3719             }
3720             elsif ($local) {
3721 40         132 return {};
3722             }
3723             else {
3724 548         1703 return $conf;
3725             }
3726             };
3727              
3728             =pod
3729              
3730             =item today
3731              
3732             Convenience method. Returns today's date in a YYYY-MM-DD formatted string
3733              
3734             =cut
3735              
3736             =pod
3737              
3738             =begin btest(today)
3739              
3740             $test->like(__PACKAGE__->today, qr/^\d\d\d\d-\d\d-\d\d$/, 'matches date regex');
3741             $test->like(__PACKAGE__->today('abc'), qr/^\d\d\d\d-\d\d-\d\d$/, 'matches date regex despite input');
3742              
3743             =end btest(today)
3744              
3745             =cut
3746              
3747             sub today {
3748 2     2 1 774 my @today = localtime;
3749 2         34 sprintf("%04d-%02d-%02d", $today[5] + 1900, $today[4] + 1, $today[3]);
3750             }
3751              
3752             =pod
3753              
3754             =item now
3755              
3756             Convenience method. Returns a timestamp in a YYYY-MM-DD HH:MM:SS formatted string
3757              
3758             =cut
3759              
3760             =pod
3761              
3762             =begin btest(now)
3763              
3764             $test->like(__PACKAGE__->now, qr/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/, 'matches timestamp regex');
3765             $test->like(__PACKAGE__->now('def'), qr/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/, 'matches timestamp regex despite input');
3766              
3767             =end btest(now)
3768              
3769             =cut
3770              
3771             sub now {
3772 2     2 1 594 my @today = localtime;
3773 2         28 sprintf("%04d-%02d-%02d %02d:%02d:%02d", $today[5] + 1900, $today[4] + 1, $today[3], @today[2,1,0]);
3774             }
3775              
3776             =pod
3777              
3778             =item gen_handle
3779              
3780             returns a filehandle in a different package. Useful for when you need to open filehandles and pass 'em around.
3781              
3782             my $handle = Basset::Object->gen_handle();
3783             open ($handle, "/path/to/my/list");
3784              
3785             All but identical to gensym in Symbol by this point.
3786              
3787             =cut
3788              
3789             =pod
3790              
3791             =begin btest(gen_handle)
3792              
3793             $test->ok(__PACKAGE__->gen_handle, "Generated handle");
3794             my $h = __PACKAGE__->gen_handle;
3795             $test->ok($h, "Generated second handle");
3796             $test->is(ref $h, "GLOB", "And it's a globref");
3797              
3798             =end btest(gen_handle)
3799              
3800             =cut
3801              
3802             our $handle = 0;
3803              
3804             sub gen_handle {
3805 8     8   53 no strict 'refs';
  8         19  
  8         20130  
3806 22     22 1 629 my $self = shift;
3807 22         74 my $name = "HANDLE" . $handle++;
3808              
3809 22         36 my $h = \*{"Basset::Object::Handle::" . $name}; #You'll note that I don't want my
  22         146  
3810             #namespace polluted either
3811 22         66 delete $Basset::Object::Handle::{$name};
3812 22         71 return $h;
3813             };
3814              
3815             =pod
3816              
3817             =item perform
3818              
3819             if I were writing this in objective-C, I'd call it performSelectors:withObjects: Ho hum. I've really grown fond of the objective-C
3820             syntax. Anyway, since I can't do that, it's just called perform.
3821              
3822             $object->perform(
3823             'methods' => [qw(name password address)],
3824             'values' => ['Jim', 'password', 'Chew St']
3825             ) || die $object->errstring;
3826              
3827             Given a list of methods and values, it calls each method in turn with each value passed. If anything fails, it an error and stops
3828             proceeding through the list.
3829              
3830             Optionally, you may pass in a dereference hash to dereference an arrayref or hashref.
3831              
3832             $object->perform(
3833             'methods' => [qw(name password address permission)],
3834             'values' => ['Jim', 'password', 'Chew St', ['PT07', 'AB']],
3835             'dereference' => [qw(permission)],
3836             ) || die $object->errstring;
3837              
3838             With the dereference value, it calls
3839              
3840             $object->permission('PT07', 'AB');
3841              
3842             Without the dereference value, it calls
3843              
3844             $object->permission(['PT07', 'AB']);
3845              
3846             This can (obviously) even be called with a single method. This is preferrable to just calling $obj->$method(@args) in the code
3847             if $method is not guaranteed to be callable since perform automatically does a 'can' check on the method for you.
3848              
3849             Optionally, you may also pass in a continue parameter.
3850              
3851             $object->perform(
3852             'methods' => [qw(name password address permission)],
3853             'values' => ['Jim', 'password', 'Chew St', ['PT07', 'AB']],
3854             'dereference' => [qw(permission)],
3855             'continue' => 1
3856             ) || die $object->errstring;
3857              
3858             continue should be used with great caution. continue will cause execution to continue even if an error occurs. At the end, you'll
3859             still get an undef back, and your error message will be a list of \n delimited error messages, your error code will be a list of \n
3860             delimited error codes. This is appropriate if you want to set multiple attributes at once (or other methods that are indpendent of each
3861             other) and want to report all errors en masse at the end.
3862              
3863             =cut
3864              
3865             =pod
3866              
3867             =begin btest(perform)
3868              
3869             package Basset::Test::Testing::__PACKAGE__::perform::Subclass;
3870             our @ISA = qw(__PACKAGE__);
3871              
3872             Basset::Test::Testing::__PACKAGE__::perform::Subclass->add_attr('attr1');
3873             Basset::Test::Testing::__PACKAGE__::perform::Subclass->add_attr('attr2');
3874             Basset::Test::Testing::__PACKAGE__::perform::Subclass->add_attr('attr3');
3875              
3876             sub method1 {
3877             return 77;
3878             }
3879              
3880             sub method2 {
3881             my $self = shift;
3882             return scalar @_;
3883             };
3884              
3885             package __PACKAGE__;
3886              
3887             $test->ok(Basset::Test::Testing::__PACKAGE__::perform::Subclass->isa('__PACKAGE__'), 'we have a subclass');
3888             $test->ok(Basset::Test::Testing::__PACKAGE__::perform::Subclass->can('attr1'), 'subclass has attr1');
3889             $test->ok(Basset::Test::Testing::__PACKAGE__::perform::Subclass->can('attr2'), 'subclass has attr2');
3890             $test->ok(Basset::Test::Testing::__PACKAGE__::perform::Subclass->can('attr2'), 'subclass has attr3');
3891             $test->ok(Basset::Test::Testing::__PACKAGE__::perform::Subclass->can('method1'), 'subclass has method1');
3892             $test->ok(Basset::Test::Testing::__PACKAGE__::perform::Subclass->can('method2'), 'subclass has method2');
3893             $test->is(scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->method1, 77, 'method1 returns 77');
3894             $test->is(scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->method2, 0, 'method2 behaves as expected');
3895             $test->is(scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->method2('a'), 1, 'method2 behaves as expected');
3896             $test->is(scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->method2(0,0), 2, 'method2 behaves as expected');
3897              
3898             my $o = Basset::Test::Testing::__PACKAGE__::perform::Subclass->new();
3899              
3900             $test->ok($o, "Instantiated object");
3901              
3902             my $class = 'Basset::Test::Testing::__PACKAGE__::perform::Subclass';
3903              
3904             $test->is(scalar($class->perform), undef, "Cannot perform w/o method");
3905             $test->is($class->errcode, 'BO-04', 'proper error code');
3906             $test->is(scalar($class->perform('methods' => 'able')), undef, "Cannot perform w/o values");
3907             $test->is($class->errcode, 'BO-05', 'proper error code');
3908             $test->is(scalar($class->perform('methods' => 'able', 'values' => 'baker')), undef, "methods must be arrayref");
3909             $test->is($class->errcode, 'BO-11', 'proper error code');
3910             $test->is(scalar($class->perform('methods' => ['able'], 'values' => 'baker')), undef, "values must be arrayref");
3911             $test->is($class->errcode, 'BO-12', 'proper error code');
3912              
3913             $test->ok(
3914             scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->perform(
3915             'methods' => ['method1'],
3916             'values' => ['a'],
3917             ),
3918             "Class performs method1");
3919              
3920             $test->ok(
3921             scalar $o->perform(
3922             'methods' => ['method1'],
3923             'values' => ['a'],
3924             ),
3925             "Object performs method1");
3926              
3927             $test->ok(!
3928             scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->perform(
3929             'methods' => ['method2'],
3930             'values' => [],
3931             ),
3932             "Class cannot perform method2 w/o args");
3933              
3934             $test->ok(
3935             scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->perform(
3936             'methods' => ['method2'],
3937             'values' => ['a']
3938             ),
3939             "Class performs method2 w/1 arg");
3940              
3941             $test->ok(
3942             scalar Basset::Test::Testing::__PACKAGE__::perform::Subclass->perform(
3943             'methods' => ['method2'],
3944             'values' => ['b'],
3945             ),
3946             "Class performs method2 w/1 arg in arrayref");
3947              
3948             $test->ok(!
3949             scalar $o->perform(
3950             'methods' => ['attr1'],
3951             'values' => []
3952             ),
3953             "object cannot access attribute w/o args"
3954             );
3955              
3956             $test->is(scalar $o->attr1, undef, 'attr1 is undefined');
3957             $test->is(scalar $o->attr2, undef, 'attr2 is undefined');
3958             $test->is(scalar $o->attr3, undef, 'attr3 is undefined');
3959              
3960             $test->ok(
3961             scalar $o->perform(
3962             'methods' => ['attr1'],
3963             'values' => ['attr1_val']
3964             ),
3965             "object performed attr1"
3966             );
3967              
3968             $test->is(scalar $o->attr1(), 'attr1_val', 'attr1 set via perform');
3969              
3970             $test->ok(
3971             scalar $o->perform(
3972             'methods' => ['attr2', 'attr3'],
3973             'values' => ['attr2_val', 'attr3_val']
3974             ),
3975             "object performed attr2, attr3"
3976             );
3977              
3978             $test->is(scalar $o->attr2(), 'attr2_val', 'attr2 set via perform');
3979             $test->is(scalar $o->attr3(), 'attr3_val', 'attr3 set via perform');
3980              
3981             $test->ok(!
3982             scalar $o->perform(
3983             'methods' => ['attr4'],
3984             'values' => ['attr4_val']
3985             ),
3986             "object cannot perform unknown method"
3987             );
3988              
3989             $test->ok(!
3990             scalar $o->perform(
3991             'methods' => ['attr4', 'attr2'],
3992             'values' => ['attr4_val', 'attr2_val_2'],
3993             ),
3994             'object cannot perform unknown method w/known method'
3995             );
3996              
3997             $test->is(scalar $o->attr2, 'attr2_val', 'attr2 unchanged');
3998              
3999             $test->ok(!
4000             scalar $o->perform(
4001             'methods' => ['attr1'],
4002             'values' => [undef]
4003             ),
4004             "object failed trying to perform attr1"
4005             );
4006              
4007             $test->ok(!
4008             scalar $o->perform(
4009             'methods' => ['attr1', 'attr2'],
4010             'values' => [undef, 'attr2_val_2'],
4011             ),
4012             'object failed trying to perform attr1'
4013             );
4014              
4015             $test->is(scalar $o->attr2, 'attr2_val', 'attr2 unchanged');
4016              
4017             $test->ok(!
4018             scalar $o->perform(
4019             'methods' => ['attr1', 'attr2'],
4020             'values' => [undef, 'attr2_val_2'],
4021             'continue' => 1,
4022             ),
4023             'object failed trying to perform attr1'
4024             );
4025              
4026             $test->is(scalar $o->attr2, 'attr2_val_2', 'attr2 changed due to continue');
4027              
4028             my $arr = ['a', 'b'];
4029             $test->ok($arr, "Have an arrayref");
4030              
4031             $test->ok(
4032             scalar $o->perform(
4033             'methods' => ['attr3'],
4034             'values' => [$arr],
4035             ),
4036             "Performed attr3"
4037             );
4038              
4039             $test->is($o->attr3, $arr, "attr3 contains arrayref");
4040              
4041             $test->ok(
4042             scalar $o->perform(
4043             'methods' => ['attr3'],
4044             'values' => [$arr],
4045             'dereference' => ['attr3'],
4046             ),
4047             "Performed attr3 with de-reference"
4048             );
4049              
4050             $test->is($o->attr3, 'a', "attr3 contains first element of arrayref");
4051              
4052             $test->ok(
4053             scalar $o->perform(
4054             'methods' => ['attr2', 'attr3'],
4055             'values' => [$arr, $arr],
4056             'dereference' => ['attr2'],
4057             ),
4058             "Performed attr3 with de-reference"
4059             );
4060              
4061             $test->is($o->attr2, 'a', "attr2 contains first element of arrayref");
4062             $test->is($o->attr3, $arr, "attr3 contains arrayref");
4063              
4064             =end btest(perform)
4065              
4066             =cut
4067              
4068             sub perform {
4069 20     20 1 562 my $self = shift;
4070              
4071 20         81 my %args = @_;
4072              
4073 20 100       81 my $methods = $args{'methods'} or return $self->error("Cannot perform w/o methods", "BO-04");
4074 19 100       61 my $values = $args{'values'} or return $self->error("Cannot perform w/o values", "BO-05");
4075 18 100       28 my $deref = {map {$_, 1} @{$args{'dereference'} || []}};
  2         10  
  18         102  
4076 18   100     82 my $continue= $args{'continue'} || 0;
4077              
4078 18 100       61 return $self->error("methods must be arrayref", "BO-11") unless ref $methods eq 'ARRAY';
4079 17 100       47 return $self->error("values must be arrayref", "BO-12") unless ref $values eq 'ARRAY';
4080              
4081 16 100       57 return $self->error('Cannot perform. Different number of methods and values', 'BO-07') unless @$methods == @$values;
4082              
4083 14         22 my @errors = ();
4084 14         22 my @codes = ();
4085              
4086             #non destructive copies
4087 14         48 ($methods, $values) = ([@$methods], [@$values]);
4088              
4089 14         42 while (@$methods) {
4090 17         27 my $method = shift @$methods;
4091 17         29 my $value = shift @$values;
4092              
4093 17         25 my @args = ($value);
4094              
4095 17 100 100     112 if (ref $value eq 'ARRAY' && $deref->{$method}) {
    50 33        
4096 2         8 @args = @$value;
4097             } elsif (ref $value eq 'HASH' && $deref->{$method}) {
4098 0         0 @args = %$value;
4099             };
4100              
4101 17 100       96 if ($self->can($method)) {
4102 15 100       49 unless (defined $self->$method(@args)) {
4103 3 100       9 if ($args{'continue'}) {
4104 1         3 push @errors, $self->error();
4105 1   50     5 push @codes, $self->errcode || "BO-06";
4106             } else {
4107 2 50       7 $value = defined $value ? $value : 'value is undefined';
4108 2   50     10 return $self->error("Could not perform method ($method) with value ($value) : " . $self->error(), $self->errcode || "BO-06");
4109             }
4110             }
4111             } else {
4112 2         14 return $self->error("Object cannot perform method ($method)", "BO-10");
4113             };
4114             };
4115              
4116 10 100       54 if (@errors) {
4117 1         6 return $self->error(join("\n", @errors), join("\n", @codes));
4118             } else {
4119 9         68 return 1;
4120             };
4121              
4122             };
4123              
4124             =pod
4125              
4126             =item stack_trace
4127              
4128             A method useful for debugging. When called, returns a stack trace.
4129              
4130             sub some_method {
4131             my $self = shift;
4132             #you know something weird happens here.
4133             print STDERR $self->stack_trace();
4134             };
4135              
4136             =cut
4137              
4138             =pod
4139              
4140             =begin btest(stack_trace)
4141              
4142             sub tracer {
4143             return __PACKAGE__->stack_trace;
4144             };
4145              
4146             $test->ok(tracer(), "Got a stack trace");
4147             my $trace = tracer();
4148             $test->ok($trace, "Has a stack trace");
4149             $test->like($trace, qr/Package:/, "Contains word: 'Package:'");
4150             $test->like($trace, qr/Filename:/, "Contains word: 'Filename:'");
4151             $test->like($trace, qr/Line number:/, "Contains word: 'Line number:'");
4152             $test->like($trace, qr/Subroutine:/, "Contains word: 'Subroutine:'");
4153             $test->like($trace, qr/Has Args\? :/, "Contains word: 'Has Args:'");
4154             $test->like($trace, qr/Want array\? :/, "Contains word: 'Want array:'");
4155             $test->like($trace, qr/Evaltext:/, "Contains word: 'Evaltext:'");
4156             $test->like($trace, qr/Is require\? :/, "Contains word: 'Is require:'");
4157              
4158             =end btest(stack_trace)
4159              
4160             =cut
4161              
4162             sub stack_trace {
4163 2     2 1 375 my $caller_count = 1;
4164 2         3 my $caller_stack = undef;
4165 2         8 my @verbose_caller = ("Package: ", "Filename: ", "Line number: ", "Subroutine: ", "Has Args? : ",
4166             "Want array? : ", "Evaltext: ", "Is require? : ");
4167              
4168 2 50       10 push @verbose_caller, ("Hints: ", "Bitmask: ") if $] >= 5.006; #5.6 has a more verbose caller stack.
4169              
4170 2         17 while (my @caller = caller($caller_count++)){
4171 2         4 $caller_stack .= "\t---------\n";
4172 2         8 foreach (0..$#caller){
4173 22 100       37 my $callvalue = defined $caller[$_] ? $caller[$_] : '';
4174 22         100 $caller_stack .= "\t\t$verbose_caller[$_]$callvalue\n";# if $caller[$_];
4175             };
4176             };
4177              
4178 2         4 $caller_stack .= "\t---------\n";
4179 2         12 return $caller_stack;
4180             };
4181              
4182             =pod
4183              
4184             =item no_op
4185              
4186             no_op is a simple little method that just always returns 1, no matter what. Useful for cases where
4187             you want to be able to call a method and have it succeed, such as a generic place holder.
4188              
4189             =cut
4190              
4191             =pod
4192              
4193             =begin btest(no_op)
4194              
4195             $test->ok(__PACKAGE__->no_op, "No op");
4196             $test->is(__PACKAGE__->no_op, 1, "No op is 1");
4197             my $obj = __PACKAGE__->new();
4198             $test->ok($obj, "Got object");
4199             $test->ok($obj->no_op, "Object no ops");
4200             $test->is($obj->no_op, 1, "Object no op is 1");
4201              
4202             =end btest(no_op)
4203              
4204             =cut
4205              
4206 32     32 1 14395 sub no_op { return 1 };
4207              
4208             =pod
4209              
4210             =item system_prefix
4211              
4212             Returns the prefix used by the system for internal methods as generated by add_attr and the like.
4213              
4214             =cut
4215              
4216             =pod
4217              
4218             =begin btest(system_prefix)
4219              
4220             $test->is(__PACKAGE__->system_prefix(), '__b_', 'expected system prefix');
4221              
4222             =end btest(system_prefix)
4223              
4224             =cut
4225              
4226 160     160 1 1364 sub system_prefix { return '__b_'};
4227              
4228             =pod
4229              
4230             =item privatize
4231              
4232             Returns a method prepended with the system prefix, useful for making private methods.
4233              
4234             Some::Class->privatize('foo'); #returns Some::Class->system_prefix . 'foo';
4235              
4236             =cut
4237              
4238             sub privatize {
4239 56     56 1 882 my $class = shift;
4240 56 100       145 my $method = shift or return $class->error("Cannot privatize w/o method", "BO-24");
4241              
4242 55         177 my $prefix = $class->system_prefix;
4243 55 100       208 return index($method, $prefix) >= 0
4244             ? $method
4245             : $class->system_prefix . $method;
4246             }
4247              
4248             =pod
4249              
4250             =begin btest(privatize)
4251              
4252             $test->ok(! __PACKAGE__->privatize, 'Cannot privatize w/o method');
4253             $test->is(__PACKAGE__->errcode, "BO-24", "proper error code");
4254              
4255             $test->is(__PACKAGE__->privatize('foo'), '__b_foo', "privatized foo");
4256             $test->is(__PACKAGE__->privatize('__b_foo'), '__b_foo', "__b_foo remains __b_foo");
4257              
4258             =end btest(privatize)
4259              
4260             =cut
4261              
4262             =pod
4263              
4264             =item deprivatize
4265              
4266             Returns a method with the system prefix removed, useful for unmaking private methods.
4267              
4268             Some::Class->deprivatize('__b_foo'); #returns 'foo';
4269              
4270             =cut
4271              
4272             sub deprivatize {
4273 3     3 1 948 my $class = shift;
4274 3 100       17 my $method = shift or return $class->error("Cannot deprivatize w/o method", "BO-25");
4275              
4276 2         12 my $prefix = $class->system_prefix;
4277              
4278 2 100       10 if (index($method, $prefix) == 0) {
4279 1         4 $method = substr($method, length $prefix);
4280             }
4281              
4282 2         9 return $method;
4283             }
4284              
4285             =pod
4286              
4287             =begin btest(deprivatize)
4288              
4289             $test->ok(! __PACKAGE__->deprivatize, 'Cannot deprivatize w/o method');
4290             $test->is(__PACKAGE__->errcode, "BO-25", "proper error code");
4291              
4292             $test->is(__PACKAGE__->deprivatize('foo'), 'foo', "deprivatized foo");
4293             $test->is(__PACKAGE__->deprivatize('__b_foo'), 'foo', "deprivatized __b_foo");
4294              
4295             =end btest(deprivatize)
4296              
4297             =cut
4298              
4299             =pod
4300              
4301             =item is_private
4302              
4303             Returns a true value if the method is private (starts with system prefix), and false otherwise.
4304              
4305             Some::Class->is_private('__b_foo'); #returns true;
4306             Some::Class->is_private('foo'); #returns false;
4307              
4308             =cut
4309              
4310             sub is_private {
4311 3     3 1 646 my $class = shift;
4312 3 100       14 my $method = shift or return $class->error("Cannot determine is_private w/o method", "BO-26");
4313              
4314 2         9 return index($method, $class->system_prefix) == 0;
4315             }
4316              
4317             =pod
4318              
4319             =begin btest(deprivatize)
4320              
4321             $test->ok(! __PACKAGE__->is_private, 'Cannot is_private w/o method');
4322             $test->is(__PACKAGE__->errcode, "BO-26", "proper error code");
4323              
4324             $test->ok(! __PACKAGE__->is_private('foo'), 'foo is not private');
4325             $test->ok(__PACKAGE__->is_private('__b_foo'), '__b_foo is private');
4326              
4327             =end btest(deprivatize)
4328              
4329             =cut
4330              
4331             =pod
4332              
4333             =item cast
4334              
4335             Returns the object casted to the given class.
4336              
4337             my $object = Some::Class->new();
4338             my $casted = $object->cast('Some::Class::Subclass');
4339              
4340             If passed a second true argument, returns a copy of the object casted.
4341              
4342             my $object = Some::Class->new();
4343             my $castedCopy = $object->cast('Some::Class::Subclass', 'copy');
4344              
4345             =cut
4346              
4347             sub cast {
4348 5     5 1 2434 my $self = shift;
4349              
4350 5 100       32 return $self->error("Can only cast objects", "BO-21") unless ref $self;
4351              
4352 4 100       25 my $class = shift or return $self->error("Cannot cast w/o class", "BO-22");
4353 2   50     9 my $should_copy = shift || 0;
4354              
4355 2         5 my $cast = undef;
4356              
4357 2 50       8 if ($should_copy) {
4358 2 50       9 $cast = $self->copy or return;
4359             } else {
4360 0         0 $cast = $self;
4361             }
4362              
4363 2 50       15 $self->load_pkg($class) or return;
4364              
4365 2         16 return bless $cast, $class;
4366              
4367             }
4368              
4369             =pod
4370              
4371             =begin btest(cast)
4372              
4373             package Basset::Test::Testing::__PACKAGE__::cast::Subclass1;
4374             our @ISA = qw(__PACKAGE__);
4375              
4376             package __PACKAGE__;
4377              
4378             #pretend it was loaded normally
4379             $INC{__PACKAGE__->module_for_class("Basset::Test::Testing::__PACKAGE__::cast::Subclass1")}++;
4380              
4381             my $subclass = "Basset::Test::Testing::__PACKAGE__::cast::Subclass1";
4382              
4383             $test->ok(! __PACKAGE__->cast, "Cannot cast classes");
4384             $test->is(__PACKAGE__->errcode, "BO-21", "proper error code");
4385              
4386             my $o = __PACKAGE__->new();
4387             $test->ok($o, "got object");
4388              
4389             $test->ok(! $o->cast, "Cannot cast w/o class");
4390             $test->is($o->errcode, "BO-22", "proper error code");
4391             my $c = $o->cast($subclass, 'copy');
4392             $test->ok($c, "casted object");
4393             $test->is($o->pkg, "__PACKAGE__", "original part of super package");
4394             $test->is($c->pkg, $subclass, "casted object part of sub package");
4395             $test->is($c->errcode, $o->errcode, "error codes match, rest is assumed");
4396              
4397             my $o2 = __PACKAGE__->new();
4398             $test->ok($o2, "got object");
4399              
4400             $test->ok(! $o2->cast, "Cannot cast w/o class");
4401             $test->is($o2->errcode, "BO-22", "proper error code");
4402             my $c2 = $o2->cast($subclass, 'copy');
4403             $test->ok($c2, "casted object");
4404             $test->is($o2->pkg, "__PACKAGE__", "original part of super package");
4405             $test->is($c2->pkg, $subclass, "casted object part of sub package");
4406             $test->is($c2->errcode, $o->errcode, "error codes match, rest is assumed");
4407              
4408             =end btest(cast)
4409              
4410             =cut
4411              
4412             #used for introspection.
4413             __PACKAGE__->add_trickle_class_attr('_class_attributes', {});
4414             __PACKAGE__->add_trickle_class_attr('_instance_attributes', {});
4415              
4416             # _obj_error is the object attribute slot for storing the most recent error that occurred. It is
4417             # set via the first argument to the ->error method when called with an object.
4418             # i.e., $obj->error('foo', 'bar'); #_obj_error is 'foo'
4419             __PACKAGE__->add_attr('_obj_error');
4420              
4421             # _obj_errcode is the object attribute slot for storing the most recent error code that occurred. It is
4422             # set via the second argument to the ->error method when called with an object.
4423             # i.e., $obj->error('foo', 'bar'); #_obj_errcode is 'bar'
4424             __PACKAGE__->add_attr('_obj_errcode');
4425              
4426             # _pkg_error is the class attribute slot for storing the most recent error that occurred. It is
4427             # set via the first argument to the ->error method when called with a class.
4428             # i.e., $class->error('foo', 'bar'); #_pkg_error is 'foo'
4429             __PACKAGE__->add_trickle_class_attr('_pkg_error');
4430              
4431             # _pkg_errcode is the class attribute slot for storing the most recent error code that occurred. It is
4432             # set via the second argument to the ->error method when called with a class.
4433             # i.e., $class->error('foo', 'bar'); #_pkg_errcode is 'bar'
4434             __PACKAGE__->add_trickle_class_attr('_pkg_errcode');
4435              
4436             =pod
4437              
4438             =back
4439              
4440             =head1 ATTRIBUTES
4441              
4442             =over
4443              
4444             =item errortranslator
4445              
4446             The errortranslator needs to be set to a hashref, and it translates programmer
4447             readable errors into user readable errors. It's clunky and a mess and a hack, but it works.
4448              
4449             __PACKAGE__->errortranslator(
4450             {
4451             'violation of key constraint foo: Cannot INSERT' => 'Please specify a value for foo'
4452             }
4453             );
4454              
4455             $obj->do_something || die $obj->error(); # dies 'violation of key constraint foo: Cannot INSERT'
4456             $obj->do_something || die $obj->usererror();# dies 'Please specify a value for foo'
4457              
4458             The error translator looks at the error values, and if a more friendly user error exists, it returns that one instead.
4459             errortranslator looks at and returns (in order):
4460              
4461             the actual error,
4462             the raw error,
4463             the error code,
4464             a '*' wildcard,
4465             and then just returns the original error w/o modification.
4466              
4467             Be careful using the '*' wildcard. This will translate -any- error message that doesn't have a friendlier version.
4468              
4469             =cut
4470              
4471             =pod
4472              
4473             =begin btest(errortranslator)
4474              
4475             my $uses_real = __PACKAGE__->use_real_errors();
4476             $test->is(__PACKAGE__->use_real_errors(0), 0, "Uses real errors");
4477              
4478             my $translator = {
4479             'test error' => 'test message'
4480             };
4481              
4482             $test->ok($translator, "Created translator");
4483             $test->is(__PACKAGE__->errortranslator($translator), $translator, "Set translator");
4484             $test->is(scalar __PACKAGE__->error('test error', 'test code'), undef, "Set error");
4485             $test->is(__PACKAGE__->usererror(), 'test message', 'Re-wrote error message');
4486              
4487             $test->is(__PACKAGE__->errortranslator($uses_real), $uses_real, 'Class reset uses real error');
4488              
4489             =end btest(errortranslator)
4490              
4491             =cut
4492              
4493             # The error translator turns system defined error messages into user readable error messages.
4494             # It's clunky, but it's the best we've got for now.
4495             __PACKAGE__->add_trickle_class_attr('errortranslator');
4496              
4497             =pod
4498              
4499             =item use_real_errors
4500              
4501             use_real_errors bypasses the errortranslator and only returns the errstring. This is useful so that your developers can get
4502             back useful information, but your users can get back a friendly message.
4503              
4504             =cut
4505              
4506             =begin btest(use_real_errors)
4507              
4508             my $translator = __PACKAGE__->errortranslator();
4509             $test->ok(__PACKAGE__->errortranslator(
4510             {
4511             'test code' => "friendly test message",
4512             'formatted test error %d' => "friendlier test message",
4513             'formatted test error 7' => 'friendliest test message',
4514             'extra error' => 'friendliest test message 2'
4515             }),
4516             'Class set error translator'
4517             );
4518              
4519             my $uses_real = __PACKAGE__->use_real_errors();
4520              
4521             my $confClass = __PACKAGE__->pkg_for_type('conf');
4522             $test->ok($confClass, "Got conf");
4523              
4524             my $cfg = $confClass->conf;
4525             $test->ok($cfg, "Got configuration");
4526              
4527             $test->ok($cfg->{"Basset::Object"}->{'use_real_errors'} = 1, "enables real errors");
4528              
4529             $test->is(scalar __PACKAGE__->error("extra error", "test code"), undef, "Class sets error");
4530             $test->is(__PACKAGE__->usererror(), "extra error...with code (test code)", "Class gets literal error for literal");
4531              
4532             $test->is(scalar __PACKAGE__->error(["formatted test error %d", 7], "test code"), undef, "Class sets formatted error");
4533             $test->is(__PACKAGE__->usererror(), "formatted test error 7...with code (test code)", "Class gets literal error for formatted string");
4534              
4535             $test->is(scalar __PACKAGE__->error(["formatted test error %d", 9], "test code"), undef, "Class sets formatted error");
4536             $test->is(__PACKAGE__->usererror(), "formatted test error 9...with code (test code)", "Class gets literal error for string format");
4537              
4538             $test->is(scalar __PACKAGE__->error("Some test error", "test code"), undef, "Class sets standard error");
4539             $test->is(__PACKAGE__->usererror(), "Some test error...with code (test code)", "Class gets literal error for error code");
4540              
4541             $test->is(scalar __PACKAGE__->error("Some unknown error", "unknown code"), undef, "Class sets standard error w/o translation");
4542             $test->is(__PACKAGE__->usererror(), "Some unknown error...with code (unknown code)", "Class gets no user error");
4543              
4544             $test->ok(__PACKAGE__->errortranslator(
4545             {
4546             'test code' => "friendly test message",
4547             'formatted test error %d' => "friendlier test message",
4548             'formatted test error 7' => 'friendliest test message',
4549             'extra error' => 'friendliest test message 2',
4550             '*' => 'star error',
4551             }),
4552             'Class changed error translator'
4553             );
4554              
4555             $test->is(scalar __PACKAGE__->error("Some unknown error", "unknown code"), undef, "Class sets standard error w/o translation");
4556             $test->is(__PACKAGE__->usererror(), "Some unknown error...with code (unknown code)", "Class gets literal star error");
4557              
4558             $test->is(__PACKAGE__->errortranslator($translator), $translator, 'Class reset error translator');
4559             #$test->is(__PACKAGE__->errortranslator($uses_real), $uses_real, 'Class reset uses real error');
4560             #$test->ok('foo', 'bar');
4561             $test->is($cfg->{"__PACKAGE__"}->{'use_real_errors'} = $uses_real, $uses_real, "enables reset uses real errors");
4562              
4563             =end btest(use_real_errors)
4564              
4565             =cut
4566              
4567             __PACKAGE__->add_default_class_attr('use_real_errors');
4568              
4569             =pod
4570              
4571             =item delegate
4572              
4573             This is borrows from objective-C, because I like it so much. Basically, the delegate is a simple
4574             catch all place for an additional object that operates on your current object.
4575              
4576             sub some_method {
4577             my $self = shift;
4578             #call the delegate when we call some_method
4579             if ($self->delegate && $self->delegate->can('foo')) {
4580             $self->delegate->foo(@useful_arguments);
4581             };
4582             }
4583              
4584             =cut
4585              
4586             =pod
4587              
4588             =begin btest(delegate)
4589              
4590             my $o = __PACKAGE__->new();
4591             $test->ok($o, "Set up object");
4592             my $o2 = __PACKAGE__->new();
4593             $test->ok($o2, "Set up second object");
4594             $test->ok(! scalar __PACKAGE__->delegate($o), "Class cannot set delegate");
4595             $test->is(scalar $o->delegate($o2), $o2, "Object set delegate");
4596             $test->is(scalar $o->delegate(), $o2, "Object accessed delegate");
4597             $test->is(scalar $o->delegate(undef), undef, "Object deleted delegate");
4598              
4599             =end btest(delegate)
4600              
4601             =cut
4602              
4603             __PACKAGE__->add_attr('delegate');
4604              
4605             =pod
4606              
4607             =item types
4608              
4609             Defined in your conf file. Lists types used by the factory and pkg_for_type. See those methods for more info.
4610             Use a hashref in the conf file:
4611              
4612             types %= user=Basset::User
4613             types %= group=Basset::Group
4614             #etc
4615              
4616             That is, types should be an array of values that are = delimited. type=class.
4617              
4618             =cut
4619              
4620             =pod
4621              
4622             =begin btest(types)
4623              
4624             $test->ok(__PACKAGE__->types, "Got types out of the conf file");
4625             my $typesbkp = __PACKAGE__->types();
4626             my $newtypes = {%$typesbkp, 'testtype1' => '__PACKAGE__', 'testtype2' => 'boguspkg'};
4627             $test->ok($typesbkp, "Backed up the types");
4628             $test->is(__PACKAGE__->types($newtypes), $newtypes, "Set new types");
4629             $test->is(__PACKAGE__->pkg_for_type('testtype1'), '__PACKAGE__', "Got class for new type");
4630             $test->ok(! scalar __PACKAGE__->pkg_for_type('testtype2'), "Could not access invalid type");
4631             $test->is(__PACKAGE__->types($typesbkp), $typesbkp, "Re-set original types");
4632              
4633             =end btest(types)
4634              
4635             =cut
4636              
4637             #we're careful not to re-define this one, since it was probably already defined in Basset::Object::Conf, which is necessary due to circular
4638             #inheritance issues.
4639             __PACKAGE__->add_trickle_class_attr('types', {}) unless __PACKAGE__->can('types');
4640              
4641             #set up our defaults. Config file? Why bother.
4642             __PACKAGE__->types->{'logger'} ||= 'Basset::Logger';
4643             __PACKAGE__->types->{'notificationcenter'} ||= 'Basset::NotificationCenter';
4644             __PACKAGE__->types->{'conf'} ||= 'Basset::Object::Conf';
4645             __PACKAGE__->types->{'driver'} ||= 'Basset::DB';
4646             __PACKAGE__->types->{'table'} ||= 'Basset::DB::Table';
4647             __PACKAGE__->types->{'template'} ||= 'Basset::Template';
4648             __PACKAGE__->types->{'object'} ||= 'Basset::Object';
4649             __PACKAGE__->types->{'persistentobject'} ||= 'Basset::Object::Persistent';
4650             __PACKAGE__->types->{'machine'} ||= 'Basset::Machine';
4651             __PACKAGE__->types->{'state'} ||= 'Basset::Machine::State';
4652             __PACKAGE__->types->{'test'} ||= 'Basset::Test';
4653              
4654              
4655             =pod
4656              
4657             =item restrictions
4658              
4659             This stores the restrictions that B be added to this class, but not necessarily the
4660             ones that are in effect. Add new restrictions with the add_restriction method.
4661              
4662             =cut
4663              
4664             =pod
4665              
4666             =begin btest(restrictions)
4667              
4668             package Basset::Test::Testing::__PACKAGE__::restrictions::subclass1;
4669             our @ISA = qw(__PACKAGE__);
4670              
4671             package __PACKAGE__;
4672              
4673             $test->ok(Basset::Test::Testing::__PACKAGE__::restrictions::subclass1->isa('__PACKAGE__'), 'proper subclass');
4674             my $restrictions = {
4675             'foo' => [
4676             'a' => 'b'
4677             ]
4678             };
4679             $test->ok($restrictions, 'made restrictions');
4680             $test->is(Basset::Test::Testing::__PACKAGE__::restrictions::subclass1->restrictions($restrictions), $restrictions, 'added restrictions');
4681             $test->is(Basset::Test::Testing::__PACKAGE__::restrictions::subclass1->restrictions, $restrictions, 'accessed restrictions');
4682              
4683             =end btest(restrictions)
4684              
4685             =cut
4686              
4687             __PACKAGE__->add_trickle_class_attr('restrictions');
4688              
4689             =pod
4690              
4691             =begin btest(applied_restrictions)
4692              
4693             package Basset::Test::Testing::__PACKAGE__::applied_restrictions::Subclass;
4694             our @ISA = qw(__PACKAGE__);
4695              
4696             my %restrictions = (
4697             'specialerror' => [
4698             'error' => 'error3',
4699             'errcode' => 'errcode3'
4700             ],
4701             'invalidrestriction' => [
4702             'junkymethod' => 'otherjunkymethod'
4703             ]
4704             );
4705              
4706             __PACKAGE__->add_class_attr('e3');
4707             __PACKAGE__->add_class_attr('c3');
4708              
4709             $test->is(__PACKAGE__->e3(0), 0, "set e3 to 0");
4710             $test->is(__PACKAGE__->c3(0), 0, "set c3 to 0");
4711              
4712             sub error3 {
4713             my $self = shift;
4714             $self->e3($self->e3 + 1);
4715             return $self->SUPER::error(@_);
4716             }
4717              
4718             sub errcode3 {
4719             my $self = shift;
4720             $self->c3($self->c3 + 1);
4721             return $self->SUPER::errcode(@_);
4722             }
4723              
4724             $test->ok(scalar Basset::Test::Testing::__PACKAGE__::applied_restrictions::Subclass->add_restrictions(%restrictions), "Added restrictions to subclass");
4725              
4726             package __PACKAGE__;
4727              
4728             $test->ok(Basset::Test::Testing::__PACKAGE__::applied_restrictions::Subclass->isa('__PACKAGE__'), 'Proper subclass');
4729             my $subclass = Basset::Test::Testing::__PACKAGE__::applied_restrictions::Subclass->restrict('specialerror');
4730             $test->ok($subclass, "Restricted error");
4731             $test->ok(! scalar $subclass->add_restricted_method('invalidrestriction', 'junkymethod'), "Could not add invalid restriction");
4732             $test->ok($subclass->restricted, "Subclass is restricted");
4733              
4734             $test->ok($subclass->applied_restrictions, "Subclass has applied restrictions");
4735             my $restrictions = $subclass->applied_restrictions;
4736              
4737             $test->ok(ref $restrictions eq 'ARRAY', 'applied restrictions are an array');
4738             $test->is(scalar @$restrictions, 1, "Subclass has 1 restriction");
4739             $test->is($restrictions->[0], 'specialerror', 'Correct restriction in place');
4740              
4741             =end btest(applied_restrictions)
4742              
4743             =cut
4744              
4745             __PACKAGE__->add_trickle_class_attr('applied_restrictions', []);
4746              
4747             =pod
4748              
4749             =item restricted
4750              
4751             Boolean flag. returns 0 if the class is non-restricted, or 1 if it is restricted.
4752              
4753             =cut
4754              
4755             =pod
4756              
4757             =begin btest(restricted)
4758              
4759             package Basset::Test::Testing::__PACKAGE__::restricted::Subclass1;
4760             our @ISA = qw(__PACKAGE__);
4761              
4762             package __PACKAGE__;
4763              
4764             $test->ok(! __PACKAGE__->restricted, "__PACKAGE__ is not restricted");
4765             $test->ok(! Basset::Test::Testing::__PACKAGE__::restricted::Subclass1->restricted, "Subclass is not restricted");
4766             my $subclass = __PACKAGE__->inline_class;
4767             $test->ok($subclass, "Subclassed __PACKAGE__");
4768             my $subclass2 = Basset::Test::Testing::__PACKAGE__::restricted::Subclass1->inline_class();
4769             $test->ok($subclass2, "Restricted Basset::Test::Testing::__PACKAGE__::restricted::Subclass1");
4770             $test->ok($subclass->restricted, "Subclass is restricted");
4771             $test->ok($subclass2->restricted, "Subclass is restricted");
4772              
4773             =end btest(restricted)
4774              
4775             =cut
4776              
4777             __PACKAGE__->add_trickle_class_attr('restricted', 0);
4778              
4779             =pod
4780              
4781             =item exceptions
4782              
4783             boolean flag 1/0. Off by default. Some people, for some silly reason, like to use exceptions.
4784             Personally, I avoid them like the plague. Nonetheless, I'm an agreeable sort and wanted to provide
4785             the option. Standard procedure is to call a method or bubble up an error:
4786              
4787             sub method {
4788             my $self = shift;
4789              
4790             my $obj = shift;
4791              
4792             $obj->trysomething() or return $self->error($obj->errvals);
4793             }
4794              
4795             methods return undef, so if the return is undefined, you bubble it back up until something can
4796             handle it. With exceptions enabled, the error method (called somewhere inside $obj's trysomething
4797             method) would instead die with an error of the errorcode passed. Additionally, the error itself
4798             is set in the last_exception attribute. So you write your method call this way, if exceptions
4799             are enabled:
4800              
4801             sub method {
4802             my $self = shift;
4803             my $obj = shift;
4804              
4805             eval {
4806             $obj->trysomething();
4807             }
4808             if ($@ =~ /interesting error code/) {
4809             print "We died because of " . $obj->last_exception . "\n";
4810             } else {
4811             $obj->error($obj->errvals);#re-throw the exception
4812             }
4813             }
4814              
4815             Note that last_exception should be used to find out the error involved, not the ->error method. This
4816             is because you can't know which object actually threw the exception.
4817              
4818             =cut
4819              
4820             =pod
4821              
4822             =begin btest(exceptions)
4823              
4824             my $confClass = __PACKAGE__->pkg_for_type('conf');
4825             $test->ok($confClass, "Got conf");
4826              
4827             my $cfg = $confClass->conf;
4828             $test->ok($cfg, "Got configuration");
4829              
4830             my $exceptions = $cfg->{"Basset::Object"}->{'exceptions'};
4831              
4832             $test->is($cfg->{"Basset::Object"}->{'exceptions'} = 0, 0, "disables exceptions");
4833             $test->is($cfg->{"Basset::Object"}->{'exceptions'} = 0, 0, "enables exceptions");
4834             $test->is($cfg->{"Basset::Object"}->{'exceptions'} = $exceptions, $exceptions, "reset exceptions");
4835              
4836             =end btest(exceptions)
4837              
4838             =cut
4839              
4840             __PACKAGE__->add_default_class_attr('exceptions');
4841              
4842             =pod
4843              
4844             =item last_exception
4845              
4846             stores the message associated with the last exception
4847              
4848             =cut
4849              
4850             =pod
4851              
4852             =begin btest(last_exception)
4853              
4854             my $o = __PACKAGE__->new();
4855             $test->ok($o, "Got object");
4856              
4857             my $confClass = __PACKAGE__->pkg_for_type('conf');
4858             $test->ok($confClass, "Got conf");
4859              
4860             my $cfg = $confClass->conf;
4861             $test->ok($cfg, "Got configuration");
4862              
4863             $test->ok($cfg->{"Basset::Object"}->{'exceptions'} = 1, "enables exceptions");
4864              
4865             $test->ok(scalar __PACKAGE__->wipe_errors, "Wiped out errors");
4866             $test->ok(! __PACKAGE__->last_exception, "Last exception is empty");
4867             eval {
4868             __PACKAGE__->error('test exception', 'test code');
4869             };
4870             $test->like($@, "/test code/", "Thrown exception matches");
4871             $test->like(__PACKAGE__->last_exception, qr/test exception/, "Last exception matches");
4872             $test->like($o->last_exception, qr/test exception/, "Object last exception matches");
4873             $test->is($cfg->{"Basset::Object"}->{'exceptions'} = 0, 0,"disables exceptions");
4874              
4875             =end btest(last_exception)
4876              
4877             =cut
4878              
4879             __PACKAGE__->add_class_attr('last_exception');
4880              
4881             =pod
4882              
4883             =back
4884              
4885             =cut
4886              
4887             1;
4888             __END__