6 Moose::Cookbook::Recipe5 - More subtypes, coercion in a B<Request> class
12 use Moose::Util::TypeConstraints;
15 use Params::Coerce ();
20 => where { $_->isa('HTTP::Headers') };
24 => via { HTTP::Headers->new( @{ $_ } ) }
26 => via { HTTP::Headers->new( %{ $_ } ) };
30 => where { $_->isa('URI') };
34 => via { $_->isa('URI')
36 : Params::Coerce::coerce( 'URI', $_ ) }
38 => via { URI->new( $_, 'http' ) };
42 => where { /^HTTP\/[0-9]\.[0-9]$/ };
44 has 'base' => (is => 'rw', isa => 'Uri', coerce => 1);
45 has 'uri' => (is => 'rw', isa => 'Uri', coerce => 1);
46 has 'method' => (is => 'rw', isa => 'Str');
47 has 'protocol' => (is => 'rw', isa => 'Protocol');
52 default => sub { HTTP::Headers->new }
57 This recipe introduces the idea of type coercions, and the C<coerce>
58 keyword. Coercions can be attached to existing type constraints,
59 and can be used to transform input of one type into input of another
60 type. This can be an extremely powerful tool if used correctly, which
61 is why it is off by default. If you want your accessor to attempt
62 a coercion, you must specifically ask for it with the B<coerce> option.
64 Now, onto the coercions.
66 First we need to create a subtype to attach our coercion to. Here we
67 create a basic I<Header> subtype, which matches any instance of the
68 class B<HTTP::Headers>:
72 => where { $_->isa('HTTP::Headers') };
74 The simplest thing from here would be create an accessor declaration
80 default => sub { HTTP::Headers->new }
83 We would then have a self-validating accessor whose default value is
84 an empty instance of B<HTTP::Headers>. This is nice, but it is not
87 The constructor for B<HTTP::Headers> accepts a list of key-value pairs
88 representing the HTTP header fields. In Perl, such a list could
89 easily be stored in an ARRAY or HASH reference. We would like our
90 class's interface to be able to accept this list of key-value pairs
91 in place of the B<HTTP::Headers> instance, and just DWIM. This is where
92 coercion can help. First, let's declare our coercion:
96 => via { HTTP::Headers->new( @{ $_ } ) }
98 => via { HTTP::Headers->new( %{ $_ } ) };
100 We first tell it that we are attaching the coercion to the 'Header'
101 subtype. We then give it a set of C<from> clauses which map other
102 subtypes to coercion routines (through the C<via> keyword). Fairly
103 simple really; however, this alone does nothing. We have to tell
104 our attribute declaration to actually use the coercion, like so:
110 default => sub { HTTP::Headers->new }
113 This will coerce any B<ArrayRef> or B<HashRef> which is passed into
114 the C<headers> accessor into an instance of B<HTTP::Headers>. So the
115 the following lines of code are all equivalent:
117 $foo->headers(HTTP::Headers->new(bar => 1, baz => 2));
118 $foo->headers([ 'bar', 1, 'baz', 2 ]);
119 $foo->headers({ bar => 1, baz => 2 });
121 As you can see, careful use of coercions can produce a very open
122 interface for your class, while still retaining the "safety" of
123 your type constraint checks.
125 Our next coercion takes advantage of the power of CPAN to handle
126 the details of our coercion. In this particular case it uses the
127 L<Params::Coerce> module, which fits in rather nicely with L<Moose>.
129 Again, we create a simple subtype to represent instances of the
134 => where { $_->isa('URI') };
136 Then we add the coercion:
140 => via { $_->isa('URI')
142 : Params::Coerce::coerce( 'URI', $_ ) }
144 => via { URI->new( $_, 'http' ) };
146 The first C<from> clause we introduce is for the 'Object' subtype. An 'Object'
147 is simply any C<bless>ed value. This means that if the coercion encounters
148 another object, it should use this clause. Now we look at the C<via> block.
149 First it checks to see if the object is a B<URI> instance. Since the coercion
150 process occurs prior to any type constraint checking, it is entirely possible
151 for this to happen, and if it does happen, we simply want to pass the instance
152 on through. However, if it is not an instance of B<URI>, then we need to coerce
153 it. This is where L<Params::Coerce> can do its magic, and we can just use its
154 return value. Simple really, and much less work since we used a module from CPAN
157 The second C<from> clause is attached to the 'Str' subtype, and
158 illustrates how coercions can also be used to handle certain
159 'default' behaviors. In this coercion, we simple take any string
160 and pass it to the B<URI> constructor along with the default
163 And of course, our coercions do nothing unless they are told to,
166 has 'base' => (is => 'rw', isa => 'Uri', coerce => 1);
167 has 'uri' => (is => 'rw', isa => 'Uri', coerce => 1);
169 As you can see, re-using the coercion allows us to enforce a
170 consistent and very flexible API across multiple accessors.
174 This recipe illustrated the power of coercions to build a more
175 flexible and open API for your accessors, while still retaining
176 all the safety that comes from using Moose's type constraints.
177 Using coercions it becomes simple to manage (from a single
178 location) a consistent API not only across multiple accessors,
179 but across multiple classes as well.
181 In the next recipe, we will introduce roles, a concept originally
182 borrowed from Smalltalk, which made it's way into Perl 6, and
187 Stevan Little E<lt>stevan@iinteractive.comE<gt>
189 =head1 COPYRIGHT AND LICENSE
191 Copyright 2006, 2007 by Infinity Interactive, Inc.
193 L<http://www.iinteractive.com>
195 This library is free software; you can redistribute it and/or modify
196 it under the same terms as Perl itself.