File Coverage

blib/lib/Simple/Accessor.pm
Criterion Covered Total %
statement 80 84 95.2
branch 32 48 66.6
condition n/a
subroutine 12 12 100.0
pod n/a
total 124 144 86.1


line stmt bran cond sub pod time code
1             package Simple::Accessor;
2             $Simple::Accessor::VERSION = '1.13';
3 3     3   3450 use strict;
  3         7  
  3         96  
4 3     3   15 use warnings;
  3         5  
  3         546  
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 now call 'new' on your class, and create objects using these attributes
57              
58             package main;
59             use MyClass;
60              
61             my $o = MyClass->new()
62             or MyClass->new(bar => 42)
63             or MyClass->new(apple => 'fruit', cherry => 'fruit', banana => 'yummy');
64              
65             You can get / set any value using the accessor
66              
67             is $o->bar(), 42;
68             $o->bar(51);
69             is $o->bar(), 51;
70              
71             You can provide your own init method that will be call by new with default args.
72             This is optional.
73              
74             package MyClass;
75              
76             sub build { # previously known as initialize
77             my ($self, %opts) = @_;
78              
79             $self->foo(12345);
80             }
81              
82             You can also control the object after or before its creation using
83              
84             sub _before_build {
85             my ($self, %opts) = @_;
86             ...
87             }
88              
89             sub _after_build {
90             my ($self, %opts) = @_;
91             ...
92             bless $self, 'Basket';
93             }
94              
95             You can also provide individual builders / initializers
96              
97             sub _build_bar { # previously known as _initialize_bar
98             # will be used if no value has been provided for bar
99             1031;
100             }
101              
102             sub _build_cherry {
103             'red';
104             }
105              
106             You can even use a very basic but useful hook system.
107             Any false value return by before or validate, will stop the setting process.
108             Be careful with the after method, as there is no protection against infinite loop.
109              
110             sub _before_foo {
111             my ($self, $v) = @_;
112              
113             # do whatever you want with $v
114             return 1 or 0;
115             }
116              
117             sub _validate_foo {
118             my ($self, $v) = @_;
119             # invalid value ( will not be set )
120             return 0 if ( $v == 42);
121             # valid value
122             return 1;
123             }
124              
125             sub _after_cherry {
126             my ($self) = @_;
127              
128             # use the set value for extra operations
129             $self->apple($self->cherry());
130             }
131              
132             =head1 METHODS
133              
134             None. The only public method provided is the classical import.
135              
136             =cut
137              
138             my $INFO;
139              
140             sub import {
141 7     7   2636 my ( $class, @attr ) = @_;
142              
143 7         16 my $from = caller();
144              
145 7 100       23 $INFO = {} unless defined $INFO;
146 7 50       24 $INFO->{$from} = {} unless defined $INFO->{$from};
147 7         22 $INFO->{$from}->{'attributes'} = [ @attr ];
148              
149 7         18 _add_with($from);
150 7         39 _add_new($from);
151 7         27 _add_accessors( to => $from, attributes => \@attr );
152              
153 7         504 return;
154             }
155              
156             sub _add_with {
157 7     7   13 my $class = shift;
158 7 50       15 return unless $class;
159              
160 7         17 my $with = $class . '::with';
161             {
162 3     3   34 no strict 'refs';
  3         6  
  3         700  
  7         11  
163             *$with = sub {
164 2     2   11 my ( @what ) = @_;
165              
166 2 100       7 $INFO->{$class}->{'with'} = [] unless $INFO->{$class}->{'with'};
167 2         3 push @{$INFO->{$class}->{'with'}}, @what;
  2         5  
168              
169 2         5 foreach my $module ( @what ) {
170 2 50       103 eval qq[require $module; 1] or die $@;
171             _add_accessors(
172             to => $class,
173             attributes => $INFO->{$module}->{attributes},
174 2         10 from_role => $module
175             );
176             }
177              
178 2         4 return;
179 7         51 };
180             }
181             }
182              
183             sub _add_new {
184 7     7   13 my $class = shift;
185 7 50       21 return unless $class;
186              
187 7         13 my $new = $class . '::new';
188             {
189 3     3   22 no strict 'refs';
  3         12  
  3         892  
  7         12  
190             *$new = sub {
191 11     11   3052 my ( $class, %opts ) = @_;
192              
193 11         25 my $self = bless {}, $class;
194              
195             # set values if attributes exist
196             map {
197 11         30 eval { $self->$_( $opts{$_} ) }
  3         4  
  3         22  
198             } keys %opts;
199              
200 11 50       59 if ( $self->can( '_before_build') ) {
201 0         0 $self->_before_build( %opts );
202             }
203              
204 11         24 foreach my $init ( 'build', 'initialize' ) {
205 22 100       76 if ( $self->can( $init ) ) {
206 4 50       13 return unless $self->$init(%opts);
207             }
208             }
209              
210 11 50       54 if ( $self->can( '_after_build') ) {
211 0         0 $self->_after_build( %opts );
212             }
213              
214 11         44 return $self;
215 7         38 };
216             }
217             }
218              
219             sub _add_accessors {
220 9     9   29 my (%opts) = @_;
221              
222 9 50       50 return unless my $class = $opts{to};
223 9         17 my @attributes = @{ $opts{attributes} };
  9         25  
224 9 50       19 return unless @attributes;
225              
226 9         17 my $from_role = $opts{from_role};
227              
228 9         16 foreach my $att (@attributes) {
229 16         35 my $accessor = $class . "::" . $att;
230              
231 16 50       124 die "$class: attribute '$att' is already defined." if $class->can($att);
232              
233             # allow symbolic refs to typeglob
234 3     3   22 no strict 'refs';
  3         23  
  3         910  
235             *$accessor = sub {
236 45     45   7481 my ( $self, $v ) = @_;
237 45 100       175 if ( defined $v ) {
    100          
238 15         29 foreach (qw{before validate set after}) {
239 58 100       125 if ( $_ eq 'set' ) {
240 14         28 $self->{$att} = $v;
241 14         23 next;
242             }
243 44         81 my $sub = '_' . $_ . '_' . $att;
244 44 100       213 if ( $self->can( $sub ) ) {
    50          
245 4 100       11 return unless $self->$sub($v);
246             } elsif ( $from_role ) {
247 0 0       0 if ( my $code = $from_role->can( $sub ) ) {
248 0 0       0 return unless $code->( $self, $v );
249             }
250             }
251             }
252             }
253             elsif ( !defined $self->{$att} ) {
254             # try to initialize the value (try first with build)
255             # initialize is here for backward compatibility with older versions
256 12         24 foreach my $builder ( qw{build initialize} ) {
257 19         48 my $sub = '_' . $builder . '_' . $att;
258 19 100       99 if ( $self->can( $sub ) ) {
    100          
259 4         14 return $self->{$att} = $self->$sub();
260             } elsif ( $from_role ) {
261 2 50       10 if ( my $code = $from_role->can( $sub ) ) {
262 2         6 return $self->{$att} = $code->( $self );
263             }
264             }
265             }
266             }
267              
268 38         147 return $self->{$att};
269 16         89 };
270             }
271 9         28 @attributes = ();
272             }
273              
274             1;
275              
276             =head1 CONTRIBUTE
277              
278             You can contribute to this project on github https://github.com/atoomic/Simple-Accessor
279              
280             =cut
281              
282             __END__