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.19';
3 51     588   669 use v5.10;
  51         189  
4 51     60   270 use strict;
  51         117  
  51         1162  
5 51     51   279 use warnings;
  51         130  
  51         1378  
6 51     51   332 use Moo;
  51         130  
  51         391  
7 51     51   19457 use Types::Standard qw(Str ArrayRef HashRef InstanceOf Bool);
  51         120206  
  51         514  
8 51     51   151312 use Scalar::Util qw(blessed);
  51         120  
  51         2754  
9 51     51   356 use Carp qw(croak carp);
  51         111  
  51         2648  
10 51     51   343 use Sub::Util qw(set_subname);
  51         107  
  51         3142  
11              
12 51     51   22255 use Form::Tiny::FieldDefinitionBuilder;
  51         259  
  51         2088  
13 51     51   21715 use Form::Tiny::Hook;
  51         205  
  51         1841  
14 51     51   1781 use Form::Tiny::Error;
  51         121  
  51         1671  
15 51     51   287 use Form::Tiny::Utils qw(try uniq get_package_form_meta has_form_meta);
  51         128  
  51         24479  
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 185 my ($self, $package) = @_;
95 66         1210 $self->_set_package($package);
96              
97 66 50       2627 if (!$package->can('form_meta')) {
98 51     51   481 no strict 'refs';
  51         150  
  51         2144  
99 51     51   405 no warnings 'redefine';
  51         365  
  51         30162  
100              
101 66         329 *{"${package}::form_meta"} = sub {
102 923     923   39142 goto \&get_package_form_meta;
        923      
103 66         337 };
104 66         196 set_subname "${package}::form_meta", *{"${package}::form_meta"};
  66         650  
105             }
106             }
107              
108             sub build_error
109             {
110 64     64 0 176 my ($self, $name, %params) = @_;
111 64         155 my $class = "Form::Tiny::Error::$name";
112 64         194 my $message = $self->messages->{$name};
113              
114 64 100       179 if (defined $message) {
115 3         7 $params{error} = $message;
116             }
117              
118 64         1386 return $class->new(%params);
119             }
120              
121             sub run_hooks_for
122             {
123 146     146 0 419 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     250 for my $hook (@{$self->hooks->{$stage} // []}) {
  146         921  
128 2         10 my $ret = $hook->code->(@data);
129 2 50       110 splice @data, -1, 1, $ret
130             if $hook->is_modifying;
131             }
132              
133 146         386 return $data[-1];
134             }
135              
136             sub inline_hooks
137             {
138 322     322 0 671 my ($self) = @_;
139              
140 322   66     1183 $self->{_cache}{inline_hooks} //= do {
141 83         172 my %inlined;
142 83         142 for my $stage (keys %{$self->hooks}) {
  83         423  
143 41         100 my @hooks = @{$self->hooks->{$stage}};
  41         184  
144             $inlined{$stage} = sub {
145 250     250   2366 my @data = @_;
146              
147 250         527 for my $hook (@hooks) {
148 263         1056 my $ret = $hook->code->(@data);
149 263 100       1580 splice @data, -1, 1, $ret
150             if $hook->is_modifying;
151             }
152              
153 250         675 return $data[-1];
154 41         313 };
155             }
156              
157 83         340 \%inlined;
158             };
159              
160 322         1308 return $self->{_cache}{inline_hooks};
161             }
162              
163             sub bootstrap
164             {
165 117     117   290 my ($self) = @_;
166 117 50       440 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         144 my @parents = do {
173 63         267 my $package_name = $self->package;
174 51     51   481 no strict 'refs';
  51         159  
  51         79245  
175 63         115 @{"${package_name}::ISA"};
  63         424  
176             };
177              
178 63         190 my @real_parents = grep { has_form_meta($_) } @parents;
  62         239  
179              
180 63 50       318 croak 'Form::Tiny does not support multiple inheritance'
181             if @real_parents > 1;
182              
183 63         188 my ($parent) = @real_parents;
184              
185             # this is required so that proper hooks on inherit_from can be fired
186 63 100       351 $self->inherit_roles_from($parent ? $parent->form_meta : undef);
187 63 100       313 $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         124 $self->inherit_roles_from;
192             }
193              
194 117         1660 $self->setup;
195             }
196              
197             sub setup
198             {
199 117     117 0 2670 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         2748 $self->_set_complete(1);
204 117         4272 return;
205             }
206              
207             sub resolved_fields
208             {
209 221     221 0 551 my ($self, $object) = @_;
210              
211 221 100       710 return [@{$self->fields}] if !$self->is_dynamic;
  215         4275  
212              
213 6 50       39 croak 'resolved_fields requires form object'
214             unless defined blessed $object;
215              
216             return [
217             map {
218 11 100       93 $_->isa('Form::Tiny::FieldDefinitionBuilder')
219             ? $_->build($object)
220             : $_
221 6         17 } @{$self->fields}
  6         22  
222             ];
223             }
224              
225             sub add_field
226             {
227 292     292 0 780 my ($self, @parameters) = @_;
228 292         538 delete $self->{_cache};
229              
230 292 50       696 croak 'adding a form field requires at least one parameter'
231             unless scalar @parameters;
232              
233 292         580 my $scalar_param = shift @parameters;
234 292 100       778 if (ref $scalar_param eq '') {
235 240         718 $scalar_param = {@parameters, name => $scalar_param};
236             }
237              
238 292         5171 my $builder = Form::Tiny::FieldDefinitionBuilder->new(build_data => $scalar_param)->build;
239 285         886 push @{$self->fields}, $builder;
  285         1213  
240              
241 285 100       1635 $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     1094 if $self->is_dynamic || @{$builder->get_name_path->path} > 1;
  279         5136  
248              
249 285         8349 return $builder;
250             }
251              
252             sub add_field_validator
253             {
254 6     6 0 19 my ($self, $field, $message, $code) = @_;
255 6         10 delete $self->{_cache};
256              
257 6         12 push @{$field->addons->{validators}}, [$message, $code];
  6         30  
258 6         26 return $self;
259             }
260              
261             sub add_hook
262             {
263 67     67 0 69081 my ($self, $hook, $code) = @_;
264 67         160 delete $self->{_cache};
265              
266 67 100 66     634 if (defined blessed $hook && $hook->isa('Form::Tiny::Hook')) {
267 41         134 push @{$self->hooks->{$hook->hook}}, $hook;
  41         355  
268             }
269             else {
270 26         63 push @{$self->hooks->{$hook}}, Form::Tiny::Hook->new(
  26         640  
271             hook => $hook,
272             code => $code
273             );
274             }
275 67         1603 return $self;
276             }
277              
278             sub add_message
279             {
280 4     4 0 10 my ($self, $name, $message) = @_;
281              
282 4         5 my $isa;
283             my $err = try sub {
284 4     4   37 $isa = "Form::Tiny::Error::$name"->isa('Form::Tiny::Error');
285 4         18 };
286              
287 4 100 66     42 croak "$name is not a valid Form::Tiny error class name"
288             unless !$err && $isa;
289              
290 3         12 $self->messages->{$name} = $message;
291 3         11 return $self;
292             }
293              
294             sub inherit_roles_from
295             {
296 117     117 0 285 my ($self, $parent) = @_;
297              
298 117 100       303 if (defined $parent) {
299 5         19 $self->set_meta_roles([uniq(@{$parent->meta_roles}, @{$self->meta_roles})]);
  5         43  
  5         29  
300             }
301              
302             Moo::Role->apply_roles_to_object(
303 39         428 $self, @{$self->meta_roles}
304 117 100       437 ) if @{$self->meta_roles};
  117         583  
305              
306             Moo::Role->apply_roles_to_package(
307 63         545 $self->package, @{$self->form_roles}
308 117 100 66     79186 ) if $self->has_package && @{$self->form_roles};
  63         645  
309              
310 117         116441 return $self;
311             }
312              
313             sub inherit_from
314             {
315 5     5 0 192 my ($self, $parent) = @_;
316              
317 5 50 33     78 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         14 $self->set_fields([@{$parent->fields}, @{$self->fields}]);
  5         22  
  5         162  
322              
323             # hooks inheritance - need to filter out hooks that are not
324             # meant to be inherited
325 5         224 my %hooks = %{$self->hooks};
  5         34  
326 5         16 my %parent_hooks = %{$parent->hooks};
  5         30  
327 5         31 for my $key (keys %parent_hooks) {
328             $parent_hooks{$key} = [
329 5         9 grep { $_->inherited } @{$parent_hooks{$key}}
  6         21  
  5         9  
330             ];
331             }
332              
333             # actual hooks inheritance
334             $self->set_hooks(
335             {
336             map {
337 5   50     93 $_ => [@{$parent_hooks{$_} // []}, @{$hooks{$_} // []}]
  5   50     7  
  5         16  
  5         87  
338             } keys %parent_hooks,
339             keys %hooks
340             }
341             );
342              
343 5         231 $self->set_flat($parent->is_flat);
344 5         41 $self->set_dynamic($parent->is_dynamic);
345              
346 5         74 return $self;
347             }
348              
349             sub _build_blueprint
350             {
351 27     27   166 my ($self, $context, %params) = @_;
352 27         51 my %result;
353              
354 27   100     105 my $recurse = $params{recurse} // 1;
355             my $transform_base = sub {
356 173     173   353 my ($def) = @_;
357              
358 173 100 100     2823 if ($def->is_subform && $recurse) {
359 4         107 my $meta = get_package_form_meta($def->type);
360 4         31 return $meta->blueprint($def->type, %params);
361             }
362              
363 169         6712 return $def;
364 27         148 };
365              
366 27   66     145 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     210 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       699 my $fields = $context ? $context->field_defs : $self->fields;
375              
376 25         779 for my $def (@$fields) {
377 174         270 my @meta = @{$def->get_name_path->meta};
  174         3014  
378 174         2232 my @path = @{$def->get_name_path->path};
  174         2696  
379              
380             # adjust path so that instead of stars (*) we get zeros
381 174 100       1886 @path = map { $meta[$_] eq 'ARRAY' ? 0 : $path[$_] } 0 .. $#path;
  274         823  
382              
383 174         461 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         276 return \%result;
395             }
396              
397             sub blueprint
398             {
399 28     28 0 108 my ($self, @args) = @_;
400 28         50 my $context;
401 28 100 66     215 $context = shift @args
402             if @args && has_form_meta($args[0]);
403              
404 28 100 100     408 if ($self->is_dynamic || @args) {
405 24         152 return $self->_build_blueprint($context, @args);
406             }
407             else {
408             # $context can be skipped if the form is not dynamic
409 4         148 return $self->static_blueprint;
410             }
411             }
412              
413             1;
414