summaryrefslogtreecommitdiff
path: root/lib/Type/Tiny/Manual/UsingWithClassTiny.pod
blob: b127b2b9f2e80dd663108d455dd96bbaf4f93420 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
=pod

=encoding utf-8

=head1 NAME

Type::Tiny::Manual::UsingWithClassTiny - use of Type::Tiny with Class::Tiny

=head1 MANUAL

L<Class::Tiny> is an even-smaller-than-Moo class builder.

Let's translate the classic Horse class from Moo to Class::Tiny.

Moo:

  package Horse {
    use Moo;
    use Types::Standard qw( Str Num ArrayRef );
    use namespace::autoclean;
    
    has name       => ( is => 'ro', isa => Str, required => 1 );
    has gender     => ( is => 'ro', isa => Str );
    has age        => ( is => 'rw', isa => Num );
    has children   => (
      is       => 'ro',
      isa      => ArrayRef,
      default  => sub { return [] },
    );
  }

Class::Tiny:

  package Horse {
    use Class::Tiny qw( gender age ), {
      name     => sub { die "name is required"; },
      children => sub { return [] },
    };
    use Types::Standard qw( Str Num ArrayRef Dict Optional Slurpy Any Object );
    use Type::Params qw( signature_for );
    use namespace::autoclean;
    
    # type checks
    signature_for BUILD => (
      method => Object,
      named  => [
        name       => Str,
        gender     => Optional[Str],
        age        => Optional[Num],
        children   => Optional[ArrayRef],
        ()         => Slurpy[Any],
      ],
      fallback => 1,
    );
    signature_for [ 'name', 'gender', 'children' ] => (
      method     => Object,
      positional => [],
    );
    signature_for age => (
      method     => Object,
      positional => [ Optional[Num] ],
    );
  }

What's going on here?

Well, Class::Tiny, after it has built a new object, will do this:

  $self->BUILD($args);

(Technically, it calls C<BUILD> not just for the current class, but for all
parent classes too.) We can hook onto this in order to check type constraints
for the constructor.

We use C<signature_for> from L<Type::Params> to wrap the original C<BUILD>
method (which doesn't exist, so C<< fallback => 1 >> will just assume an
empty sub) with a type check for its arguments. The type check is just a
B<Dict> that checks the class's required and optional attributes and includes
B<< Slurpy[Any] >> at the end to be flexible for subclasses adding new
attributes.

Then we wrap the C<name>, C<gender>, and C<children> methods with checks
to make sure they're only being called as getters, and we wrap C<age>,
allowing it to be called as a setter with a B<Num>.

There are also a couple of CPAN modules that can help you out.

=head2 Class::Tiny::ConstrainedAccessor

L<Class::Tiny::ConstrainedAccessor> creates a C<BUILD> and accessors that
enforce Type::Tiny constraints.  Attribute types are passed to
Class::Tiny::ConstrainedAccessor; attribute defaults are passed to Class::Tiny.

  package Horse {
    use Types::Standard qw( Str Num ArrayRef );
    use Class::Tiny::ConstrainedAccessor {
      name     => Str,
      gender   => Str,
      age      => Num,
      children => ArrayRef,
    };
    use Class::Tiny qw( gender age ), {
      name     => sub { die "name is required"; },
      children => sub { return [] },
    };
  }

=head2 Class::Tiny::Antlers

L<Class::Tiny::Antlers> provides Moose-like syntax for Class::Tiny, including
support for C<isa>.  You do not also need to use Class::Tiny itself.

  package Horse {
    use Class::Tiny::Antlers qw(has);
    use Types::Standard qw( Str Num ArrayRef );
    use namespace::autoclean;
    
    has name       => (
      is        => 'ro',
      isa       => Str,
      default   => sub { die "name is required" },
    );
    has gender     => ( is => 'ro',    isa => Str );
    has age        => ( is => 'rw',    isa => Num );
    has children   => (
      is        => 'ro',
      isa       => ArrayRef,
      default   => sub { return [] },
    );
  }

=head1 NEXT STEPS

Here's your next step:

=over

=item * L<Type::Tiny::Manual::UsingWithOther>

Using Type::Tiny with Class::InsideOut, Params::Check, and Object::Accessor.

=back

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2013-2014, 2017-2022 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

=cut