File Coverage

blib/lib/Form/Tiny/Meta.pm
Criterion Covered Total %
statement 195 195 100.0
branch 49 56 87.5
condition 31 41 75.6
subroutine 35 35 100.0
pod 12 13 92.3
total 322 340 94.7


line stmt bran cond sub pod time code
1             package Form::Tiny::Meta;
2             $Form::Tiny::Meta::VERSION = '2.21';
3 52     604   631 use v5.10;
  52         176  
4 52     61   269 use strict;
  52         111  
  52         1169  
5 52     52   275 use warnings;
  52         113  
  52         1418  
6 52     52   311 use Moo;
  52         121  
  52         342  
7 52     52   18656 use Types::Standard qw(Str ArrayRef HashRef InstanceOf Bool);
  52         121522  
  52         505  
8 52     52   148747 use Scalar::Util qw(blessed);
  52         126  
  52         2633  
9 52     52   343 use Carp qw(croak carp);
  52         122  
  52         2666  
10 52     52   325 use Sub::Util qw(set_subname);
  52         119  
  52         3027  
11              
12 52     52   21834 use Form::Tiny::FieldDefinitionBuilder;
  52         189  
  52         1988  
13 52     52   20946 use Form::Tiny::Hook;
  52         209  
  52         1744  
14 52     52   383 use Form::Tiny::Error;
  52         117  
  52         1639  
15 52     52   305 use Form::Tiny::Utils qw(try uniq get_package_form_meta has_form_meta);
  52         116  
  52         23112  
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 68     68 0 219 my ($self, $package) = @_;
95 68         1282 $self->_set_package($package);
96              
97 68 50       2600 if (!$package->can('form_meta')) {
98 52     52   478 no strict 'refs';
  52         133  
  52         2195  
99 52     52   611 no warnings 'redefine';
  52         285  
  52         30129  
100              
101 68         335 *{"${package}::form_meta"} = sub {
102 938     938   42639 goto \&get_package_form_meta;
        938      
103 68         315 };
104 68         273 set_subname "${package}::form_meta", *{"${package}::form_meta"};
  68         626  
105             }
106             }
107              
108             sub build_error
109             {
110 64     64 1 167 my ($self, $name, %params) = @_;
111 64         158 my $class = "Form::Tiny::Error::$name";
112 64         217 my $message = $self->messages->{$name};
113              
114 64 100       155 if (defined $message) {
115 3         7 $params{error} = $message;
116             }
117              
118 64         1285 return $class->new(%params);
119             }
120              
121             sub run_hooks_for
122             {
123 148     148 1 383 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 148   100     210 for my $hook (@{$self->hooks->{$stage} // []}) {
  148         871  
128 2         10 my $ret = $hook->code->(@data);
129 2 50       105 splice @data, -1, 1, $ret
130             if $hook->is_modifying;
131             }
132              
133 148         379 return $data[-1];
134             }
135              
136             sub inline_hooks
137             {
138 327     327 1 647 my ($self) = @_;
139              
140 327   66     1047 $self->{_cache}{inline_hooks} //= do {
141 85         161 my %inlined;
142 85         146 for my $stage (keys %{$self->hooks}) {
  85         395  
143 41         81 my @hooks = @{$self->hooks->{$stage}};
  41         167  
144             $inlined{$stage} = sub {
145 250     250   2230 my @data = @_;
146              
147 250         478 for my $hook (@hooks) {
148 263         979 my $ret = $hook->code->(@data);
149 263 100       1394 splice @data, -1, 1, $ret
150             if $hook->is_modifying;
151             }
152              
153 250         591 return $data[-1];
154 41         235 };
155             }
156              
157 85         333 \%inlined;
158             };
159              
160 327         1272 return $self->{_cache}{inline_hooks};
161             }
162              
163             sub bootstrap
164             {
165 119     119   288 my ($self) = @_;
166 119 50       426 return if $self->complete;
167              
168             # package name may be non-existent if meta is anon
169 119 100       487 if ($self->has_package) {
170              
171             # when this breaks, mst gets to point and laugh at me
172 65         122 my @parents = do {
173 65         200 my $package_name = $self->package;
174 52     52   523 no strict 'refs';
  52         239  
  52         77503  
175 65         107 @{"${package_name}::ISA"};
  65         425  
176             };
177              
178 65         188 my @real_parents = grep { has_form_meta($_) } @parents;
  64         227  
179              
180 65 50       320 croak 'Form::Tiny does not support multiple inheritance'
181             if @real_parents > 1;
182              
183 65         169 my ($parent) = @real_parents;
184              
185             # this is required so that proper hooks on inherit_from can be fired
186 65 100       332 $self->inherit_roles_from($parent ? $parent->form_meta : undef);
187 65 100       273 $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         121 $self->inherit_roles_from;
192             }
193              
194 119         1483 $self->setup;
195             }
196              
197             sub setup
198             {
199 119     119 1 2455 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 119         2708 $self->_set_complete(1);
204 119         4194 return;
205             }
206              
207             sub resolved_fields
208             {
209 226     226 1 524 my ($self, $object) = @_;
210              
211 226 100       745 return [@{$self->fields}] if !$self->is_dynamic;
  220         4338  
212              
213 6 50       41 croak 'resolved_fields requires form object'
214             unless defined blessed $object;
215              
216             return [
217             map {
218 11 100       81 $_->isa('Form::Tiny::FieldDefinitionBuilder')
219             ? $_->build($object)
220             : $_
221 6         14 } @{$self->fields}
  6         22  
222             ];
223             }
224              
225             sub add_field
226             {
227 295     295 1 814 my ($self, @parameters) = @_;
228 295         520 delete $self->{_cache};
229              
230 295 50       741 croak 'adding a form field requires at least one parameter'
231             unless scalar @parameters;
232              
233 295         539 my $scalar_param = shift @parameters;
234 295 100       807 if (ref $scalar_param eq '') {
235 243         689 $scalar_param = {@parameters, name => $scalar_param};
236             }
237              
238 295         5171 my $builder = Form::Tiny::FieldDefinitionBuilder->new(build_data => $scalar_param)->build;
239 288         884 push @{$self->fields}, $builder;
  288         1192  
240              
241 288 100       1554 $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 288 100 100     1042 if $self->is_dynamic || @{$builder->get_name_path->path} > 1;
  282         5153  
248              
249 288         8204 return $builder;
250             }
251              
252             sub add_field_validator
253             {
254 6     6 1 17 my ($self, $field, $message, $code) = @_;
255 6         14 delete $self->{_cache};
256              
257 6         10 push @{$field->addons->{validators}}, [$message, $code];
  6         30  
258 6         15 return $self;
259             }
260              
261             sub add_hook
262             {
263 67     67 1 65550 my ($self, $hook, $code) = @_;
264 67         167 delete $self->{_cache};
265              
266 67 100 66     563 if (defined blessed $hook && $hook->isa('Form::Tiny::Hook')) {
267 41         84 push @{$self->hooks->{$hook->hook}}, $hook;
  41         310  
268             }
269             else {
270 26         74 push @{$self->hooks->{$hook}}, Form::Tiny::Hook->new(
  26         645  
271             hook => $hook,
272             code => $code
273             );
274             }
275 67         1549 return $self;
276             }
277              
278             sub add_message
279             {
280 4     4 1 8 my ($self, $name, $message) = @_;
281              
282 4         6 my $isa;
283             my $err = try sub {
284 4     4   40 $isa = "Form::Tiny::Error::$name"->isa('Form::Tiny::Error');
285 4         18 };
286              
287 4 100 66     46 croak "$name is not a valid Form::Tiny error class name"
288             unless !$err && $isa;
289              
290 3         11 $self->messages->{$name} = $message;
291 3         12 return $self;
292             }
293              
294             sub inherit_roles_from
295             {
296 119     119 1 290 my ($self, $parent) = @_;
297              
298 119 100       400 if (defined $parent) {
299 5         15 $self->set_meta_roles([uniq(@{$parent->meta_roles}, @{$self->meta_roles})]);
  5         38  
  5         23  
300             }
301              
302             Moo::Role->apply_roles_to_object(
303 39         345 $self, @{$self->meta_roles}
304 119 100       409 ) if @{$self->meta_roles};
  119         489  
305              
306             Moo::Role->apply_roles_to_package(
307 65         565 $self->package, @{$self->form_roles}
308 119 100 66     72612 ) if $self->has_package && @{$self->form_roles};
  65         589  
309              
310 119         114395 return $self;
311             }
312              
313             sub inherit_from
314             {
315 5     5 1 233 my ($self, $parent) = @_;
316              
317 5 50 33     72 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         18 $self->set_fields([@{$parent->fields}, @{$self->fields}]);
  5         29  
  5         147  
322              
323             # hooks inheritance - need to filter out hooks that are not
324             # meant to be inherited
325 5         175 my %hooks = %{$self->hooks};
  5         28  
326 5         11 my %parent_hooks = %{$parent->hooks};
  5         32  
327 5         17 for my $key (keys %parent_hooks) {
328             $parent_hooks{$key} = [
329 5         9 grep { $_->inherited } @{$parent_hooks{$key}}
  6         29  
  5         10  
330             ];
331             }
332              
333             # actual hooks inheritance
334             $self->set_hooks(
335             {
336             map {
337 5   50     94 $_ => [@{$parent_hooks{$_} // []}, @{$hooks{$_} // []}]
  5   50     8  
  5         12  
  5         69  
338             } keys %parent_hooks,
339             keys %hooks
340             }
341             );
342              
343 5         203 $self->set_flat($parent->is_flat);
344 5         29 $self->set_dynamic($parent->is_dynamic);
345              
346 5         71 return $self;
347             }
348              
349             sub _build_blueprint
350             {
351 27     27   152 my ($self, $context, %params) = @_;
352 27         49 my %result;
353              
354 27   100     108 my $recurse = $params{recurse} // 1;
355             my $transform_base = sub {
356 173     173   358 my ($def) = @_;
357              
358 173 100 100     2959 if ($def->is_subform && $recurse) {
359 4         105 my $meta = get_package_form_meta($def->type);
360 4         25 return $meta->blueprint($def->type, %params);
361             }
362              
363 169         6666 return $def;
364 27         130 };
365              
366 27   66     149 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     175 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       597 my $fields = $context ? $context->field_defs : $self->fields;
375              
376 25         693 for my $def (@$fields) {
377 174         3443 my $meta = $def->get_name_path->meta_arrays;
378 174         1707 my @path = @{$def->get_name_path->path};
  174         2632  
379              
380             # adjust path so that instead of stars (*) we get zeros
381 174 100       1793 @path = map { $meta->[$_] ? 0 : $path[$_] } 0 .. $#path;
  274         915  
382              
383 174         427 Form::Tiny::Utils::_assign_field(
384             \%result,
385             $def, [[\@path, scalar $transform->($def, $transform_base)]]
386             );
387             }
388              
389 25         258 return \%result;
390             }
391              
392             sub blueprint
393             {
394 28     28 1 92 my ($self, @args) = @_;
395 28         52 my $context;
396 28 100 66     216 $context = shift @args
397             if @args && has_form_meta($args[0]);
398              
399 28 100 100     350 if ($self->is_dynamic || @args) {
400 24         156 return $self->_build_blueprint($context, @args);
401             }
402             else {
403             # $context can be skipped if the form is not dynamic
404 4         95 return $self->static_blueprint;
405             }
406             }
407              
408             1;
409              
410             __END__