cleanup
[gitmo/Class-MOP.git] / lib / Class / MOP / Attribute.pm
CommitLineData
8b978dd5 1
2package Class::MOP::Attribute;
3
4use strict;
5use warnings;
6
2eb717d5 7use Carp 'confess';
9ec169fe 8use Scalar::Util 'blessed', 'reftype', 'weaken';
2eb717d5 9
bd4e03f9 10our $VERSION = '0.06';
8b978dd5 11
727919c5 12sub meta {
13 require Class::MOP::Class;
aa448b16 14 Class::MOP::Class->initialize(blessed($_[0]) || $_[0]);
727919c5 15}
2eb717d5 16
727919c5 17# NOTE: (meta-circularity)
18# This method will be replaces in the
19# boostrap section of Class::MOP, by
20# a new version which uses the
21# &Class::MOP::Class::construct_instance
22# method to build an attribute meta-object
23# which itself is described with attribute
24# meta-objects.
25# - Ain't meta-circularity grand? :)
8b978dd5 26sub new {
27 my $class = shift;
28 my $name = shift;
29 my %options = @_;
30
cbd9f942 31 (defined $name && $name)
8b978dd5 32 || confess "You must provide a name for the attribute";
5659d76e 33 $options{init_arg} = $name
34 if not exists $options{init_arg};
2eb717d5 35
8b978dd5 36 bless {
c50c603e 37 name => $name,
38 accessor => $options{accessor},
39 reader => $options{reader},
40 writer => $options{writer},
41 predicate => $options{predicate},
42 init_arg => $options{init_arg},
9ec169fe 43 default => $options{default},
44 # keep a weakened link to the
45 # class we are associated with
46 associated_class => undef,
8b978dd5 47 } => $class;
48}
49
7b31baf4 50# NOTE:
5659d76e 51# this is a primative (and kludgy) clone operation
52# for now, it will be repleace in the Class::MOP
53# bootstrap with a proper one, however we know
54# that this one will work fine for now.
55sub clone {
56 my $self = shift;
57 my %options = @_;
58 (blessed($self))
59 || confess "Can only clone an instance";
60 return bless { %{$self}, %options } => blessed($self);
61}
62
bd4e03f9 63sub initialize_instance_slot {
fed4cee7 64 my ($self, $class, $instance, $params) = @_;
291073fc 65 # OPTIMIZATION NOTE:
66 # We break the attribute encapsulation here
67 # in order to save a number of method calls
68 # to $self and speed things up a bit
69 my $init_arg = $self->{init_arg};
bd4e03f9 70 # try to fetch the init arg from the %params ...
71 my $val;
72 $val = $params->{$init_arg} if exists $params->{$init_arg};
73 # if nothing was in the %params, we can use the
74 # attribute's default value (if it has one)
291073fc 75 if (!defined $val && $self->{default}) {
76 $val = $self->default($instance);
77 }
78 $instance->{$self->{name}} = $val;
bd4e03f9 79}
80
5659d76e 81# NOTE:
7b31baf4 82# the next bunch of methods will get bootstrapped
83# away in the Class::MOP bootstrapping section
84
c50c603e 85sub name { $_[0]->{name} }
86
7b31baf4 87sub associated_class { $_[0]->{associated_class} }
88
727919c5 89sub has_accessor { defined($_[0]->{accessor}) ? 1 : 0 }
90sub has_reader { defined($_[0]->{reader}) ? 1 : 0 }
91sub has_writer { defined($_[0]->{writer}) ? 1 : 0 }
c50c603e 92sub has_predicate { defined($_[0]->{predicate}) ? 1 : 0 }
727919c5 93sub has_init_arg { defined($_[0]->{init_arg}) ? 1 : 0 }
94sub has_default { defined($_[0]->{default}) ? 1 : 0 }
c50c603e 95
96sub accessor { $_[0]->{accessor} }
97sub reader { $_[0]->{reader} }
98sub writer { $_[0]->{writer} }
99sub predicate { $_[0]->{predicate} }
100sub init_arg { $_[0]->{init_arg} }
101
7b31baf4 102# end bootstrapped away method section.
103# (all methods below here are kept intact)
104
c50c603e 105sub default {
106 my $self = shift;
107 if (reftype($self->{default}) && reftype($self->{default}) eq 'CODE') {
727919c5 108 # if the default is a CODE ref, then
109 # we pass in the instance and default
110 # can return a value based on that
111 # instance. Somewhat crude, but works.
c50c603e 112 return $self->{default}->(shift);
113 }
114 $self->{default};
115}
8b978dd5 116
9ec169fe 117# class association
727919c5 118
9ec169fe 119sub attach_to_class {
120 my ($self, $class) = @_;
121 (blessed($class) && $class->isa('Class::MOP::Class'))
122 || confess "You must pass a Class::MOP::Class instance (or a subclass)";
123 weaken($self->{associated_class} = $class);
124}
125
126sub detach_from_class {
127 my $self = shift;
128 $self->{associated_class} = undef;
129}
130
131## Method generation helpers
132
133sub generate_accessor_method {
134 my ($self, $attr_name) = @_;
013b1897 135 sub {
136 $_[0]->{$attr_name} = $_[1] if scalar(@_) == 2;
137 $_[0]->{$attr_name};
138 };
9ec169fe 139}
140
141sub generate_reader_method {
142 my ($self, $attr_name) = @_;
b9dfbf78 143 sub {
144 confess "Cannot assign a value to a read-only accessor" if @_ > 1;
145 $_[0]->{$attr_name};
146 };
9ec169fe 147}
148
149sub generate_writer_method {
150 my ($self, $attr_name) = @_;
013b1897 151 sub { $_[0]->{$attr_name} = $_[1] };
9ec169fe 152}
153
154sub generate_predicate_method {
155 my ($self, $attr_name) = @_;
013b1897 156 sub { defined $_[0]->{$attr_name} ? 1 : 0 };
9ec169fe 157}
158
159sub process_accessors {
160 my ($self, $type, $accessor) = @_;
013b1897 161 if (reftype($accessor)) {
162 (reftype($accessor) eq 'HASH')
163 || confess "bad accessor/reader/writer/predicate format, must be a HASH ref";
9ec169fe 164 my ($name, $method) = each %{$accessor};
a4258ffd 165 return ($name, Class::MOP::Attribute::Accessor->wrap($method));
2eb717d5 166 }
9ec169fe 167 else {
168 my $generator = $self->can('generate_' . $type . '_method');
169 ($generator)
170 || confess "There is no method generator for the type='$type'";
171 if (my $method = $self->$generator($self->name)) {
a4258ffd 172 return ($accessor => Class::MOP::Attribute::Accessor->wrap($method));
9ec169fe 173 }
343203ee 174 confess "Could not create the '$type' method for " . $self->name . " because : $@";
9ec169fe 175 }
176}
177
178sub install_accessors {
179 my $self = shift;
180 my $class = $self->associated_class;
c50c603e 181
9ec169fe 182 $class->add_method(
183 $self->process_accessors('accessor' => $self->accessor())
184 ) if $self->has_accessor();
185
186 $class->add_method(
187 $self->process_accessors('reader' => $self->reader())
188 ) if $self->has_reader();
189
190 $class->add_method(
191 $self->process_accessors('writer' => $self->writer())
192 ) if $self->has_writer();
193
194 $class->add_method(
195 $self->process_accessors('predicate' => $self->predicate())
196 ) if $self->has_predicate();
197 return;
2eb717d5 198}
199
b51af7f9 200{
201 my $_remove_accessor = sub {
202 my ($accessor, $class) = @_;
c50c603e 203 if (reftype($accessor) && reftype($accessor) eq 'HASH') {
204 ($accessor) = keys %{$accessor};
205 }
b51af7f9 206 my $method = $class->get_method($accessor);
207 $class->remove_method($accessor)
2eb717d5 208 if (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'));
b51af7f9 209 };
c50c603e 210
b51af7f9 211 sub remove_accessors {
9ec169fe 212 my $self = shift;
213 $_remove_accessor->($self->accessor(), $self->associated_class()) if $self->has_accessor();
214 $_remove_accessor->($self->reader(), $self->associated_class()) if $self->has_reader();
215 $_remove_accessor->($self->writer(), $self->associated_class()) if $self->has_writer();
216 $_remove_accessor->($self->predicate(), $self->associated_class()) if $self->has_predicate();
b51af7f9 217 return;
218 }
219
8b978dd5 220}
221
2eb717d5 222package Class::MOP::Attribute::Accessor;
223
224use strict;
225use warnings;
226
727919c5 227use Class::MOP::Method;
228
2eb717d5 229our $VERSION = '0.01';
230
231our @ISA = ('Class::MOP::Method');
232
8b978dd5 2331;
234
235__END__
236
237=pod
238
239=head1 NAME
240
241Class::MOP::Attribute - Attribute Meta Object
242
243=head1 SYNOPSIS
244
245 Class::MOP::Attribute->new('$foo' => (
fe122940 246 accessor => 'foo', # dual purpose get/set accessor
247 predicate => 'has_foo' # predicate check for defined-ness
248 init_arg => '-foo', # class->new will look for a -foo key
249 default => 'BAR IS BAZ!' # if no -foo key is provided, use this
8b978dd5 250 ));
251
252 Class::MOP::Attribute->new('$.bar' => (
fe122940 253 reader => 'bar', # getter
254 writer => 'set_bar', # setter
255 predicate => 'has_bar' # predicate check for defined-ness
256 init_arg => ':bar', # class->new will look for a :bar key
8b978dd5 257 # no default value means it is undef
258 ));
259
260=head1 DESCRIPTION
261
fe122940 262The Attribute Protocol is almost entirely an invention of this module,
263and is completely optional to this MOP. This is because Perl 5 does not
264have consistent notion of what is an attribute of a class. There are
265so many ways in which this is done, and very few (if any) are
266easily discoverable by this module.
552e3d24 267
268So, all that said, this module attempts to inject some order into this
fe122940 269chaos, by introducing a consistent API which can be used to create
270object attributes.
552e3d24 271
272=head1 METHODS
273
274=head2 Creation
275
276=over 4
277
fe122940 278=item B<new ($name, ?%options)>
279
280An attribute must (at the very least), have a C<$name>. All other
a2e85e6c 281C<%options> are contained added as key-value pairs. Acceptable keys
fe122940 282are as follows:
283
284=over 4
285
286=item I<init_arg>
287
288This should be a string value representing the expected key in
289an initialization hash. For instance, if we have an I<init_arg>
290value of C<-foo>, then the following code will Just Work.
291
292 MyClass->meta->construct_instance(-foo => "Hello There");
293
7b31baf4 294In an init_arg is not assigned, it will automatically use the
295value of C<$name>.
296
fe122940 297=item I<default>
298
299The value of this key is the default value which
300C<Class::MOP::Class::construct_instance> will initialize the
301attribute to.
302
303B<NOTE:>
304If the value is a simple scalar (string or number), then it can
305be just passed as is. However, if you wish to initialize it with
306a HASH or ARRAY ref, then you need to wrap that inside a CODE
307reference, like so:
308
309 Class::MOP::Attribute->new('@foo' => (
310 default => sub { [] },
311 ));
312
313 # or ...
314
315 Class::MOP::Attribute->new('%foo' => (
316 default => sub { {} },
317 ));
318
319If you wish to initialize an attribute with a CODE reference
320itself, then you need to wrap that in a subroutine as well, like
321so:
322
323 Class::MOP::Attribute->new('&foo' => (
324 default => sub { sub { print "Hello World" } },
325 ));
326
327And lastly, if the value of your attribute is dependent upon
328some other aspect of the instance structure, then you can take
329advantage of the fact that when the I<default> value is a CODE
330reference, it is passed the raw (unblessed) instance structure
331as it's only argument. So you can do things like this:
332
333 Class::MOP::Attribute->new('$object_identity' => (
334 default => sub { Scalar::Util::refaddr($_[0]) },
335 ));
336
337This last feature is fairly limited as there is no gurantee of
338the order of attribute initializations, so you cannot perform
339any kind of dependent initializations. However, if this is
340something you need, you could subclass B<Class::MOP::Class> and
341this class to acheive it. However, this is currently left as
342an exercise to the reader :).
343
344=back
345
a2e85e6c 346The I<accessor>, I<reader>, I<writer> and I<predicate> keys can
fe122940 347contain either; the name of the method and an appropriate default
348one will be generated for you, B<or> a HASH ref containing exactly one
349key (which will be used as the name of the method) and one value,
350which should contain a CODE reference which will be installed as
351the method itself.
59e7697f 352
353=over 4
354
355=item I<accessor>
356
fe122940 357The I<accessor> is a standard perl-style read/write accessor. It will
358return the value of the attribute, and if a value is passed as an argument,
359it will assign that value to the attribute.
360
361B<NOTE:>
362This method will properly handle the following code, by assigning an
363C<undef> value to the attribute.
364
365 $object->set_something(undef);
366
59e7697f 367=item I<reader>
368
fe122940 369This is a basic read-only accessor, it will just return the value of
370the attribute.
371
59e7697f 372=item I<writer>
373
fe122940 374This is a basic write accessor, it accepts a single argument, and
375assigns that value to the attribute. This method does not intentially
376return a value, however perl will return the result of the last
377expression in the subroutine, which returns in this returning the
378same value that it was passed.
59e7697f 379
fe122940 380B<NOTE:>
381This method will properly handle the following code, by assigning an
382C<undef> value to the attribute.
59e7697f 383
fe122940 384 $object->set_something();
385
386=item I<predicate>
387
388This is a basic test to see if the value of the attribute is not
389C<undef>. It will return true (C<1>) if the attribute's value is
390defined, and false (C<0>) otherwise.
59e7697f 391
392=back
552e3d24 393
bd4e03f9 394=item B<clone (%options)>
395
396=item B<initialize_instance_slot ($instance, $params)>
397
552e3d24 398=back
399
400=head2 Informational
401
fe122940 402These are all basic read-only value accessors for the values
403passed into C<new>. I think they are pretty much self-explanitory.
404
552e3d24 405=over 4
406
407=item B<name>
408
409=item B<accessor>
410
411=item B<reader>
412
413=item B<writer>
414
c50c603e 415=item B<predicate>
416
552e3d24 417=item B<init_arg>
418
fe122940 419=item B<default (?$instance)>
420
421As noted in the documentation for C<new> above, if the I<default>
422value is a CODE reference, this accessor will pass a single additional
423argument C<$instance> into it and return the value.
552e3d24 424
425=back
426
427=head2 Informational predicates
428
a2e85e6c 429These are all basic predicate methods for the values passed into C<new>.
fe122940 430
552e3d24 431=over 4
432
433=item B<has_accessor>
434
552e3d24 435=item B<has_reader>
436
552e3d24 437=item B<has_writer>
438
c50c603e 439=item B<has_predicate>
440
552e3d24 441=item B<has_init_arg>
442
552e3d24 443=item B<has_default>
444
552e3d24 445=back
446
9ec169fe 447=head2 Class association
448
449=over 4
450
451=item B<associated_class>
452
453=item B<attach_to_class ($class)>
454
455=item B<detach_from_class>
456
457=back
458
552e3d24 459=head2 Attribute Accessor generation
460
461=over 4
462
9ec169fe 463=item B<install_accessors>
2eb717d5 464
465This allows the attribute to generate and install code for it's own
a2e85e6c 466I<accessor/reader/writer/predicate> methods. This is called by
fe122940 467C<Class::MOP::Class::add_attribute>.
2eb717d5 468
9ec169fe 469This method will call C<process_accessors> for each of the possible
470method types (accessor, reader, writer & predicate).
471
472=item B<process_accessors ($type, $value)>
473
474This takes a C<$type> (accessor, reader, writer or predicate), and
475a C<$value> (the value passed into the constructor for each of the
476different types). It will then either generate the method itself
477(using the C<generate_*_method> methods listed below) or it will
478use the custom method passed through the constructor.
479
480=over 4
481
482=item B<generate_accessor_method ($attr_name)>
483
484=item B<generate_predicate_method ($attr_name)>
485
486=item B<generate_reader_method ($attr_name)>
487
488=item B<generate_writer_method ($attr_name)>
489
490=back
491
492=item B<remove_accessors>
2eb717d5 493
494This allows the attribute to remove the method for it's own
a2e85e6c 495I<accessor/reader/writer/predicate>. This is called by
fe122940 496C<Class::MOP::Class::remove_attribute>.
2eb717d5 497
498=back
499
500=head2 Introspection
501
502=over 4
552e3d24 503
2eb717d5 504=item B<meta>
552e3d24 505
fe122940 506This will return a B<Class::MOP::Class> instance which is related
507to this class.
508
509It should also be noted that B<Class::MOP> will actually bootstrap
510this module by installing a number of attribute meta-objects into
511it's metaclass. This will allow this class to reap all the benifits
512of the MOP when subclassing it.
513
552e3d24 514=back
515
8b978dd5 516=head1 AUTHOR
517
a2e85e6c 518Stevan Little E<lt>stevan@iinteractive.comE<gt>
8b978dd5 519
520=head1 COPYRIGHT AND LICENSE
521
522Copyright 2006 by Infinity Interactive, Inc.
523
524L<http://www.iinteractive.com>
525
526This library is free software; you can redistribute it and/or modify
527it under the same terms as Perl itself.
528
9ec169fe 529=cut