0_05
[gitmo/Moose.git] / lib / Moose / Meta / Attribute.pm
CommitLineData
c0e30cf5 1
2package Moose::Meta::Attribute;
3
4use strict;
5use warnings;
6
78cd1d3b 7use Scalar::Util 'blessed', 'weaken', 'reftype';
a15dff8d 8use Carp 'confess';
9
4c4fbe56 10our $VERSION = '0.05';
78cd1d3b 11
a3c7e2fe 12use Moose::Util::TypeConstraints ();
bc1e29b5 13
c0e30cf5 14use base 'Class::MOP::Attribute';
15
ca01a97b 16__PACKAGE__->meta->add_attribute('required' => (reader => 'is_required' ));
17__PACKAGE__->meta->add_attribute('lazy' => (reader => 'is_lazy' ));
6ba6d68c 18__PACKAGE__->meta->add_attribute('coerce' => (reader => 'should_coerce'));
19__PACKAGE__->meta->add_attribute('weak_ref' => (reader => 'is_weak_ref' ));
82168dbb 20__PACKAGE__->meta->add_attribute('type_constraint' => (
21 reader => 'type_constraint',
22 predicate => 'has_type_constraint',
23));
8c9d74e7 24__PACKAGE__->meta->add_attribute('trigger' => (
25 reader => 'trigger',
26 predicate => 'has_trigger',
27));
82168dbb 28
78cd1d3b 29sub new {
30 my ($class, $name, %options) = @_;
1d768fb1 31 $class->_process_options($name, \%options);
32 $class->SUPER::new($name, %options);
33}
34
ce0e8d63 35sub clone_and_inherit_options {
36 my ($self, %options) = @_;
37 # you can change default, required and coerce
38 my %actual_options;
39 foreach my $legal_option (qw(default coerce required)) {
40 if (exists $options{$legal_option}) {
41 $actual_options{$legal_option} = $options{$legal_option};
42 delete $options{$legal_option};
43 }
44 }
fcb7afc2 45 # isa can be changed, but only if the
46 # new type is a subtype
ce0e8d63 47 if ($options{isa}) {
48 my $type_constraint;
49 if (blessed($options{isa}) && $options{isa}->isa('Moose::Meta::TypeConstraint')) {
50 $type_constraint = $options{isa};
51 }
52 else {
53 $type_constraint = Moose::Util::TypeConstraints::find_type_constraint($options{isa});
54 (defined $type_constraint)
55 || confess "Could not find the type constraint '" . $options{isa} . "'";
56 }
57 ($type_constraint->is_subtype_of($self->type_constraint->name))
58 || confess "New type constraint setting must be a subtype of inherited one"
59 if $self->has_type_constraint;
60 $actual_options{type_constraint} = $type_constraint;
61 delete $options{isa};
62 }
63 (scalar keys %options == 0)
64 || confess "Illegal inherited options => (" . (join ', ' => keys %options) . ")";
65 $self->clone(%actual_options);
1d768fb1 66}
67
68sub _process_options {
69 my ($class, $name, $options) = @_;
70 if (exists $options->{is}) {
71 if ($options->{is} eq 'ro') {
72 $options->{reader} = $name;
73 (!exists $options->{trigger})
8c9d74e7 74 || confess "Cannot have a trigger on a read-only attribute";
78cd1d3b 75 }
1d768fb1 76 elsif ($options->{is} eq 'rw') {
77 $options->{accessor} = $name;
78 ((reftype($options->{trigger}) || '') eq 'CODE')
8c9d74e7 79 || confess "A trigger must be a CODE reference"
1d768fb1 80 if exists $options->{trigger};
78cd1d3b 81 }
82 }
83
1d768fb1 84 if (exists $options->{isa}) {
02a0fb52 85
1d768fb1 86 if (exists $options->{does}) {
87 if (eval { $options->{isa}->can('does') }) {
88 ($options->{isa}->does($options->{does}))
02a0fb52 89 || confess "Cannot have an isa option and a does option if the isa does not do the does";
90 }
7eaef7ad 91 else {
92 confess "Cannot have an isa option which cannot ->does()";
93 }
02a0fb52 94 }
95
78cd1d3b 96 # allow for anon-subtypes here ...
1d768fb1 97 if (blessed($options->{isa}) && $options->{isa}->isa('Moose::Meta::TypeConstraint')) {
98 $options->{type_constraint} = $options->{isa};
78cd1d3b 99 }
100 else {
c07af9d2 101
1d768fb1 102 if ($options->{isa} =~ /\|/) {
103 my @type_constraints = split /\s*\|\s*/ => $options->{isa};
104 $options->{type_constraint} = Moose::Util::TypeConstraints::create_type_constraint_union(
c07af9d2 105 @type_constraints
78cd1d3b 106 );
c07af9d2 107 }
108 else {
109 # otherwise assume it is a constraint
1d768fb1 110 my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{isa});
c07af9d2 111 # if the constraing it not found ....
112 unless (defined $constraint) {
113 # assume it is a foreign class, and make
114 # an anon constraint for it
115 $constraint = Moose::Util::TypeConstraints::subtype(
116 'Object',
1d768fb1 117 Moose::Util::TypeConstraints::where { $_->isa($options->{isa}) }
c07af9d2 118 );
119 }
1d768fb1 120 $options->{type_constraint} = $constraint;
c07af9d2 121 }
78cd1d3b 122 }
123 }
1d768fb1 124 elsif (exists $options->{does}) {
02a0fb52 125 # allow for anon-subtypes here ...
1d768fb1 126 if (blessed($options->{does}) && $options->{does}->isa('Moose::Meta::TypeConstraint')) {
127 $options->{type_constraint} = $options->{isa};
02a0fb52 128 }
129 else {
130 # otherwise assume it is a constraint
1d768fb1 131 my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{does});
02a0fb52 132 # if the constraing it not found ....
133 unless (defined $constraint) {
134 # assume it is a foreign class, and make
135 # an anon constraint for it
136 $constraint = Moose::Util::TypeConstraints::subtype(
137 'Role',
1d768fb1 138 Moose::Util::TypeConstraints::where { $_->does($options->{does}) }
02a0fb52 139 );
140 }
1d768fb1 141 $options->{type_constraint} = $constraint;
02a0fb52 142 }
143 }
78cd1d3b 144
1d768fb1 145 if (exists $options->{coerce} && $options->{coerce}) {
146 (exists $options->{type_constraint})
4b598ea3 147 || confess "You cannot have coercion without specifying a type constraint";
1d768fb1 148 (!$options->{type_constraint}->isa('Moose::Meta::TypeConstraint::Union'))
c07af9d2 149 || confess "You cannot have coercion with a type constraint union";
4b598ea3 150 confess "You cannot have a weak reference to a coerced value"
1d768fb1 151 if $options->{weak_ref};
ca01a97b 152 }
78cd1d3b 153
1d768fb1 154 if (exists $options->{lazy} && $options->{lazy}) {
155 (exists $options->{default})
ca01a97b 156 || confess "You cannot have lazy attribute without specifying a default value for it";
1d768fb1 157 }
78cd1d3b 158}
c0e30cf5 159
d500266f 160sub initialize_instance_slot {
161 my ($self, $class, $instance, $params) = @_;
162 my $init_arg = $self->init_arg();
163 # try to fetch the init arg from the %params ...
164 my $val;
165 if (exists $params->{$init_arg}) {
166 $val = $params->{$init_arg};
167 }
168 else {
169 # skip it if it's lazy
170 return if $self->is_lazy;
171 # and die if it's required and doesn't have a default value
172 confess "Attribute (" . $self->name . ") is required"
173 if $self->is_required && !$self->has_default;
174 }
175 # if nothing was in the %params, we can use the
176 # attribute's default value (if it has one)
177 if (!defined $val && $self->has_default) {
178 $val = $self->default($instance);
179 }
180 if (defined $val) {
181 if ($self->has_type_constraint) {
c07af9d2 182 my $type_constraint = $self->type_constraint;
183 if ($self->should_coerce && $type_constraint->has_coercion) {
184 $val = $type_constraint->coercion->coerce($val);
d500266f 185 }
c07af9d2 186 (defined($type_constraint->check($val)))
187 || confess "Attribute (" .
188 $self->name .
189 ") does not pass the type contraint (" .
190 $type_constraint->name .
191 ") with '$val'";
d500266f 192 }
193 }
194 $instance->{$self->name} = $val;
195 if (defined $val && $self->is_weak_ref) {
196 weaken($instance->{$self->name});
197 }
198}
199
a15dff8d 200sub generate_accessor_method {
201 my ($self, $attr_name) = @_;
ca01a97b 202 my $value_name = $self->should_coerce ? '$val' : '$_[1]';
203 my $code = 'sub { '
204 . 'if (scalar(@_) == 2) {'
205 . ($self->is_required ?
206 'defined($_[1]) || confess "Attribute ($attr_name) is required, so cannot be set to undef";'
207 : '')
208 . ($self->should_coerce ?
209 'my $val = $self->type_constraint->coercion->coerce($_[1]);'
210 : '')
211 . ($self->has_type_constraint ?
212 ('(defined $self->type_constraint->check(' . $value_name . '))'
c07af9d2 213 . '|| confess "Attribute ($attr_name) does not pass the type contraint (" . $self->type_constraint->name . ") with \'' . $value_name . '\'"'
ca01a97b 214 . 'if defined ' . $value_name . ';')
215 : '')
216 . '$_[0]->{$attr_name} = ' . $value_name . ';'
217 . ($self->is_weak_ref ?
218 'weaken($_[0]->{$attr_name});'
219 : '')
8c9d74e7 220 . ($self->has_trigger ?
daea75c9 221 '$self->trigger->($_[0], ' . $value_name . ', $self);'
8c9d74e7 222 : '')
ca01a97b 223 . ' }'
224 . ($self->is_lazy ?
225 '$_[0]->{$attr_name} = ($self->has_default ? $self->default($_[0]) : undef)'
226 . 'unless exists $_[0]->{$attr_name};'
227 : '')
228 . ' $_[0]->{$attr_name};'
229 . ' }';
230 my $sub = eval $code;
231 confess "Could not create writer for '$attr_name' because $@ \n code: $code" if $@;
232 return $sub;
a15dff8d 233}
234
235sub generate_writer_method {
236 my ($self, $attr_name) = @_;
ca01a97b 237 my $value_name = $self->should_coerce ? '$val' : '$_[1]';
238 my $code = 'sub { '
239 . ($self->is_required ?
240 'defined($_[1]) || confess "Attribute ($attr_name) is required, so cannot be set to undef";'
241 : '')
242 . ($self->should_coerce ?
243 'my $val = $self->type_constraint->coercion->coerce($_[1]);'
244 : '')
245 . ($self->has_type_constraint ?
246 ('(defined $self->type_constraint->check(' . $value_name . '))'
c07af9d2 247 . '|| confess "Attribute ($attr_name) does not pass the type contraint (" . $self->type_constraint->name . ") with \'' . $value_name . '\'"'
ca01a97b 248 . 'if defined ' . $value_name . ';')
249 : '')
250 . '$_[0]->{$attr_name} = ' . $value_name . ';'
251 . ($self->is_weak_ref ?
252 'weaken($_[0]->{$attr_name});'
253 : '')
8c9d74e7 254 . ($self->has_trigger ?
daea75c9 255 '$self->trigger->($_[0], ' . $value_name . ', $self);'
8c9d74e7 256 : '')
ca01a97b 257 . ' }';
258 my $sub = eval $code;
259 confess "Could not create writer for '$attr_name' because $@ \n code: $code" if $@;
260 return $sub;
a15dff8d 261}
c0e30cf5 262
d7f17ebb 263sub generate_reader_method {
264 my ($self, $attr_name) = @_;
ca01a97b 265 my $code = 'sub {'
266 . 'confess "Cannot assign a value to a read-only accessor" if @_ > 1;'
267 . ($self->is_lazy ?
268 '$_[0]->{$attr_name} = ($self->has_default ? $self->default($_[0]) : undef)'
269 . 'unless exists $_[0]->{$attr_name};'
270 : '')
271 . '$_[0]->{$attr_name};'
272 . '}';
273 my $sub = eval $code;
274 confess "Could not create reader for '$attr_name' because $@ \n code: $code" if $@;
275 return $sub;
d7f17ebb 276}
277
c0e30cf5 2781;
279
280__END__
281
282=pod
283
284=head1 NAME
285
6ba6d68c 286Moose::Meta::Attribute - The Moose attribute metaclass
c0e30cf5 287
288=head1 DESCRIPTION
289
e522431d 290This is a subclass of L<Class::MOP::Attribute> with Moose specific
6ba6d68c 291extensions.
292
293For the most part, the only time you will ever encounter an
294instance of this class is if you are doing some serious deep
295introspection. To really understand this class, you need to refer
296to the L<Class::MOP::Attribute> documentation.
e522431d 297
c0e30cf5 298=head1 METHODS
299
6ba6d68c 300=head2 Overridden methods
301
302These methods override methods in L<Class::MOP::Attribute> and add
303Moose specific features. You can safely assume though that they
304will behave just as L<Class::MOP::Attribute> does.
305
c0e30cf5 306=over 4
307
308=item B<new>
309
ce0e8d63 310=item B<clone_and_inherit_options>
1d768fb1 311
d500266f 312=item B<initialize_instance_slot>
313
a15dff8d 314=item B<generate_accessor_method>
315
316=item B<generate_writer_method>
317
d7f17ebb 318=item B<generate_reader_method>
319
a15dff8d 320=back
321
6ba6d68c 322=head2 Additional Moose features
323
324Moose attributes support type-contstraint checking, weak reference
325creation and type coercion.
326
a15dff8d 327=over 4
328
329=item B<has_type_constraint>
330
6ba6d68c 331Returns true if this meta-attribute has a type constraint.
332
a15dff8d 333=item B<type_constraint>
334
6ba6d68c 335A read-only accessor for this meta-attribute's type constraint. For
336more information on what you can do with this, see the documentation
337for L<Moose::Meta::TypeConstraint>.
a15dff8d 338
6ba6d68c 339=item B<is_weak_ref>
a15dff8d 340
02a0fb52 341Returns true if this meta-attribute produces a weak reference.
4b598ea3 342
ca01a97b 343=item B<is_required>
344
02a0fb52 345Returns true if this meta-attribute is required to have a value.
ca01a97b 346
347=item B<is_lazy>
348
02a0fb52 349Returns true if this meta-attribute should be initialized lazily.
ca01a97b 350
351NOTE: lazy attributes, B<must> have a C<default> field set.
352
34a66aa3 353=item B<should_coerce>
4b598ea3 354
02a0fb52 355Returns true if this meta-attribute should perform type coercion.
6ba6d68c 356
8c9d74e7 357=item B<has_trigger>
358
02a0fb52 359Returns true if this meta-attribute has a trigger set.
360
8c9d74e7 361=item B<trigger>
362
02a0fb52 363This is a CODE reference which will be executed every time the
364value of an attribute is assigned. The CODE ref will get two values,
365the invocant and the new value. This can be used to handle I<basic>
366bi-directional relations.
367
c0e30cf5 368=back
369
370=head1 BUGS
371
372All complex software has bugs lurking in it, and this module is no
373exception. If you find a bug please either email me, or add the bug
374to cpan-RT.
375
c0e30cf5 376=head1 AUTHOR
377
378Stevan Little E<lt>stevan@iinteractive.comE<gt>
379
380=head1 COPYRIGHT AND LICENSE
381
382Copyright 2006 by Infinity Interactive, Inc.
383
384L<http://www.iinteractive.com>
385
386This library is free software; you can redistribute it and/or modify
387it under the same terms as Perl itself.
388
389=cut