Class::MOP - lots of knot tying, this should make subclassing more reliable and strai...
[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';
c50c603e 8use Scalar::Util 'blessed', 'reftype';
2eb717d5 9
8b978dd5 10our $VERSION = '0.01';
11
727919c5 12sub meta {
13 require Class::MOP::Class;
14 Class::MOP::Class->initialize($_[0])
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";
2eb717d5 33 (!exists $options{reader} && !exists $options{writer})
34 || confess "You cannot declare an accessor and reader and/or writer functions"
35 if exists $options{accessor};
36
8b978dd5 37 bless {
c50c603e 38 name => $name,
39 accessor => $options{accessor},
40 reader => $options{reader},
41 writer => $options{writer},
42 predicate => $options{predicate},
43 init_arg => $options{init_arg},
44 default => $options{default}
8b978dd5 45 } => $class;
46}
47
c50c603e 48sub name { $_[0]->{name} }
49
727919c5 50sub has_accessor { defined($_[0]->{accessor}) ? 1 : 0 }
51sub has_reader { defined($_[0]->{reader}) ? 1 : 0 }
52sub has_writer { defined($_[0]->{writer}) ? 1 : 0 }
c50c603e 53sub has_predicate { defined($_[0]->{predicate}) ? 1 : 0 }
727919c5 54sub has_init_arg { defined($_[0]->{init_arg}) ? 1 : 0 }
55sub has_default { defined($_[0]->{default}) ? 1 : 0 }
c50c603e 56
57sub accessor { $_[0]->{accessor} }
58sub reader { $_[0]->{reader} }
59sub writer { $_[0]->{writer} }
60sub predicate { $_[0]->{predicate} }
61sub init_arg { $_[0]->{init_arg} }
62
63sub default {
64 my $self = shift;
65 if (reftype($self->{default}) && reftype($self->{default}) eq 'CODE') {
727919c5 66 # if the default is a CODE ref, then
67 # we pass in the instance and default
68 # can return a value based on that
69 # instance. Somewhat crude, but works.
c50c603e 70 return $self->{default}->(shift);
71 }
72 $self->{default};
73}
8b978dd5 74
727919c5 75{
76 # this is just a utility routine to
77 # handle the details of accessors
78 my $_inspect_accessor = sub {
79 my ($attr_name, $type, $accessor) = @_;
80
81 my %ACCESSOR_TEMPLATES = (
82 'accessor' => sub {
83 $_[0]->{$attr_name} = $_[1] if scalar(@_) == 2;
84 $_[0]->{$attr_name};
85 },
86 'reader' => sub {
87 $_[0]->{$attr_name};
88 },
89 'writer' => sub {
90 $_[0]->{$attr_name} = $_[1];
91 return;
92 },
93 'predicate' => sub {
94 return defined $_[0]->{$attr_name} ? 1 : 0;
95 }
96 );
97
c50c603e 98 if (reftype($accessor) && reftype($accessor) eq 'HASH') {
99 my ($name, $method) = each %{$accessor};
727919c5 100 return ($name, Class::MOP::Attribute::Accessor->wrap($method));
c50c603e 101 }
102 else {
727919c5 103 return ($accessor => Class::MOP::Attribute::Accessor->wrap($ACCESSOR_TEMPLATES{$type}));
104 }
105 };
106
107 sub install_accessors {
108 my ($self, $class) = @_;
109 (blessed($class) && $class->isa('Class::MOP::Class'))
110 || confess "You must pass a Class::MOP::Class instance (or a subclass)";
111
112 $class->add_method(
113 $_inspect_accessor->($self->name, 'accessor' => $self->accessor())
114 ) if $self->has_accessor();
115
116 $class->add_method(
117 $_inspect_accessor->($self->name, 'reader' => $self->reader())
118 ) if $self->has_reader();
119
120 $class->add_method(
121 $_inspect_accessor->($self->name, 'writer' => $self->writer())
122 ) if $self->has_writer();
123
124 $class->add_method(
125 $_inspect_accessor->($self->name, 'predicate' => $self->predicate())
126 ) if $self->has_predicate();
2eb717d5 127 }
c50c603e 128
2eb717d5 129}
130
131sub remove_accessors {
132 my ($self, $class) = @_;
133 (blessed($class) && $class->isa('Class::MOP::Class'))
134 || confess "You must pass a Class::MOP::Class instance (or a subclass)";
135
136 if ($self->has_accessor()) {
c50c603e 137 my $accessor = $self->accessor();
138 if (reftype($accessor) && reftype($accessor) eq 'HASH') {
139 ($accessor) = keys %{$accessor};
140 }
141 my $method = $class->get_method($accessor);
142 $class->remove_method($accessor)
2eb717d5 143 if (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'));
144 }
145 else {
146 if ($self->has_reader()) {
c50c603e 147 my $reader = $self->reader();
148 if (reftype($reader) && reftype($reader) eq 'HASH') {
149 ($reader) = keys %{$reader};
150 }
151 my $method = $class->get_method($reader);
152 $class->remove_method($reader)
2eb717d5 153 if (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'));
154 }
155 if ($self->has_writer()) {
c50c603e 156 my $writer = $self->writer();
157 if (reftype($writer) && reftype($writer) eq 'HASH') {
158 ($writer) = keys %{$writer};
159 }
160 my $method = $class->get_method($writer);
161 $class->remove_method($writer)
2eb717d5 162 if (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'));
163 }
c50c603e 164 }
165
166 if ($self->has_predicate()) {
167 my $predicate = $self->predicate();
168 if (reftype($predicate) && reftype($predicate) eq 'HASH') {
169 ($predicate) = keys %{$predicate};
170 }
171 my $method = $class->get_method($predicate);
172 $class->remove_method($predicate)
173 if (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'));
174 }
8b978dd5 175}
176
2eb717d5 177package Class::MOP::Attribute::Accessor;
178
179use strict;
180use warnings;
181
727919c5 182use Class::MOP::Method;
183
2eb717d5 184our $VERSION = '0.01';
185
186our @ISA = ('Class::MOP::Method');
187
8b978dd5 1881;
189
190__END__
191
192=pod
193
194=head1 NAME
195
196Class::MOP::Attribute - Attribute Meta Object
197
198=head1 SYNOPSIS
199
200 Class::MOP::Attribute->new('$foo' => (
201 accessor => 'foo', # dual purpose get/set accessor
202 init_arg => '-foo', # class->new will look for a -foo key
203 default => 'BAR IS BAZ!' # if no -foo key is provided, use this
204 ));
205
206 Class::MOP::Attribute->new('$.bar' => (
207 reader => 'bar', # getter
208 writer => 'set_bar', # setter
209 init_arg => '-bar', # class->new will look for a -bar key
210 # no default value means it is undef
211 ));
212
213=head1 DESCRIPTION
214
552e3d24 215The Attribute Protocol is almost entirely an invention of this module. This is
216because Perl 5 does not have consistent notion of what is an attribute
217of a class. There are so many ways in which this is done, and very few
218(if any) are discoverable by this module.
219
220So, all that said, this module attempts to inject some order into this
221chaos, by introducing a more consistent approach.
222
223=head1 METHODS
224
225=head2 Creation
226
227=over 4
228
229=item B<new ($name, %accessor_description, $class_initialization_arg, $default_value)>
230
231=back
232
233=head2 Informational
234
235=over 4
236
237=item B<name>
238
239=item B<accessor>
240
241=item B<reader>
242
243=item B<writer>
244
c50c603e 245=item B<predicate>
246
552e3d24 247=item B<init_arg>
248
249=item B<default>
250
251=back
252
253=head2 Informational predicates
254
255=over 4
256
257=item B<has_accessor>
258
259Returns true if this attribute uses a get/set accessor, and false
260otherwise
261
262=item B<has_reader>
263
264Returns true if this attribute has a reader, and false otherwise
265
266=item B<has_writer>
267
268Returns true if this attribute has a writer, and false otherwise
269
c50c603e 270=item B<has_predicate>
271
272Returns true if this attribute has a predicate, and false otherwise
273
552e3d24 274=item B<has_init_arg>
275
276Returns true if this attribute has a class intialization argument, and
277false otherwise
278
279=item B<has_default>
280
281Returns true if this attribute has a default value, and false
282otherwise.
283
284=back
285
286=head2 Attribute Accessor generation
287
288=over 4
289
2eb717d5 290=item B<install_accessors ($class)>
291
292This allows the attribute to generate and install code for it's own
293accessor methods. This is called by C<Class::MOP::Class::add_attribute>.
294
295=item B<remove_accessors ($class)>
296
297This allows the attribute to remove the method for it's own
298accessor. This is called by C<Class::MOP::Class::remove_attribute>.
299
300=back
301
302=head2 Introspection
303
304=over 4
552e3d24 305
2eb717d5 306=item B<meta>
552e3d24 307
308=back
309
8b978dd5 310=head1 AUTHOR
311
312Stevan Little E<gt>stevan@iinteractive.comE<lt>
313
314=head1 COPYRIGHT AND LICENSE
315
316Copyright 2006 by Infinity Interactive, Inc.
317
318L<http://www.iinteractive.com>
319
320This library is free software; you can redistribute it and/or modify
321it under the same terms as Perl itself.
322
323=cut