File Coverage

blib/lib/Simple/Accessor.pm
Criterion Covered Total %
statement 113 113 100.0
branch 67 70 95.7
condition 16 21 76.1
subroutine 12 12 100.0
pod n/a
total 208 216 96.3


line stmt bran cond sub pod time code
1             package Simple::Accessor;
2             $Simple::Accessor::VERSION = '1.14';
3 13     13   2002812 use strict;
  13         29  
  13         483  
4 13     13   123 use warnings;
  13         24  
  13         3814  
5              
6             # ABSTRACT: a light and simple way to provide accessor in perl
7              
8             # VERSION
9              
10             =head1 NAME
11             Simple::Accessor - very simple, light and powerful accessor
12              
13             =head1 SYNOPSIS
14              
15             package Role::Color;
16             use Simple::Accessor qw{color};
17              
18             sub _build_color { 'red' } # default color
19              
20             package Car;
21              
22             # that s all what you need ! no more line required
23             use Simple::Accessor qw{brand hp};
24              
25             with 'Role::Color';
26              
27             sub _build_hp { 2 }
28             sub _build_brand { 'unknown' }
29              
30             package main;
31              
32             my $c = Car->new( brand => 'zebra' );
33              
34             is $c->brand, 'zebra';
35             is $c->color, 'red';
36              
37             =head1 DESCRIPTION
38              
39             Simple::Accessor provides a simple object layer without any dependency.
40             It can be used where other ORM could be considered too heavy.
41             But it has also the main advantage to only need one single line of code.
42              
43             It can be easily used in scripts...
44              
45             =head1 Usage
46              
47             Create a package and just call Simple::Accessor.
48             The new method will be imported for you, and all accessors will be directly
49             accessible.
50              
51             package MyClass;
52              
53             # that s all what you need ! no more line required
54             use Simple::Accessor qw{foo bar cherry apple};
55              
56             You can also split your attribute declarations across multiple C statements.
57             Attributes from all imports are merged and fully supported by the constructor,
58             strict constructor mode, and deterministic initialization ordering.
59              
60             package MyClass;
61              
62             use Simple::Accessor qw{foo bar};
63             use Simple::Accessor qw{cherry apple};
64              
65             # all four attributes work in the constructor
66             my $o = MyClass->new(foo => 1, bar => 2, cherry => 3, apple => 4);
67              
68             You can now call 'new' on your class, and create objects using these attributes
69              
70             package main;
71             use MyClass;
72              
73             my $o = MyClass->new()
74             or MyClass->new(bar => 42)
75             or MyClass->new(apple => 'fruit', cherry => 'fruit', banana => 'yummy');
76              
77             You can get / set any value using the accessor
78              
79             is $o->bar(), 42;
80             $o->bar(51);
81             is $o->bar(), 51;
82              
83             You can provide your own init method that will be call by new with default args.
84             This is optional.
85              
86             package MyClass;
87              
88             sub build { # previously known as initialize
89             my ($self, %opts) = @_;
90              
91             $self->foo(12345);
92             }
93              
94             You can also control the object after or before its creation using
95              
96             sub _before_build {
97             my ($self, %opts) = @_;
98             ...
99             }
100              
101             sub _after_build {
102             my ($self, %opts) = @_;
103             ...
104             bless $self, 'Basket';
105             }
106              
107             You can also provide individual builders / initializers
108              
109             sub _build_bar { # previously known as _initialize_bar
110             # will be used if no value has been provided for bar
111             1031;
112             }
113              
114             sub _build_cherry {
115             'red';
116             }
117              
118             You can enable strict constructor mode to catch typos in attribute names:
119              
120             package MyClass;
121             use Simple::Accessor qw{name age};
122              
123             sub _strict_constructor { 1 }
124              
125             package main;
126             MyClass->new(nmae => 'oops');
127             # dies: "MyClass->new(): unknown attribute(s): nmae"
128              
129             This is opt-in and off by default for backward compatibility.
130              
131             You can even use a very basic but useful hook system.
132             Any false value return by before or validate, will stop the setting process.
133             The after hooks include a re-entrancy guard: if an C<_after_*> hook triggers
134             a setter that would re-enter the same attribute, the nested C<_after_*> call
135             is skipped to prevent infinite recursion.
136              
137             sub _before_foo {
138             my ($self, $v) = @_;
139              
140             # do whatever you want with $v
141             return 1 or 0;
142             }
143              
144             sub _validate_foo {
145             my ($self, $v) = @_;
146             # invalid value ( will not be set )
147             return 0 if ( $v == 42);
148             # valid value
149             return 1;
150             }
151              
152             sub _after_cherry {
153             my ($self) = @_;
154              
155             # use the set value for extra operations
156             $self->apple($self->cherry());
157             }
158              
159             =head1 METHODS
160              
161             None. The only public method provided is the classical import.
162              
163             =cut
164              
165             my $INFO;
166              
167             sub import {
168 83     83   171872 my ( $class, @attr ) = @_;
169              
170 83         178 my $from = caller();
171              
172 83 100       221 $INFO = {} unless defined $INFO;
173 83 100       280 $INFO->{$from} = {} unless defined $INFO->{$from};
174 83   100     401 $INFO->{$from}->{'attributes'} ||= [];
175              
176 83         178 _add_with($from);
177 83         175 _add_new($from);
178 83         190 _add_accessors( to => $from, attributes => \@attr );
179              
180             # append after _add_accessors succeeds (it dies on duplicates)
181 82         124 push @{$INFO->{$from}->{'attributes'}}, @attr;
  82         226  
182              
183 82         26511 return;
184             }
185              
186             sub _add_with {
187 83     83   113 my $class = shift;
188 83 50       156 return unless $class;
189 83 100       704 return if $class->can('with');
190              
191 80         160 my $with = $class . '::with';
192             {
193 13     13   153 no strict 'refs';
  13         39  
  13         6783  
  80         96  
194             *$with = sub {
195 31     31   776614 my ( @what ) = @_;
196              
197 31 100       172 $INFO->{$class}->{'with'} = [] unless $INFO->{$class}->{'with'};
198 31         53 push @{$INFO->{$class}->{'with'}}, @what;
  31         91  
199              
200 31         70 foreach my $module ( @what ) {
201 32 100       242 die "Invalid module name: $module" unless $module =~ /\A[A-Za-z_]\w*(?:::\w+)*\z/;
202             # skip require if the role is already registered (e.g. inline package)
203 30 100 66     172 unless ($INFO->{$module} && $INFO->{$module}->{attributes}) {
204 8 100       589 eval qq[require $module; 1] or die $@;
205             }
206             die "$module is not a Simple::Accessor role"
207 28 100 66     186 unless $INFO->{$module} && $INFO->{$module}->{attributes};
208             # Resolve each attribute's origin role for transitive composition.
209             # If MiddleRole composed OriginRole's attrs, their hooks live
210             # in OriginRole — not MiddleRole. Pass the correct origin so
211             # the accessor closure can find _build_*, _before_*, etc.
212 26   100     126 my $origins = $INFO->{$module}{attr_origin} || {};
213 26         44 foreach my $att (@{$INFO->{$module}->{attributes}}) {
  26         68  
214             _add_accessors(
215             to => $class,
216             attributes => [$att],
217 32   66     174 from_role => $origins->{$att} || $module
218             );
219             }
220             }
221              
222 25         80 return;
223 80         668 };
224             }
225             }
226              
227             sub _add_new {
228 83     83   112 my $class = shift;
229 83 50       142 return unless $class;
230 83 100       331 return if $class->can('new');
231              
232 80         127 my $new = $class . '::new';
233             {
234 13     13   99 no strict 'refs';
  13         37  
  13         5335  
  80         94  
235             *$new = sub {
236 79     79   938675 my ( $class, %opts ) = @_;
237              
238 79         218 my $self = bless {}, $class;
239              
240 79 100       526 if ( $self->can( '_before_build') ) {
241 2         9 $self->_before_build( %opts );
242             }
243              
244             # set values for known attributes (in declaration order)
245 79   50     288 my $attrs = $INFO->{$class}{attributes} || [];
246 79         135 foreach my $attr ( @{$attrs} ) {
  79         180  
247 168 100       470 $self->$attr( $opts{$attr} ) if exists $opts{$attr};
248             }
249              
250             # strict constructor: die on unknown attributes
251 78 100 66     398 if ( $self->can('_strict_constructor') && $self->_strict_constructor() ) {
252 7         37 my %known = map { $_ => 1 } @{$attrs};
  16         49  
  7         17  
253 7         23 my @unknown = sort grep { !$known{$_} } keys %opts;
  14         47  
254 7 100       22 if (@unknown) {
255 4         73 die "$class\->new(): unknown attribute(s): "
256             . join(', ', @unknown) . "\n";
257             }
258             }
259              
260 74         154 foreach my $init ( 'build', 'initialize' ) {
261 141 100       610 if ( $self->can( $init ) ) {
262 13 100       48 return unless $self->$init(%opts);
263 9         56 last; # build takes precedence over initialize
264             }
265             }
266              
267 70 100       273 if ( $self->can( '_after_build') ) {
268 3         10 $self->_after_build( %opts );
269             }
270              
271 70         310 return $self;
272 80         549 };
273             }
274             }
275              
276             sub _add_accessors {
277 115     115   360 my (%opts) = @_;
278              
279 115 50       262 return unless my $class = $opts{to};
280 115         156 my @attributes = @{ $opts{attributes} };
  115         264  
281 115 100       219 return unless @attributes;
282              
283 113         175 my $from_role = $opts{from_role};
284              
285 113         268 foreach my $att (@attributes) {
286 141         247 my $accessor = $class . "::" . $att;
287              
288 141 100       838 if ( $class->can($att) ) {
289             # skip silently when composing roles (duplicates are OK)
290 4 100       28 next if $from_role;
291 1         24 die "$class: attribute '$att' is already defined.";
292             }
293              
294             # track role attributes in the class's attribute list and remember
295             # which role originally defined them (for transitive composition)
296 137 100       259 if ( $from_role ) {
297 29         42 push @{$INFO->{$class}{attributes}}, $att;
  29         75  
298 29         76 $INFO->{$class}{attr_origin}{$att} = $from_role;
299             }
300              
301             # allow symbolic refs to typeglob
302 13     13   104 no strict 'refs';
  13         24  
  13         7270  
303             *$accessor = sub {
304 229     229   30415 my ( $self, $v ) = @_;
305 229 100       750 if ( @_ > 1 ) {
    100          
306             # re-entrancy guard: skip _after_* if we're already setting this attribute
307 119         337 my $is_reentrant = $self->{__sa_setting}{$att};
308 119         320 local $self->{__sa_setting}{$att} = 1;
309              
310 119         229 foreach (qw{before validate set after}) {
311 460 100       972 if ( $_ eq 'set' ) {
312 112         188 $self->{$att} = $v;
313 112         182 next;
314             }
315 348 100 100     795 if ( $_ eq 'after' && $is_reentrant ) {
316 2         7 next;
317             }
318 346         509 my $sub = '_' . $_ . '_' . $att;
319 346 100       1430 if ( $self->can( $sub ) ) {
    100          
320 40 100       119 return unless $self->$sub($v);
321             } elsif ( $from_role ) {
322 42 100       237 if ( my $code = $from_role->can( $sub ) ) {
323 18 100       50 return unless $code->( $self, $v );
324             }
325             }
326             }
327             }
328             elsif ( !exists $self->{$att} ) {
329             # try to initialize the value (try first with build)
330             # initialize is here for backward compatibility with older versions
331 36         71 foreach my $builder ( qw{build initialize} ) {
332 51         110 my $sub = '_' . $builder . '_' . $att;
333 51 100       365 if ( $self->can( $sub ) ) {
    100          
334 10         29 return $self->{$att} = $self->$sub();
335             } elsif ( $from_role ) {
336 14 100       112 if ( my $code = $from_role->can( $sub ) ) {
337 12         35 return $self->{$att} = $code->( $self );
338             }
339             }
340             }
341             }
342              
343 199         667 return $self->{$att};
344 137         1294 };
345             }
346             }
347              
348             1;
349              
350             =head1 CONTRIBUTE
351              
352             You can contribute to this project on github https://github.com/atoomic/Simple-Accessor
353              
354             =cut
355              
356             __END__