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.26';
3 53     661   706 use v5.10;
  53         201  
4 53     62   313 use strict;
  53         96  
  53         1545  
5 53     53   259 use warnings;
  53         124  
  53         2832  
6 53     53   342 use Moo;
  53         101  
  53         433  
7 53     53   24175 use Types::Standard qw(Str ArrayRef HashRef InstanceOf Bool);
  53         109730  
  53         648  
8 53     53   129595 use Scalar::Util qw(blessed);
  53         521  
  53         4064  
9 53     53   500 use Carp qw(croak carp);
  53         122  
  53         3640  
10 53     53   357 use Sub::Util qw(set_subname);
  53         99  
  53         3935  
11              
12 53     53   28710 use Form::Tiny::FieldDefinitionBuilder;
  53         228  
  53         2536  
13 53     53   26993 use Form::Tiny::Hook;
  53         216  
  53         2298  
14 53     53   392 use Form::Tiny::Error;
  53         107  
  53         2485  
15 53     53   343 use Form::Tiny::Utils qw(try uniq get_package_form_meta has_form_meta);
  53         128  
  53         26509  
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 71     71 0 231 my ($self, $package) = @_;
95 71         1587 $self->_set_package($package);
96              
97 71 50       3125 if (!$package->can('form_meta')) {
98 53     53   499 no strict 'refs';
  53         129  
  53         2969  
99 53     53   330 no warnings 'redefine';
  53         138  
  53         35700  
100              
101 71         359 *{"${package}::form_meta"} = sub {
102 977     977   1297134 goto \&get_package_form_meta;
        977      
103 71         398 };
104 71         185 set_subname "${package}::form_meta", *{"${package}::form_meta"};
  71         731  
105             }
106             }
107              
108             sub build_error
109             {
110 71     71 1 274 my ($self, $name, %params) = @_;
111 71         250 my $class = "Form::Tiny::Error::$name";
112 71         268 my $message = $self->messages->{$name};
113              
114 71 100       233 if (defined $message) {
115 4         5 $params{error} = $message;
116             }
117              
118 71         1719 return $class->new(%params);
119             }
120              
121             sub run_hooks_for
122             {
123 161     161 1 491 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 161   100     304 for my $hook (@{$self->hooks->{$stage} // []}) {
  161         1347  
128 2         15 my $ret = $hook->code->(@data);
129 2 50       137 splice @data, -1, 1, $ret
130             if $hook->is_modifying;
131             }
132              
133 161         418 return $data[-1];
134             }
135              
136             sub inline_hooks
137             {
138 340     340 1 748 my ($self) = @_;
139              
140 340   66     1404 $self->{_cache}{inline_hooks} //= do {
141 88         242 my %inlined;
142 88         173 for my $stage (keys %{$self->hooks}) {
  88         515  
143 44         83 my @hooks = @{$self->hooks->{$stage}};
  44         176  
144             $inlined{$stage} = sub {
145 261     261   2293 my @data = @_;
146              
147 261         526 for my $hook (@hooks) {
148 274         1827 my $ret = $hook->code->(@data);
149 274 100       1901 splice @data, -1, 1, $ret
150             if $hook->is_modifying;
151             }
152              
153 261         791 return $data[-1];
154 44         390 };
155             }
156              
157 88         388 \%inlined;
158             };
159              
160 340         1354 return $self->{_cache}{inline_hooks};
161             }
162              
163             sub bootstrap
164             {
165 122     122   370 my ($self) = @_;
166 122 50       563 return if $self->complete;
167              
168             # package name may be non-existent if meta is anon
169 122 100       586 if ($self->has_package) {
170              
171             # when this breaks, mst gets to point and laugh at me
172 68         146 my @parents = do {
173 68         295 my $package_name = $self->package;
174 53     53   452 no strict 'refs';
  53         172  
  53         107180  
175 68         133 @{"${package_name}::ISA"};
  68         517  
176             };
177              
178 68         218 my @real_parents = grep { has_form_meta($_) } @parents;
  67         301  
179              
180 68 50       324 croak 'Form::Tiny does not support multiple inheritance'
181             if @real_parents > 1;
182              
183 68         198 my ($parent) = @real_parents;
184              
185             # this is required so that proper hooks on inherit_from can be fired
186 68 100       546 $self->inherit_roles_from($parent ? $parent->form_meta : undef);
187 68 100       392 $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         165 $self->inherit_roles_from;
192             }
193              
194 122         2269 $self->setup;
195             }
196              
197             sub setup
198             {
199 122     122 1 673 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 122         4509 $self->_set_complete(1);
204 122         5374 return;
205             }
206              
207             sub resolved_fields
208             {
209 236     236 1 675 my ($self, $object) = @_;
210              
211 236 100       1043 return [@{$self->fields}] if !$self->is_dynamic;
  228         5651  
212              
213 8 50       43 croak 'resolved_fields requires form object'
214             unless defined blessed $object;
215              
216             return [
217             map {
218 13 100       123 $_->isa('Form::Tiny::FieldDefinitionBuilder')
219             ? $_->build($object)
220             : $_
221 8         20 } @{$self->fields}
  8         32  
222             ];
223             }
224              
225             sub add_field
226             {
227 298     298 1 1031 my ($self, @parameters) = @_;
228 298         602 delete $self->{_cache};
229              
230 298 50       826 croak 'adding a form field requires at least one parameter'
231             unless scalar @parameters;
232              
233 298         703 my $scalar_param = shift @parameters;
234 298 100       1025 if (ref $scalar_param eq '') {
235 245         867 $scalar_param = {@parameters, name => $scalar_param};
236             }
237              
238 298         7481 my $builder = Form::Tiny::FieldDefinitionBuilder->new(build_data => $scalar_param)->build;
239 291         1098 push @{$self->fields}, $builder;
  291         1441  
240              
241 291 100       2251 $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 291 100 100     1528 if $self->is_dynamic || @{$builder->get_name_path->path} > 1;
  284         7256  
248              
249 291         10201 return $builder;
250             }
251              
252             sub add_field_validator
253             {
254 6     6 1 20 my ($self, $field, $message, $code) = @_;
255 6         13 delete $self->{_cache};
256              
257 6         12 push @{$field->addons->{validators}}, [$message, $code];
  6         51  
258 6         20 return $self;
259             }
260              
261             sub add_hook
262             {
263 70     70 1 82984 my ($self, $hook, $code) = @_;
264 70         218 delete $self->{_cache};
265              
266 70 100 66     690 if (defined blessed $hook && $hook->isa('Form::Tiny::Hook')) {
267 44         103 push @{$self->hooks->{$hook->hook}}, $hook;
  44         437  
268             }
269             else {
270 26         62 push @{$self->hooks->{$hook}}, Form::Tiny::Hook->new(
  26         832  
271             hook => $hook,
272             code => $code
273             );
274             }
275 70         2233 return $self;
276             }
277              
278             sub add_message
279             {
280 4     4 1 13 my ($self, $name, $message) = @_;
281              
282 4         8 my $isa;
283             my $err = try sub {
284 4     4   58 $isa = "Form::Tiny::Error::$name"->isa('Form::Tiny::Error');
285 4         45 };
286              
287 4 100 66     68 croak "$name is not a valid Form::Tiny error class name"
288             unless !$err && $isa;
289              
290 3         18 $self->messages->{$name} = $message;
291 3         15 return $self;
292             }
293              
294             sub inherit_roles_from
295             {
296 122     122 1 341 my ($self, $parent) = @_;
297              
298 122 100       359 if (defined $parent) {
299 5         12 $self->set_meta_roles([uniq(@{$parent->meta_roles}, @{$self->meta_roles})]);
  5         61  
  5         32  
300             }
301              
302             Moo::Role->apply_roles_to_object(
303 42         557 $self, @{$self->meta_roles}
304 122 100       415 ) if @{$self->meta_roles};
  122         633  
305              
306             Moo::Role->apply_roles_to_package(
307 68         814 $self->package, @{$self->form_roles}
308 122 100 66     96558 ) if $self->has_package && @{$self->form_roles};
  68         659  
309              
310 122         154064 return $self;
311             }
312              
313             sub inherit_from
314             {
315 5     5 1 37 my ($self, $parent) = @_;
316              
317 5 50 33     71 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         12 $self->set_fields([@{$parent->fields}, @{$self->fields}]);
  5         20  
  5         207  
322              
323             # hooks inheritance - need to filter out hooks that are not
324             # meant to be inherited
325 5         222 my %hooks = %{$self->hooks};
  5         28  
326 5         11 my %parent_hooks = %{$parent->hooks};
  5         20  
327 5         16 for my $key (keys %parent_hooks) {
328             $parent_hooks{$key} = [
329 5         8 grep { $_->inherited } @{$parent_hooks{$key}}
  6         26  
  5         28  
330             ];
331             }
332              
333             # actual hooks inheritance
334             $self->set_hooks(
335             {
336             map {
337 5   50     81 $_ => [@{$parent_hooks{$_} // []}, @{$hooks{$_} // []}]
  5   50     9  
  5         17  
  5         99  
338             } keys %parent_hooks,
339             keys %hooks
340             }
341             );
342              
343 5         186 $self->set_flat($parent->is_flat);
344 5         27 $self->set_dynamic($parent->is_dynamic);
345              
346 5         263 return $self;
347             }
348              
349             sub _build_blueprint
350             {
351 30     30   141 my ($self, $context, %params) = @_;
352 30         53 my %result;
353              
354 30   100     203 my $recurse = $params{recurse} // 1;
355             my $transform_base = sub {
356 176     176   351 my ($def) = @_;
357              
358 176 100 100     3142 if ($def->is_subform && $recurse) {
359 4         60 my $meta = get_package_form_meta($def->type);
360 4         15 return $meta->blueprint($def->type, %params);
361             }
362              
363 172         7325 return $def;
364 30         175 };
365              
366 30   66     664 my $transform = $params{transform} // $transform_base;
367              
368             # croak, since we don't know anything about dynamic fields in static context
369 30 100 100     306 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 28 100       1106 my $fields = $context ? $context->field_defs : $self->fields;
375              
376 28         727 for my $def (@$fields) {
377 177         3347 my $meta = $def->get_name_path->meta_arrays;
378 177         1454 my @path = @{$def->get_name_path->path};
  177         2844  
379              
380             # adjust path so that instead of stars (*) we get zeros
381 177 100       1590 @path = map { $meta->[$_] ? 0 : $path[$_] } 0 .. $#path;
  277         850  
382              
383 177         454 Form::Tiny::Utils::_assign_field(
384             \%result,
385             $def, [[\@path, scalar $transform->($def, $transform_base)]]
386             );
387             }
388              
389 28         275 return \%result;
390             }
391              
392             sub blueprint
393             {
394 31     31 1 114 my ($self, @args) = @_;
395 31         135 my $context;
396 31 100 66     200 $context = shift @args
397             if @args && has_form_meta($args[0]);
398              
399 31 100 100     430 if ($self->is_dynamic || @args) {
400 27         210 return $self->_build_blueprint($context, @args);
401             }
402             else {
403             # $context can be skipped if the form is not dynamic
404 4         71 return $self->static_blueprint;
405             }
406             }
407              
408             1;
409              
410             __END__