File Coverage

blib/lib/Object/Depot.pm
Criterion Covered Total %
statement 163 179 91.0
branch 52 100 52.0
condition 14 26 53.8
subroutine 39 41 95.1
pod 13 13 100.0
total 281 359 78.2


line stmt bran cond sub pod time code
1             package Object::Depot;
2             our $VERSION = '0.02';
3 7     7   1133849 use strictures 2;
  7         63  
  7         253  
4              
5             =encoding utf8
6              
7             =head1 NAME
8              
9             Object::Depot - Decouple object instantiation from usage.
10              
11             =head1 SYNOPSIS
12              
13             use Object::Depot;
14            
15             my $depot = Object::Depot->new(
16             class => 'CHI',
17             # CHI->new returns a CHI::Driver object.
18             type => InstanceOf[ 'CHI::Driver' ],
19             );
20            
21             $depot->add_key(
22             sessions => {
23             driver => 'Memory',
24             global => 1,
25             },
26             );
27            
28             $depot->store( ip2geo => CHI->new(...) );
29            
30             my $sessions = $depot->fetch('sessions');
31             my $ip2geo = $depot->fetch('ip2geo');
32              
33             =head1 DESCRIPTION
34              
35             Object depots encapsulate object construction so that users of objects
36             do not need to know how to create the objects in order to use them.
37              
38             The primary use case for this library is for storing the connection
39             logic to external services and making these connections globally
40             available to all application logic. See L<Object::Depot::Singleton>
41             for turning your depot object into a global singleton.
42              
43             =cut
44              
45 7     7   4384 use Guard qw( guard );
  7         3641  
  7         398  
46 7     7   1947 use Object::Depot::Singleton qw();
  7         20  
  7         156  
47 7     7   42 use Carp qw();
  7         15  
  7         104  
48 7     7   33 use Role::Tiny qw();
  7         12  
  7         133  
49 7     7   44 use Scalar::Util qw( blessed );
  7         14  
  7         364  
50 7     7   865 use Sub::Name qw( subname );
  7         1005  
  7         378  
51 7     7   3216 use Types::Common::String qw( NonEmptySimpleStr );
  7         889599  
  7         97  
52 7     7   3997 use Types::Standard qw( Bool CodeRef HashRef Object InstanceOf );
  7         17  
  7         44  
53              
54             sub croak {
55             local $Carp::Internal{'Object::Depot'} = 1;
56             goto &Carp::croak;
57             }
58              
59             sub croakf {
60             my $msg = shift;
61             $msg = sprintf( $msg, @_ );
62             @_ = ( $msg );
63             goto &croak;
64             }
65              
66 7     7   11982 use Moo;
  7         32677  
  7         49  
67 7     7   9506 use namespace::clean;
  7         25178  
  7         56  
68              
69             sub _normalize_args {
70 12     12   27 my ($self, $args) = @_;
71              
72 12 100       37 return {} if !@$args;
73 4 100 66     29 return $args->[0] if @$args==1 and ref($args->[0]) eq 'HASH';
74 1 50       7 return { @$args } unless @$args % 2;
75              
76 0         0 croakf(
77             'Odd number of arguments passed to %s()',
78             scalar( caller ),
79             );
80             }
81              
82             sub _process_key_arg {
83 66     66   2093 my ($self, $args) = @_;
84              
85 66         387 my $caller_sub_name = (caller 1)[3];
86 66 50       233 $caller_sub_name = '__ANON__' if !defined $caller_sub_name;
87 66         350 $caller_sub_name =~ s{^.*::}{};
88              
89 66         112 my $key;
90              
91 66 100 100     360 $key = shift @$args
92             if @$args and !blessed $args->[0];
93              
94 66 100 100     282 if ($self->_has_default_key() and !defined $key) {
95 14         36 $key = $self->default_key();
96             }
97             else {
98 52 100       146 croak "No key was passed to $caller_sub_name()"
99             if !defined $key;
100              
101 44 50       154 if (!NonEmptySimpleStr->check( $key )) {
102 0 0       0 $key = defined($key) ? ["$key"] : 'UNDEF';
103 0         0 croak "Invalid key, $key, passed to $caller_sub_name(): " .
104             NonEmptySimpleStr->message( $key );
105             }
106             }
107              
108 58 100       917 $key = $self->_aliases->{$key} if exists $self->_aliases->{$key};
109              
110 58 50 33     173 if ($self->strict_keys() and !exists $self->_key_args->{$key}) {
111 0 0       0 $key = defined($key) ? qq["$key"] : 'UNDEF';
112 0         0 croak "Undeclared key, $key, passed to $caller_sub_name()"
113             }
114              
115 58         142 return $key;
116             }
117              
118             sub _export {
119 3     3   7 my $self = shift;
120 3         6 my $package = shift;
121              
122 3 50       22 return if !$self->_has_export_name();
123              
124 3         19 my $name = $self->export_name();
125 3         7 my $do_it = $self->always_export();
126              
127 3         9 foreach my $arg (@_) {
128 1 50 33     11 if (defined($arg) and $arg eq $name) {
129 1         4 $do_it = 1;
130 1         3 next;
131             }
132              
133             croakf(
134 0 0       0 'Unknown export, %s, passed to %s',
135             defined($arg) ? qq["$arg"] : 'undef',
136             $package,
137             );
138             }
139              
140 3 100       11 return if !$do_it;
141              
142 2     4   21 my $sub = subname $name => sub{ $self->fetch(@_) };
  4     4   6832  
143 7     7   6629 { no strict 'refs'; *{"$package\::$name"} = $sub };
  7         68  
  7         14895  
  2         7  
  2         4  
  2         11  
144              
145 2         7 return;
146             }
147              
148             has _all_objects => (
149             is => 'ro',
150             default => sub{ {} },
151             );
152              
153             sub _objects {
154 37     37   76 my ($self) = @_;
155              
156 37 50       225 return $self->_all_objects() if !$self->per_process();
157              
158 0         0 my $key = $$;
159 0 0       0 $key .= '-' . threads->tid() if $INC{'threads.pm'};
160              
161 0   0     0 return $self->_all_objects->{$key} ||= {};
162             }
163              
164             has _key_args => (
165             is => 'ro',
166             default => sub{ {} },
167             );
168              
169             has _aliases => (
170             is => 'ro',
171             default => sub{ {} },
172             );
173              
174             has _injections => (
175             is => 'ro',
176             default => sub{ {} },
177             );
178              
179             =head1 ARGUMENTS
180              
181             =head2 class
182              
183             class => 'CHI',
184              
185             The class which objects in this depot are expected to be. This
186             argument defaults the L</constructor> and L</type> arguments.
187              
188             Does not have a default.
189              
190             Leaving this argument unset causes L</fetch> to fail on keys that were
191             not first populated with L</store> as the L</constructor> subroutine
192             will just return C<undef>.
193              
194             =cut
195              
196             has class => (
197             is => 'ro',
198             isa => NonEmptySimpleStr,
199             predicate => '_has_class',
200             );
201              
202             =head2 constructor
203              
204             constuctor => sub{
205             my ($args) = @_;
206             return __PACKAGE__->depot->class->new( $args );
207             },
208              
209             Set this to a code ref to control how objects get constructed.
210              
211             When declaring a custom constructor be careful not to create memory
212             leaks via circular references.
213              
214             L</create> validates the objects produced by this constructor and will
215             throw an exception if they do not match L</type>.
216              
217             The default code ref is similar to the above example if L</class> is
218             set. If it is not set then the default code ref will return C<undef>.
219              
220             =cut
221              
222             has constructor => (
223             is => 'lazy',
224             isa => CodeRef,
225             );
226              
227             my $undef_constructor = sub{ undef };
228              
229             sub _build_constructor {
230 9     9   105 my ($self) = @_;
231              
232 9 50       54 return $undef_constructor if !$self->_has_class();
233              
234 9         43 return _build_class_constructor( $self->class() );
235             }
236              
237             sub _build_class_constructor {
238 9     9   22 my ($class) = @_;
239 9     14   180 return sub{ $class->new( @_ ) };
  14         344  
240             }
241              
242             =head2 type
243              
244             type => InstanceOf[ 'CHI::Driver' ],
245              
246             Set this to a L<Type::Tiny> type to control how objects in the depot
247             are validated when they are stored.
248              
249             Defaults to C<InstanceOf> L</class>, if set. If the class is not set
250             then this defaults to C<Object> (both are from L<Types::Standard>).
251              
252             =cut
253              
254             has type => (
255             is => 'lazy',
256             isa => InstanceOf[ 'Type::Tiny' ],
257             );
258              
259             sub _build_type {
260 10     10   111 my ($self) = @_;
261 10 100       91 return InstanceOf[ $self->class() ] if $self->_has_class();
262 1         4 return Object;
263             }
264              
265             =head2 per_process
266              
267             per_process => 1,
268              
269             Turn this on to store objects per-process; meaning, if the TID (thread
270             ID) or PID (process ID) change then this depot will act as if no
271             objects have been stored. Generally you will not want to turn this
272             on. On occasion, though, some objects are not thread or forking safe
273             and it is necessary.
274              
275             Defaults off.
276              
277             =cut
278              
279             has per_process => (
280             is => 'ro',
281             isa => Bool,
282             default => 0,
283             );
284              
285             =head2 disable_store
286              
287             disable_store => 1,
288              
289             When on this causes L</store> to silently not store, causing all
290             L</fetch> calls for non-injected keys to return a new object.
291              
292             Defaults off.
293              
294             =cut
295              
296             has disable_store => (
297             is => 'ro',
298             isa => Bool,
299             default => 0,
300             );
301              
302             =head2 strict_keys
303              
304             strict_keys => 1,
305              
306             Turn this on to require that all keys used must first be declared
307             via L</add_key> before they can be stored in the depot.
308              
309             Defaults to off, meaning keys may be used without having to
310             pre-declare them.
311              
312             =cut
313              
314             has strict_keys => (
315             is => 'ro',
316             isa => Bool,
317             default => 0,
318             );
319              
320             =head2 default_key
321              
322             default_key => 'generic',
323              
324             If no key is passed to key-accepting methods like L</fetch> then they
325             will use this default key if available.
326              
327             Defaults to no default key.
328              
329             =cut
330              
331             has default_key => (
332             is => 'ro',
333             isa => NonEmptySimpleStr,
334             predicate => '_has_default_key',
335             );
336              
337             =head2 key_argument
338              
339             key_argument => 'connection_key',
340              
341             When set, this causes L</arguments> to include an extra argument to be
342             passed to the class during object construction. The argument's key
343             will be whatever you set this to and the value will be the key used to
344             fetch the object.
345              
346             You will still need to write the code in your class to capture the
347             argument, such as:
348              
349             has connection_key => ( is=>'ro' );
350              
351             Defaults to no key argument.
352              
353             =cut
354              
355             has key_argument => (
356             is => 'ro',
357             isa => NonEmptySimpleStr,
358             predicate => '_has_key_argument',
359             );
360              
361             =head2 default_arguments
362              
363             default_arguments => {
364             arg => 'value',
365             ...
366             },
367              
368             When set, these arguments will be included in calls to L</arguments>.
369              
370             Defaults to an empty hash ref.
371              
372             =cut
373              
374             has default_arguments => (
375             is => 'lazy',
376             isa => HashRef,
377             default => sub{ {} },
378             );
379              
380             =head2 export_name
381              
382             export_name => 'myapp_cache',
383              
384             Set the name of a function that L<Object::Depot::Singleton> will
385             export to importers of your depot package.
386              
387             Has no default. If this is not set, then nothing will be exported.
388              
389             =cut
390              
391             has export_name => (
392             is => 'ro',
393             isa => NonEmptySimpleStr,
394             predicate => '_has_export_name',
395             );
396              
397             =head2 always_export
398              
399             always_export => 1,
400              
401             Turning this on causes L<Object::Depot::Singleton> to always export
402             the L</export_name>, rather than only when listed in the import
403             arguments. This is synonymous with the difference between
404             L<Exporter>'s C<@EXPORT_OK> and C<@EXPORT>.
405              
406             =cut
407              
408             has always_export => (
409             is => 'ro',
410             isa => Bool,
411             default => 0,
412             );
413              
414             =head1 METHODS
415              
416             =head2 fetch
417              
418             my $object = $depot->fetch( $key );
419              
420             =cut
421              
422             sub fetch {
423 29     29 1 1793 my $self = shift;
424              
425 29         88 my $key = $self->_process_key_arg( \@_ );
426 25 50       68 croak 'Too many arguments passed to fetch()' if @_;
427              
428 25         66 return $self->_fetch( $key );
429             }
430              
431             sub _fetch {
432 25     25   51 my ($self, $key) = @_;
433              
434 25         75 my $object = $self->_injections->{ $key };
435 25   100     110 $object ||= $self->_objects->{$key};
436 25 100       128 return $object if $object;
437              
438 13 100       59 return undef if !$self->_has_class();
439              
440 11         42 $object = $self->_create( $key, {} );
441              
442 11         78 $self->_store( $key, $object );
443              
444 11         57 return $object;
445             }
446              
447             =head2 store
448              
449             $depot->store( $key => $object );
450              
451             =cut
452              
453             sub store {
454 1     1 1 39 my $self = shift;
455              
456 1         4 my $key = $self->_process_key_arg( \@_ );
457 1 50       4 croak 'Too many arguments passed to store()' if @_>1;
458 1 50       4 croak 'Not enough arguments passed to store()' if @_<1;
459              
460 1         2 my $object = shift;
461 1 50       27 croakf(
462             'Invalid object passed to store(): %s',
463             $self->type->get_message( $object ),
464             ) if !$self->type->check( $object );
465              
466             croak qq[Already stored key, "$key", passed to store()]
467 1 50       101 if exists $self->_objects->{$key};
468              
469 1         5 return $self->_store( $key, $object );
470             }
471              
472             sub _store {
473 12     12   36 my ($self, $key, $object) = @_;
474              
475 12 50       62 return if $self->disable_store();
476              
477 12         119 $self->_objects->{$key} = $object;
478              
479 12         29 return;
480             }
481              
482             =head2 remove
483              
484             $depot->remove( $key );
485              
486             =cut
487              
488             sub remove {
489 2     2 1 4 my $self = shift;
490              
491 2         7 my $key = $self->_process_key_arg( \@_ );
492 2 50       6 croak 'Too many arguments passed to remove()' if @_;
493              
494 2         6 return $self->_remove( $key );
495             }
496              
497             sub _remove {
498 2     2   6 my ($self, $key) = @_;
499              
500 2         14 return delete $self->_objects->{$key};
501             }
502              
503             =head2 create
504              
505             my $object = $depot->create( $key, %extra_args );
506              
507             Gathers arguments from L</arguments> and then calls L</constructor>
508             on them, returning a new object. Extra arguments may be passed and
509             they will take precedence.
510              
511             =cut
512              
513             sub create {
514 3     3 1 15 my $self = shift;
515              
516 3         8 my $key = $self->_process_key_arg( \@_ );
517              
518 3         10 my $extra_args = $self->_normalize_args( \@_ );
519              
520 3         8 return $self->_create( $key, $extra_args );
521             }
522              
523             sub _create {
524 14     14   39 my ($self, $key, $extra_args) = @_;
525              
526 14         40 my $args = $self->_arguments( $key, $extra_args );
527              
528 14         254 my $object = $self->constructor->( $args );
529              
530 14 0 0     3851 croakf(
    50          
531             'Constructor returned an invalid value, %s, for key %s: %s',
532             defined($object) ? (ref($object) || qq["$object"]) : 'UNDEF',
533             qq["$key"],
534             $self->type->get_message( $object ),
535             ) if !$self->type->check( $object );
536              
537 14         4952 return $object;
538             }
539              
540             =head2 arguments
541              
542             my $args = $depot->arguments( $key, %extra_args );
543              
544             This method returns an arguments hash ref that would be used to
545             instantiate a new L</class> object. You could, for example, use this
546             to produce a base-line set of arguments, then sprinkle in some more,
547             and make yourself a special mock object to be injected.
548              
549             =cut
550              
551             sub arguments {
552 3     3 1 12 my $self = shift;
553              
554 3         15 my $key = $self->_process_key_arg( \@_ );
555              
556 3         9 my $extra_args = $self->_normalize_args( \@_ );
557              
558 3         10 return $self->_arguments( $key, $extra_args );
559             }
560              
561             sub _arguments {
562 17     17   35 my ($self, $key, $extra_args) = @_;
563              
564             my $args = {
565 17         377 %{ $self->default_arguments() },
566 17 100       30 %{ $self->_key_args->{$key} || {} },
  17         416  
567             %$extra_args,
568             };
569              
570 17 100       86 $args->{ $self->key_argument() } = $key
571             if $self->_has_key_argument();
572              
573 17         52 return $args;
574             }
575              
576             =head2 declared_keys
577              
578             my $keys = $depot->declared_keys();
579             foreach my $key (@$keys) { ... }
580              
581             Returns an array ref containing all the keys declared with
582             L</add_key>.
583              
584             =cut
585              
586             sub declared_keys {
587 0     0 1 0 my $self = shift;
588 0         0 return [ keys %{ $self->_key_args() } ];
  0         0  
589             }
590              
591             =head2 inject
592              
593             $depot->inject( $key, $object );
594              
595             Takes an object of your making and forces L</fetch> to return the
596             injected object. This is useful for injecting mock objects in tests.
597              
598             The injected object must validate against L</type>.
599              
600             =cut
601              
602             sub inject {
603 3     3 1 6 my $self = shift;
604              
605 3         10 my $key = $self->_process_key_arg( \@_ );
606 3 50       12 croak 'Too many arguments passed to inject()' if @_>1;
607 3 50       47 croak 'Not enough arguments passed to inject()' if @_<1;
608              
609 3         8 my $object = shift;
610 3 50       75 croakf(
611             'Invalid object passed to inject(): %s',
612             $self->type->get_message( $object ),
613             ) if !$self->type->check( $object );
614              
615             croak qq[Already injected key, "$key", passed to inject()]
616 3 50       65 if exists $self->_injections->{$key};
617              
618 3         10 $self->_injections->{$key} = $object;
619              
620 3         7 return;
621             }
622              
623             =head2 inject_with_guard
624              
625             my $guard = $depot->inject_with_guard( $key => $object );
626              
627             This is just like L</inject> except it returns a L<Guard> object
628             which, when it leaves scope and is destroyed, will automatically
629             call L</clear_injection>.
630              
631             =cut
632              
633             sub inject_with_guard {
634 1     1 1 3 my $self = shift;
635              
636 1         4 my $key = $self->_process_key_arg( \@_ );
637              
638 1         6 $self->inject( $key, @_ );
639              
640             return guard {
641 1     1   5 return $self->clear_injection( $key );
642 1         21 };
643             }
644              
645             =head2 clear_injection
646              
647             my $object = $depot->clear_injection( $key );
648              
649             Removes and returns the injected object, restoring the original
650             behavior of L</fetch>.
651              
652             =cut
653              
654             sub clear_injection {
655 3     3 1 7 my $self = shift;
656              
657 3         8 my $key = $self->_process_key_arg( \@_ );
658 3 50       29 croak 'Too many arguments passed to clear_injection()' if @_;
659              
660 3         19 return delete $self->_injections->{$key};
661             }
662              
663             =head2 injection
664              
665             my $object = $depot->injection( $key );
666              
667             Returns the injected object, or C<undef> if none has been injected.
668              
669             =cut
670              
671             sub injection {
672 9     9 1 28 my $self = shift;
673              
674 9         23 my $key = $self->_process_key_arg( \@_ );
675 9 50       20 croak 'Too many arguments passed to injection()' if @_;
676              
677 9         49 return $self->_injections->{ $key };
678             }
679              
680             =head2 has_injection
681              
682             if ($depot->has_injection( $key )) { ... }
683              
684             Returns true if an injection is in place for the specified key.
685              
686             =cut
687              
688             sub has_injection {
689 0     0 1 0 my $self = shift;
690              
691 0         0 my $key = $self->_process_key_arg( \@_ );
692 0 0       0 croak 'Too many arguments passed to has_injection()' if @_;
693              
694 0 0       0 return exists($self->_injections->{$key}) ? 1 : 0;
695             }
696              
697             =head2 add_key
698              
699             $depot->add_key( $key, %arguments );
700              
701             Declares a new key and, optionally, the arguments used to construct
702             the L</class> object.
703              
704             Arguments are optional, but if present they will be saved and used
705             by L</fetch> when calling C<new()> (via L</arguments>) on L</class>.
706              
707             =cut
708              
709             sub add_key {
710 6     6 1 548 my ($self, $key, @args) = @_;
711              
712 6 0       24 croakf(
    50          
713             'Invalid key, %s, passed to add_key(): %s',
714             defined($key) ? qq["$key"] : 'UNDEF',
715             NonEmptySimpleStr->get_message( $key ),
716             ) if !NonEmptySimpleStr->check( $key );
717              
718             croak "Already declared key, \"$key\", passed to add_key()"
719 6 50       172 if exists $self->_key_args->{$key};
720              
721 6         20 $self->_key_args->{$key} = $self->_normalize_args( \@args );
722              
723 6         16 return;
724             }
725              
726             =head2 alias_key
727              
728             $depot->alias_key( $alias_key => $real_key );
729              
730             Adds a key that is an alias to another key.
731              
732             =cut
733              
734             sub alias_key {
735 1     1 1 6 my ($self, $alias, $key) = @_;
736              
737 1 0       9 croakf(
    50          
738             'Invalid alias, %s, passed to alias_key(): %s',
739             defined($alias) ? qq["$alias"] : 'UNDEF',
740             NonEmptySimpleStr->get_message( $alias ),
741             ) if !NonEmptySimpleStr->check( $alias );
742              
743 1 0       14 croakf(
    50          
744             'Invalid key, %s, passed to alias_key(): %s',
745             defined($key) ? qq["$key"] : 'UNDEF',
746             NonEmptySimpleStr->get_message( $key ),
747             ) if !NonEmptySimpleStr->check( $key );
748              
749             croak "Already declared alias, \"$alias\", passed to alias_key()"
750 1 50       15 if exists $self->_aliases->{$alias};
751              
752             croak "Undeclared key, \"$key\", passed to alias_key()"
753 1 50 33     7 if $self->strict_keys() and !exists $self->_key_args->{$key};
754              
755 1         4 $self->_aliases->{$alias} = $key;
756              
757 1         3 return;
758             }
759              
760             1;
761             __END__
762              
763             =head1 SUPPORT
764              
765             Please submit bugs and feature requests to the
766             Object-Depot GitHub issue tracker:
767              
768             L<https://github.com/bluefeet/Object-Depot/issues>
769              
770             =head1 ACKNOWLEDGEMENTS
771              
772             Thanks to L<ZipRecruiter|https://www.ziprecruiter.com/> for
773             encouraging their employees to contribute back to the open source
774             ecosystem. Without their dedication to quality software development
775             this distribution would not exist.
776              
777             =head1 AUTHOR
778              
779             Aran Clary Deltac <bluefeet@gmail.com>
780              
781             =head1 COPYRIGHT AND LICENSE
782              
783             Copyright (C) 2020 Aran Clary Deltac
784              
785             This program is free software: you can redistribute it and/or modify
786             it under the terms of the GNU General Public License as published by
787             the Free Software Foundation, either version 3 of the License, or
788             (at your option) any later version.
789              
790             This program is distributed in the hope that it will be useful,
791             but WITHOUT ANY WARRANTY; without even the implied warranty of
792             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
793             GNU General Public License for more details.
794              
795             You should have received a copy of the GNU General Public License
796             along with this program. If not, see L<http://www.gnu.org/licenses/>.
797              
798             =cut
799