closed
[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
2bab2be6 10our $VERSION = '0.08';
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 {
f892c0f0 64 my ($self, $meta_instance, $instance, $params) = @_;
291073fc 65 my $init_arg = $self->{init_arg};
bd4e03f9 66 # try to fetch the init arg from the %params ...
67 my $val;
68 $val = $params->{$init_arg} if exists $params->{$init_arg};
69 # if nothing was in the %params, we can use the
70 # attribute's default value (if it has one)
bb8dacfa 71 if (!defined $val && defined $self->{default}) {
2d711cc8 72 $val = $self->default($instance);
73 }
43715282 74 $meta_instance->set_slot_value($instance, $self->name, $val);
bd4e03f9 75}
76
5659d76e 77# NOTE:
7b31baf4 78# the next bunch of methods will get bootstrapped
79# away in the Class::MOP bootstrapping section
80
c50c603e 81sub name { $_[0]->{name} }
82
7b31baf4 83sub associated_class { $_[0]->{associated_class} }
84
727919c5 85sub has_accessor { defined($_[0]->{accessor}) ? 1 : 0 }
86sub has_reader { defined($_[0]->{reader}) ? 1 : 0 }
87sub has_writer { defined($_[0]->{writer}) ? 1 : 0 }
c50c603e 88sub has_predicate { defined($_[0]->{predicate}) ? 1 : 0 }
727919c5 89sub has_init_arg { defined($_[0]->{init_arg}) ? 1 : 0 }
90sub has_default { defined($_[0]->{default}) ? 1 : 0 }
c50c603e 91
92sub accessor { $_[0]->{accessor} }
93sub reader { $_[0]->{reader} }
94sub writer { $_[0]->{writer} }
95sub predicate { $_[0]->{predicate} }
96sub init_arg { $_[0]->{init_arg} }
97
7b31baf4 98# end bootstrapped away method section.
99# (all methods below here are kept intact)
100
c0cbf4d9 101sub is_default_a_coderef {
102 (reftype($_[0]->{default}) && reftype($_[0]->{default}) eq 'CODE')
103}
104
c50c603e 105sub default {
c0cbf4d9 106 my ($self, $instance) = @_;
107 if ($instance && $self->is_default_a_coderef) {
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.
c0cbf4d9 112 return $self->{default}->($instance);
c50c603e 113 }
114 $self->{default};
115}
8b978dd5 116
c57c8b10 117# slots
118
119sub slots { (shift)->name }
120
9ec169fe 121# class association
727919c5 122
9ec169fe 123sub attach_to_class {
124 my ($self, $class) = @_;
125 (blessed($class) && $class->isa('Class::MOP::Class'))
126 || confess "You must pass a Class::MOP::Class instance (or a subclass)";
127 weaken($self->{associated_class} = $class);
128}
129
130sub detach_from_class {
131 my $self = shift;
132 $self->{associated_class} = undef;
133}
134
135## Method generation helpers
136
137sub generate_accessor_method {
43715282 138 my $self = shift;
b880e0de 139 my $attr_name = $self->name;
43715282 140 return sub {
141 my $meta_instance = Class::MOP::Class->initialize(Scalar::Util::blessed($_[0]))->get_meta_instance;
142 $meta_instance->set_slot_value($_[0], $attr_name, $_[1]) if scalar(@_) == 2;
143 $meta_instance->get_slot_value($_[0], $attr_name);
144 };
9ec169fe 145}
146
c0cbf4d9 147sub generate_accessor_method_inline {
148 my $self = shift;
149 my $attr_name = $self->name;
150 my $meta_instance = $self->associated_class->instance_metaclass;
151
152 my $code = eval 'sub {'
4d47b77f 153 . $meta_instance->inline_set_slot_value('$_[0]', "'$attr_name'", '$_[1]') . ' if scalar(@_) == 2; '
154 . $meta_instance->inline_get_slot_value('$_[0]', "'$attr_name'")
c0cbf4d9 155 . '}';
156 confess "Could not generate inline accessor because : $@" if $@;
157
158 return $code;
159}
160
9ec169fe 161sub generate_reader_method {
08388f17 162 my $self = shift;
b880e0de 163 my $attr_name = $self->name;
43715282 164 return sub {
165 confess "Cannot assign a value to a read-only accessor" if @_ > 1;
166 Class::MOP::Class->initialize(Scalar::Util::blessed($_[0]))
167 ->get_meta_instance
168 ->get_slot_value($_[0], $attr_name);
169 };
9ec169fe 170}
171
c0cbf4d9 172sub generate_reader_method_inline {
173 my $self = shift;
174 my $attr_name = $self->name;
175 my $meta_instance = $self->associated_class->instance_metaclass;
176
177 my $code = eval 'sub {'
178 . 'confess "Cannot assign a value to a read-only accessor" if @_ > 1;'
4d47b77f 179 . $meta_instance->inline_get_slot_value('$_[0]', "'$attr_name'")
c0cbf4d9 180 . '}';
181 confess "Could not generate inline accessor because : $@" if $@;
182
183 return $code;
184}
185
9ec169fe 186sub generate_writer_method {
08388f17 187 my $self = shift;
b880e0de 188 my $attr_name = $self->name;
43715282 189 return sub {
190 Class::MOP::Class->initialize(Scalar::Util::blessed($_[0]))
191 ->get_meta_instance
192 ->set_slot_value($_[0], $attr_name, $_[1]);
193 };
9ec169fe 194}
195
c0cbf4d9 196sub generate_writer_method_inline {
197 my $self = shift;
198 my $attr_name = $self->name;
199 my $meta_instance = $self->associated_class->instance_metaclass;
200
201 my $code = eval 'sub {'
4d47b77f 202 . $meta_instance->inline_set_slot_value('$_[0]', "'$attr_name'", '$_[1]')
c0cbf4d9 203 . '}';
204 confess "Could not generate inline accessor because : $@" if $@;
205
206 return $code;
207}
208
9ec169fe 209sub generate_predicate_method {
08388f17 210 my $self = shift;
b880e0de 211 my $attr_name = $self->name;
43715282 212 return sub {
213 defined Class::MOP::Class->initialize(Scalar::Util::blessed($_[0]))
214 ->get_meta_instance
215 ->get_slot_value($_[0], $attr_name) ? 1 : 0;
216 };
9ec169fe 217}
218
c0cbf4d9 219sub generate_predicate_method_inline {
220 my $self = shift;
221 my $attr_name = $self->name;
222 my $meta_instance = $self->associated_class->instance_metaclass;
223
224 my $code = eval 'sub {'
4d47b77f 225 . 'defined ' . $meta_instance->inline_get_slot_value('$_[0]', "'$attr_name'") . ' ? 1 : 0'
c0cbf4d9 226 . '}';
227 confess "Could not generate inline accessor because : $@" if $@;
228
229 return $code;
230}
231
9ec169fe 232sub process_accessors {
c0cbf4d9 233 my ($self, $type, $accessor, $generate_as_inline_methods) = @_;
013b1897 234 if (reftype($accessor)) {
235 (reftype($accessor) eq 'HASH')
236 || confess "bad accessor/reader/writer/predicate format, must be a HASH ref";
4d47b77f 237 my ($name, $method) = %{$accessor};
a4258ffd 238 return ($name, Class::MOP::Attribute::Accessor->wrap($method));
2eb717d5 239 }
9ec169fe 240 else {
c0cbf4d9 241 my $inline_me = ($generate_as_inline_methods && $self->associated_class->instance_metaclass->is_inlinable);
242 my $generator = $self->can('generate_' . $type . '_method' . ($inline_me ? '_inline' : ''));
9ec169fe 243 ($generator)
244 || confess "There is no method generator for the type='$type'";
245 if (my $method = $self->$generator($self->name)) {
a4258ffd 246 return ($accessor => Class::MOP::Attribute::Accessor->wrap($method));
9ec169fe 247 }
343203ee 248 confess "Could not create the '$type' method for " . $self->name . " because : $@";
9ec169fe 249 }
250}
251
252sub install_accessors {
c0cbf4d9 253 my $self = shift;
254 my $inline = shift;
255 my $class = $self->associated_class;
c50c603e 256
9ec169fe 257 $class->add_method(
c0cbf4d9 258 $self->process_accessors('accessor' => $self->accessor(), $inline)
9ec169fe 259 ) if $self->has_accessor();
260
261 $class->add_method(
c0cbf4d9 262 $self->process_accessors('reader' => $self->reader(), $inline)
9ec169fe 263 ) if $self->has_reader();
264
265 $class->add_method(
c0cbf4d9 266 $self->process_accessors('writer' => $self->writer(), $inline)
9ec169fe 267 ) if $self->has_writer();
268
269 $class->add_method(
c0cbf4d9 270 $self->process_accessors('predicate' => $self->predicate(), $inline)
9ec169fe 271 ) if $self->has_predicate();
c0cbf4d9 272
9ec169fe 273 return;
2eb717d5 274}
275
b51af7f9 276{
277 my $_remove_accessor = sub {
278 my ($accessor, $class) = @_;
c50c603e 279 if (reftype($accessor) && reftype($accessor) eq 'HASH') {
280 ($accessor) = keys %{$accessor};
281 }
b51af7f9 282 my $method = $class->get_method($accessor);
283 $class->remove_method($accessor)
2eb717d5 284 if (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'));
b51af7f9 285 };
c50c603e 286
b51af7f9 287 sub remove_accessors {
9ec169fe 288 my $self = shift;
289 $_remove_accessor->($self->accessor(), $self->associated_class()) if $self->has_accessor();
290 $_remove_accessor->($self->reader(), $self->associated_class()) if $self->has_reader();
291 $_remove_accessor->($self->writer(), $self->associated_class()) if $self->has_writer();
292 $_remove_accessor->($self->predicate(), $self->associated_class()) if $self->has_predicate();
b51af7f9 293 return;
294 }
295
8b978dd5 296}
297
2eb717d5 298package Class::MOP::Attribute::Accessor;
299
300use strict;
301use warnings;
302
727919c5 303use Class::MOP::Method;
304
2eb717d5 305our $VERSION = '0.01';
306
307our @ISA = ('Class::MOP::Method');
308
8b978dd5 3091;
310
311__END__
312
313=pod
314
315=head1 NAME
316
317Class::MOP::Attribute - Attribute Meta Object
318
319=head1 SYNOPSIS
320
321 Class::MOP::Attribute->new('$foo' => (
fe122940 322 accessor => 'foo', # dual purpose get/set accessor
323 predicate => 'has_foo' # predicate check for defined-ness
324 init_arg => '-foo', # class->new will look for a -foo key
325 default => 'BAR IS BAZ!' # if no -foo key is provided, use this
8b978dd5 326 ));
327
328 Class::MOP::Attribute->new('$.bar' => (
fe122940 329 reader => 'bar', # getter
330 writer => 'set_bar', # setter
331 predicate => 'has_bar' # predicate check for defined-ness
332 init_arg => ':bar', # class->new will look for a :bar key
8b978dd5 333 # no default value means it is undef
334 ));
335
336=head1 DESCRIPTION
337
fe122940 338The Attribute Protocol is almost entirely an invention of this module,
339and is completely optional to this MOP. This is because Perl 5 does not
340have consistent notion of what is an attribute of a class. There are
341so many ways in which this is done, and very few (if any) are
342easily discoverable by this module.
552e3d24 343
344So, all that said, this module attempts to inject some order into this
fe122940 345chaos, by introducing a consistent API which can be used to create
346object attributes.
552e3d24 347
348=head1 METHODS
349
350=head2 Creation
351
352=over 4
353
fe122940 354=item B<new ($name, ?%options)>
355
356An attribute must (at the very least), have a C<$name>. All other
a2e85e6c 357C<%options> are contained added as key-value pairs. Acceptable keys
fe122940 358are as follows:
359
360=over 4
361
362=item I<init_arg>
363
364This should be a string value representing the expected key in
365an initialization hash. For instance, if we have an I<init_arg>
366value of C<-foo>, then the following code will Just Work.
367
368 MyClass->meta->construct_instance(-foo => "Hello There");
369
7b31baf4 370In an init_arg is not assigned, it will automatically use the
371value of C<$name>.
372
fe122940 373=item I<default>
374
375The value of this key is the default value which
376C<Class::MOP::Class::construct_instance> will initialize the
377attribute to.
378
379B<NOTE:>
380If the value is a simple scalar (string or number), then it can
381be just passed as is. However, if you wish to initialize it with
382a HASH or ARRAY ref, then you need to wrap that inside a CODE
383reference, like so:
384
385 Class::MOP::Attribute->new('@foo' => (
386 default => sub { [] },
387 ));
388
389 # or ...
390
391 Class::MOP::Attribute->new('%foo' => (
392 default => sub { {} },
393 ));
394
395If you wish to initialize an attribute with a CODE reference
396itself, then you need to wrap that in a subroutine as well, like
397so:
398
399 Class::MOP::Attribute->new('&foo' => (
400 default => sub { sub { print "Hello World" } },
401 ));
402
403And lastly, if the value of your attribute is dependent upon
404some other aspect of the instance structure, then you can take
405advantage of the fact that when the I<default> value is a CODE
406reference, it is passed the raw (unblessed) instance structure
407as it's only argument. So you can do things like this:
408
409 Class::MOP::Attribute->new('$object_identity' => (
410 default => sub { Scalar::Util::refaddr($_[0]) },
411 ));
412
413This last feature is fairly limited as there is no gurantee of
414the order of attribute initializations, so you cannot perform
415any kind of dependent initializations. However, if this is
416something you need, you could subclass B<Class::MOP::Class> and
417this class to acheive it. However, this is currently left as
418an exercise to the reader :).
419
420=back
421
a2e85e6c 422The I<accessor>, I<reader>, I<writer> and I<predicate> keys can
fe122940 423contain either; the name of the method and an appropriate default
424one will be generated for you, B<or> a HASH ref containing exactly one
425key (which will be used as the name of the method) and one value,
426which should contain a CODE reference which will be installed as
427the method itself.
59e7697f 428
429=over 4
430
431=item I<accessor>
432
fe122940 433The I<accessor> is a standard perl-style read/write accessor. It will
434return the value of the attribute, and if a value is passed as an argument,
435it will assign that value to the attribute.
436
437B<NOTE:>
438This method will properly handle the following code, by assigning an
439C<undef> value to the attribute.
440
441 $object->set_something(undef);
442
59e7697f 443=item I<reader>
444
fe122940 445This is a basic read-only accessor, it will just return the value of
446the attribute.
447
59e7697f 448=item I<writer>
449
fe122940 450This is a basic write accessor, it accepts a single argument, and
451assigns that value to the attribute. This method does not intentially
452return a value, however perl will return the result of the last
453expression in the subroutine, which returns in this returning the
454same value that it was passed.
59e7697f 455
fe122940 456B<NOTE:>
457This method will properly handle the following code, by assigning an
458C<undef> value to the attribute.
59e7697f 459
fe122940 460 $object->set_something();
461
462=item I<predicate>
463
464This is a basic test to see if the value of the attribute is not
465C<undef>. It will return true (C<1>) if the attribute's value is
466defined, and false (C<0>) otherwise.
59e7697f 467
468=back
552e3d24 469
bd4e03f9 470=item B<clone (%options)>
471
472=item B<initialize_instance_slot ($instance, $params)>
473
552e3d24 474=back
475
476=head2 Informational
477
fe122940 478These are all basic read-only value accessors for the values
479passed into C<new>. I think they are pretty much self-explanitory.
480
552e3d24 481=over 4
482
483=item B<name>
484
485=item B<accessor>
486
487=item B<reader>
488
489=item B<writer>
490
c50c603e 491=item B<predicate>
492
552e3d24 493=item B<init_arg>
494
495af518 495=item B<is_default_a_coderef>
496
fe122940 497=item B<default (?$instance)>
498
499As noted in the documentation for C<new> above, if the I<default>
500value is a CODE reference, this accessor will pass a single additional
501argument C<$instance> into it and return the value.
552e3d24 502
c57c8b10 503=item B<slots>
504
505Returns a list of slots required by the attribute. This is usually
506just one, which is the name of the attribute.
507
552e3d24 508=back
509
510=head2 Informational predicates
511
a2e85e6c 512These are all basic predicate methods for the values passed into C<new>.
fe122940 513
552e3d24 514=over 4
515
516=item B<has_accessor>
517
552e3d24 518=item B<has_reader>
519
552e3d24 520=item B<has_writer>
521
c50c603e 522=item B<has_predicate>
523
552e3d24 524=item B<has_init_arg>
525
552e3d24 526=item B<has_default>
527
552e3d24 528=back
529
9ec169fe 530=head2 Class association
531
532=over 4
533
534=item B<associated_class>
535
536=item B<attach_to_class ($class)>
537
538=item B<detach_from_class>
539
2d711cc8 540=item B<slot_name>
541
542=item B<allocate_slots>
543
544=item B<deallocate_slots>
545
9ec169fe 546=back
547
552e3d24 548=head2 Attribute Accessor generation
549
550=over 4
551
9ec169fe 552=item B<install_accessors>
2eb717d5 553
554This allows the attribute to generate and install code for it's own
a2e85e6c 555I<accessor/reader/writer/predicate> methods. This is called by
fe122940 556C<Class::MOP::Class::add_attribute>.
2eb717d5 557
9ec169fe 558This method will call C<process_accessors> for each of the possible
559method types (accessor, reader, writer & predicate).
560
561=item B<process_accessors ($type, $value)>
562
563This takes a C<$type> (accessor, reader, writer or predicate), and
564a C<$value> (the value passed into the constructor for each of the
565different types). It will then either generate the method itself
566(using the C<generate_*_method> methods listed below) or it will
567use the custom method passed through the constructor.
568
569=over 4
570
08388f17 571=item B<generate_accessor_method>
9ec169fe 572
08388f17 573=item B<generate_predicate_method>
9ec169fe 574
08388f17 575=item B<generate_reader_method>
9ec169fe 576
08388f17 577=item B<generate_writer_method>
9ec169fe 578
579=back
580
495af518 581=over 4
582
583=item B<generate_accessor_method_inline>
584
585=item B<generate_predicate_method_inline>
586
587=item B<generate_reader_method_inline>
588
589=item B<generate_writer_method_inline>
590
591=back
592
9ec169fe 593=item B<remove_accessors>
2eb717d5 594
595This allows the attribute to remove the method for it's own
a2e85e6c 596I<accessor/reader/writer/predicate>. This is called by
fe122940 597C<Class::MOP::Class::remove_attribute>.
2eb717d5 598
599=back
600
601=head2 Introspection
602
603=over 4
552e3d24 604
2eb717d5 605=item B<meta>
552e3d24 606
fe122940 607This will return a B<Class::MOP::Class> instance which is related
608to this class.
609
610It should also be noted that B<Class::MOP> will actually bootstrap
611this module by installing a number of attribute meta-objects into
612it's metaclass. This will allow this class to reap all the benifits
613of the MOP when subclassing it.
614
552e3d24 615=back
616
8b978dd5 617=head1 AUTHOR
618
a2e85e6c 619Stevan Little E<lt>stevan@iinteractive.comE<gt>
8b978dd5 620
621=head1 COPYRIGHT AND LICENSE
622
623Copyright 2006 by Infinity Interactive, Inc.
624
625L<http://www.iinteractive.com>
626
627This library is free software; you can redistribute it and/or modify
628it under the same terms as Perl itself.
629
9ec169fe 630=cut