File Coverage

blib/lib/Form/Tiny/Meta.pm
Criterion Covered Total %
statement 196 196 100.0
branch 49 56 87.5
condition 31 41 75.6
subroutine 35 35 100.0
pod 0 13 0.0
total 311 341 91.2


line stmt bran cond sub pod time code
1             package Form::Tiny::Meta;
2             $Form::Tiny::Meta::VERSION = '2.20';
3 51     588   657 use v5.10;
  51         175  
4 51     60   352 use strict;
  51         113  
  51         1188  
5 51     51   302 use warnings;
  51         119  
  51         1412  
6 51     51   309 use Moo;
  51         151  
  51         379  
7 51     51   19611 use Types::Standard qw(Str ArrayRef HashRef InstanceOf Bool);
  51         123827  
  51         532  
8 51     51   151947 use Scalar::Util qw(blessed);
  51         115  
  51         2650  
9 51     51   328 use Carp qw(croak carp);
  51         104  
  51         2616  
10 51     51   338 use Sub::Util qw(set_subname);
  51         128  
  51         3001  
11              
12 51     51   23013 use Form::Tiny::FieldDefinitionBuilder;
  51         262  
  51         2063  
13 51     51   21223 use Form::Tiny::Hook;
  51         178  
  51         1822  
14 51     51   365 use Form::Tiny::Error;
  51         134  
  51         1593  
15 51     51   290 use Form::Tiny::Utils qw(try uniq get_package_form_meta has_form_meta);
  51         134  
  51         23757  
16             require Moo::Role;
17              
18             # more clear error messages in some crucial cases
19             our @CARP_NOT = qw(Form::Tiny Form::Tiny::Form);
20              
21             has 'package' => (
22             is => 'ro',
23             writer => '_set_package',
24             isa => Str,
25             predicate => 'has_package',
26             );
27              
28             has 'fields' => (
29             is => 'ro',
30             writer => 'set_fields',
31             isa => ArrayRef [
32             InstanceOf ['Form::Tiny::FieldDefinitionBuilder'] | InstanceOf ['Form::Tiny::FieldDefinition']
33             ],
34             default => sub { [] },
35             );
36              
37             has 'is_flat' => (
38             is => 'ro',
39             writer => 'set_flat',
40             default => sub { 1 },
41             );
42              
43             has 'is_dynamic' => (
44             is => 'ro',
45             writer => 'set_dynamic',
46             default => sub { 0 },
47             );
48              
49             has 'hooks' => (
50             is => 'ro',
51             writer => 'set_hooks',
52             isa => HashRef [
53             ArrayRef [InstanceOf ['Form::Tiny::Hook']]
54             ],
55             default => sub { {} },
56             );
57              
58             has 'complete' => (
59             is => 'ro',
60             isa => Bool,
61             writer => '_set_complete',
62             default => sub { 0 },
63             );
64              
65             has 'meta_roles' => (
66             is => 'ro',
67             writer => 'set_meta_roles',
68             isa => ArrayRef,
69             default => sub { [] },
70             );
71              
72             has 'form_roles' => (
73             is => 'ro',
74             writer => 'set_form_roles',
75             isa => ArrayRef,
76             default => sub { [] },
77             );
78              
79             has 'messages' => (
80             is => 'ro',
81             isa => HashRef [Str],
82             default => sub { {} },
83             );
84              
85             has 'static_blueprint' => (
86             is => 'ro',
87             isa => HashRef,
88             lazy => 1,
89             builder => '_build_blueprint',
90             );
91              
92             sub set_package
93             {
94 66     66 0 177 my ($self, $package) = @_;
95 66         1191 $self->_set_package($package);
96              
97 66 50       2542 if (!$package->can('form_meta')) {
98 51     51   434 no strict 'refs';
  51         132  
  51         2169  
99 51     51   440 no warnings 'redefine';
  51         353  
  51         30730  
100              
101 66         326 *{"${package}::form_meta"} = sub {
102 923     923   38450 goto \&get_package_form_meta;
        923      
103 66         335 };
104 66         184 set_subname "${package}::form_meta", *{"${package}::form_meta"};
  66         636  
105             }
106             }
107              
108             sub build_error
109             {
110 64     64 0 168 my ($self, $name, %params) = @_;
111 64         157 my $class = "Form::Tiny::Error::$name";
112 64         202 my $message = $self->messages->{$name};
113              
114 64 100       168 if (defined $message) {
115 3         6 $params{error} = $message;
116             }
117              
118 64         1287 return $class->new(%params);
119             }
120              
121             sub run_hooks_for
122             {
123 146     146 0 379 my ($self, $stage, @data) = @_;
124              
125             # running hooks always returns the last element they're passed
126             # (unless they are not modifying, then they don't return anything)
127 146   100     254 for my $hook (@{$self->hooks->{$stage} // []}) {
  146         914  
128 2         10 my $ret = $hook->code->(@data);
129 2 50       129 splice @data, -1, 1, $ret
130             if $hook->is_modifying;
131             }
132              
133 146         388 return $data[-1];
134             }
135              
136             sub inline_hooks
137             {
138 322     322 0 603 my ($self) = @_;
139              
140 322   66     1035 $self->{_cache}{inline_hooks} //= do {
141 83         142 my %inlined;
142 83         139 for my $stage (keys %{$self->hooks}) {
  83         380  
143 41         97 my @hooks = @{$self->hooks->{$stage}};
  41         164  
144             $inlined{$stage} = sub {
145 250     250   2230 my @data = @_;
146              
147 250         501 for my $hook (@hooks) {
148 263         948 my $ret = $hook->code->(@data);
149 263 100       1543 splice @data, -1, 1, $ret
150             if $hook->is_modifying;
151             }
152              
153 250         676 return $data[-1];
154 41         305 };
155             }
156              
157 83         365 \%inlined;
158             };
159              
160 322         1230 return $self->{_cache}{inline_hooks};
161             }
162              
163             sub bootstrap
164             {
165 117     117   298 my ($self) = @_;
166 117 50       437 return if $self->complete;
167              
168             # package name may be non-existent if meta is anon
169 117 100       490 if ($self->has_package) {
170              
171             # when this breaks, mst gets to point and laugh at me
172 63         123 my @parents = do {
173 63         203 my $package_name = $self->package;
174 51     51   473 no strict 'refs';
  51         190  
  51         80969  
175 63         115 @{"${package_name}::ISA"};
  63         409  
176             };
177              
178 63         194 my @real_parents = grep { has_form_meta($_) } @parents;
  62         224  
179              
180 63 50       321 croak 'Form::Tiny does not support multiple inheritance'
181             if @real_parents > 1;
182              
183 63         173 my ($parent) = @real_parents;
184              
185             # this is required so that proper hooks on inherit_from can be fired
186 63 100       319 $self->inherit_roles_from($parent ? $parent->form_meta : undef);
187 63 100       318 $self->inherit_from($parent->form_meta) if $parent;
188             }
189             else {
190             # no package means no inheritance, but run this to properly consume meta roles
191 54         119 $self->inherit_roles_from;
192             }
193              
194 117         1624 $self->setup;
195             }
196              
197             sub setup
198             {
199 117     117 0 2666 my ($self) = @_;
200              
201             # at this point, all roles should already be merged and all inheritance done
202             # we can make the meta definition complete
203 117         2728 $self->_set_complete(1);
204 117         4209 return;
205             }
206              
207             sub resolved_fields
208             {
209 221     221 0 511 my ($self, $object) = @_;
210              
211 221 100       729 return [@{$self->fields}] if !$self->is_dynamic;
  215         4109  
212              
213 6 50       44 croak 'resolved_fields requires form object'
214             unless defined blessed $object;
215              
216             return [
217             map {
218 11 100       89 $_->isa('Form::Tiny::FieldDefinitionBuilder')
219             ? $_->build($object)
220             : $_
221 6         17 } @{$self->fields}
  6         38  
222             ];
223             }
224              
225             sub add_field
226             {
227 292     292 0 815 my ($self, @parameters) = @_;
228 292         547 delete $self->{_cache};
229              
230 292 50       701 croak 'adding a form field requires at least one parameter'
231             unless scalar @parameters;
232              
233 292         572 my $scalar_param = shift @parameters;
234 292 100       756 if (ref $scalar_param eq '') {
235 240         664 $scalar_param = {@parameters, name => $scalar_param};
236             }
237              
238 292         5135 my $builder = Form::Tiny::FieldDefinitionBuilder->new(build_data => $scalar_param)->build;
239 285         899 push @{$self->fields}, $builder;
  285         1198  
240              
241 285 100       1604 $self->set_dynamic(1)
242             if $builder->isa('Form::Tiny::FieldDefinitionBuilder');
243              
244             # NOTE: we can only know if the form is flat if it is not dynamic
245             # otherwise we need to assume it is not flat
246             $self->set_flat(0)
247 285 100 100     1028 if $self->is_dynamic || @{$builder->get_name_path->path} > 1;
  279         5106  
248              
249 285         8482 return $builder;
250             }
251              
252             sub add_field_validator
253             {
254 6     6 0 19 my ($self, $field, $message, $code) = @_;
255 6         13 delete $self->{_cache};
256              
257 6         9 push @{$field->addons->{validators}}, [$message, $code];
  6         31  
258 6         19 return $self;
259             }
260              
261             sub add_hook
262             {
263 67     67 0 67789 my ($self, $hook, $code) = @_;
264 67         152 delete $self->{_cache};
265              
266 67 100 66     607 if (defined blessed $hook && $hook->isa('Form::Tiny::Hook')) {
267 41         81 push @{$self->hooks->{$hook->hook}}, $hook;
  41         317  
268             }
269             else {
270 26         63 push @{$self->hooks->{$hook}}, Form::Tiny::Hook->new(
  26         576  
271             hook => $hook,
272             code => $code
273             );
274             }
275 67         1579 return $self;
276             }
277              
278             sub add_message
279             {
280 4     4 0 11 my ($self, $name, $message) = @_;
281              
282 4         7 my $isa;
283             my $err = try sub {
284 4     4   58 $isa = "Form::Tiny::Error::$name"->isa('Form::Tiny::Error');
285 4         17 };
286              
287 4 100 66     43 croak "$name is not a valid Form::Tiny error class name"
288             unless !$err && $isa;
289              
290 3         13 $self->messages->{$name} = $message;
291 3         9 return $self;
292             }
293              
294             sub inherit_roles_from
295             {
296 117     117 0 287 my ($self, $parent) = @_;
297              
298 117 100       332 if (defined $parent) {
299 5         14 $self->set_meta_roles([uniq(@{$parent->meta_roles}, @{$self->meta_roles})]);
  5         62  
  5         38  
300             }
301              
302             Moo::Role->apply_roles_to_object(
303 39         351 $self, @{$self->meta_roles}
304 117 100       440 ) if @{$self->meta_roles};
  117         579  
305              
306             Moo::Role->apply_roles_to_package(
307 63         444 $self->package, @{$self->form_roles}
308 117 100 66     76576 ) if $self->has_package && @{$self->form_roles};
  63         553  
309              
310 117         113307 return $self;
311             }
312              
313             sub inherit_from
314             {
315 5     5 0 208 my ($self, $parent) = @_;
316              
317 5 50 33     96 croak 'can only inherit from objects of Form::Tiny::Meta'
318             unless defined blessed $parent && $parent->isa('Form::Tiny::Meta');
319              
320             # TODO validate for fields with same names
321 5         21 $self->set_fields([@{$parent->fields}, @{$self->fields}]);
  5         24  
  5         141  
322              
323             # hooks inheritance - need to filter out hooks that are not
324             # meant to be inherited
325 5         183 my %hooks = %{$self->hooks};
  5         33  
326 5         19 my %parent_hooks = %{$parent->hooks};
  5         20  
327 5         19 for my $key (keys %parent_hooks) {
328             $parent_hooks{$key} = [
329 5         7 grep { $_->inherited } @{$parent_hooks{$key}}
  6         19  
  5         10  
330             ];
331             }
332              
333             # actual hooks inheritance
334             $self->set_hooks(
335             {
336             map {
337 5   50     96 $_ => [@{$parent_hooks{$_} // []}, @{$hooks{$_} // []}]
  5   50     7  
  5         13  
  5         70  
338             } keys %parent_hooks,
339             keys %hooks
340             }
341             );
342              
343 5         224 $self->set_flat($parent->is_flat);
344 5         42 $self->set_dynamic($parent->is_dynamic);
345              
346 5         73 return $self;
347             }
348              
349             sub _build_blueprint
350             {
351 27     27   162 my ($self, $context, %params) = @_;
352 27         51 my %result;
353              
354 27   100     122 my $recurse = $params{recurse} // 1;
355             my $transform_base = sub {
356 173     173   352 my ($def) = @_;
357              
358 173 100 100     2881 if ($def->is_subform && $recurse) {
359 4         107 my $meta = get_package_form_meta($def->type);
360 4         32 return $meta->blueprint($def->type, %params);
361             }
362              
363 169         6622 return $def;
364 27         159 };
365              
366 27   66     150 my $transform = $params{transform} // $transform_base;
367              
368             # croak, since we don't know anything about dynamic fields in static context
369 27 100 100     225 croak "Can't create a blueprint of a dynamic form"
370             if $self->is_dynamic && !$context;
371              
372             # if context is given, get the cached resolved fields from it
373             # note: context will never be passed when it is called by Moo to build 'blueprint'
374 25 100       652 my $fields = $context ? $context->field_defs : $self->fields;
375              
376 25         730 for my $def (@$fields) {
377 174         319 my @meta = @{$def->get_name_path->meta};
  174         3094  
378 174         1989 my @path = @{$def->get_name_path->path};
  174         2669  
379              
380             # adjust path so that instead of stars (*) we get zeros
381 174 100       1668 @path = map { $meta[$_] eq 'ARRAY' ? 0 : $path[$_] } 0 .. $#path;
  274         1187  
382              
383 174         463 Form::Tiny::Utils::_assign_field(
384             \%result,
385             $def, [
386             {
387             path => \@path,
388             value => scalar $transform->($def, $transform_base),
389             }
390             ]
391             );
392             }
393              
394 25         291 return \%result;
395             }
396              
397             sub blueprint
398             {
399 28     28 0 103 my ($self, @args) = @_;
400 28         49 my $context;
401 28 100 66     208 $context = shift @args
402             if @args && has_form_meta($args[0]);
403              
404 28 100 100     368 if ($self->is_dynamic || @args) {
405 24         165 return $self->_build_blueprint($context, @args);
406             }
407             else {
408             # $context can be skipped if the form is not dynamic
409 4         95 return $self->static_blueprint;
410             }
411             }
412              
413             1;
414