File Coverage

blib/lib/Mojo/Base.pm
Criterion Covered Total %
statement 121 126 96.0
branch 73 78 93.5
condition 12 18 66.6
subroutine 104 104 100.0
pod 4 4 100.0
total 314 330 95.1


line stmt bran cond sub pod time code
1             package Mojo::Base;
2              
3 102     24282   43124 use strict;
  102         835  
  102         3160  
4 102     4994   508 use warnings;
  102         198  
  102         2536  
5 102     4796   63158 use utf8;
  102         1471  
  102         573  
6 102     4047   4641 use feature ':5.16';
  102         215  
  102         54962  
7 102     3849   57620 use mro;
  102         86268  
  102         441  
8              
9             # No imports because we get subclassed, a lot!
10 102     3849   7761 use Carp ();
  102         2028  
  102         3719  
11 102     341   541 use Scalar::Util ();
  102         189  
  102         15919  
12              
13             # Defer to runtime so Mojo::Util can use "-strict"
14             require Mojo::Util;
15              
16             # Role support requires Role::Tiny 2.000001+
17 102     341   681 use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
  102         184  
  102         170  
  102         62127  
  102         518722  
  102         31237  
18              
19             # async/await support requires Future::AsyncAwait 0.52+
20             use constant ASYNC => $ENV{MOJO_NO_ASYNC}
21             ? 0
22 102 50   341   797 : !!(eval { require Future::AsyncAwait; Future::AsyncAwait->VERSION('0.52'); 1 });
  102         5797  
  102         542  
  102         53759  
  0         0  
  0         0  
23              
24             # Protect subclasses using AUTOLOAD
25       239     sub DESTROY { }
26              
27             sub attr {
28 10958     11197 1 29701 my ($self, $attrs, $value, %kv) = @_;
29 10958 50 66     55884 return unless (my $class = ref $self || $self) && $attrs;
      33        
30              
31 10958 100 100     31865 Carp::croak 'Default has to be a code reference or constant value' if ref $value && ref $value ne 'CODE';
32 10957 100       24670 Carp::croak 'Unsupported attribute option' if grep { $_ ne 'weak' } keys %kv;
  545         2538  
33              
34             # Weaken
35 10956 100       23376 if ($kv{weak}) {
36 544         827 state %weak_names;
37 544 100       1775 unless ($weak_names{$class}) {
38 542         1736 my $names = $weak_names{$class} = [];
39             my $sub = sub {
40 4001     4240   120730 my $self = shift->next::method(@_);
        1861097      
        1598654      
        1622318      
        10470      
        10470      
        10470      
        10470      
        10470      
        10470      
41 4001   66     23367 ref $self->{$_} and Scalar::Util::weaken $self->{$_} for @$names;
42 4001         12401 return $self;
43 542         2372 };
44 542         2725 Mojo::Util::monkey_patch(my $base = $class . '::_Base', 'new', $sub);
45 102     341   636 no strict 'refs';
  102         198  
  102         136358  
46 542         1266 unshift @{"${class}::ISA"}, $base;
  542         8814  
47             }
48 544 100       1605 push @{$weak_names{$class}}, ref $attrs eq 'ARRAY' ? @$attrs : $attrs;
  544         2368  
49             }
50              
51 10956 100       14643 for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
  10956         33818  
52 14297 100       56545 Carp::croak qq{Attribute "$attr" invalid} unless $attr =~ /^[a-zA-Z_]\w*$/;
53              
54             # Very performance-sensitive code with lots of micro-optimizations
55 14296         19568 my $sub;
56 14296 100       34747 if ($kv{weak}) {
    100          
    100          
57 596 100       1612 if (ref $value) {
58             $sub = sub {
59             return
60             exists $_[0]{$attr}
61             ? $_[0]{$attr}
62 28802 100 66 29041   140826 : (ref($_[0]{$attr} = $value->($_[0])) && Scalar::Util::weaken($_[0]{$attr}), $_[0]{$attr})
    100   1590139      
        1574761      
        1578156      
        10470      
        10470      
        10403      
        10403      
        10403      
        10403      
        10403      
63             if @_ == 1;
64 1173 100       5564 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
65 1173         3976 $_[0];
66 438         2231 };
67             }
68             else {
69             $sub = sub {
70 31857 100   50937   126618 return $_[0]{$attr} if @_ == 1;
        1209001      
        1357842      
        10403      
        10298      
        10089      
71 1018 100       5342 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
72 1018         2218 $_[0];
73 158         860 };
74             }
75             }
76             elsif (ref $value) {
77             $sub = sub {
78 406849 100   467747   1642684 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
    100   799283      
        1159477      
        1522525      
        1927858      
        1216802      
        1359138      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        10089      
        9894      
        9894      
        9894      
        9894      
        9894      
        9894      
        7164      
        3798      
        2413      
79 30095         54999 $_[0]{$attr} = $_[1];
80 30095         71617 $_[0];
81 6418         22557 };
82             }
83             elsif (defined $value) {
84             $sub = sub {
85 69724 100   1921219   347324 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value) if @_ == 1;
    100   1982854      
        845649      
        799196      
        10470      
86 7298         19440 $_[0]{$attr} = $_[1];
87 7298         18534 $_[0];
88 3138         10837 };
89             }
90             else {
91 4144 100   2158561   14145 $sub = sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
  189538     2280558   2856119  
  37497     2434059   107421  
  37497     2055946   122780  
        2466059      
        665423      
        298224      
        311339      
        76094      
        13305      
92             }
93 14296         35025 Mojo::Util::monkey_patch($class, $attr, $sub);
94             }
95             }
96              
97             sub import {
98 10592     2268831   260408 my ($class, $caller) = (shift, caller);
99 10592 100       876765 return unless my @flags = @_;
100              
101             # Mojo modules are strict!
102 5425         97591 $_->import for qw(strict warnings utf8);
103 5425         330700 feature->import(':5.16');
104              
105 5425         20713 while (my $flag = shift @flags) {
106              
107             # Base
108 7456 100       268648 if ($flag eq '-base') { push @flags, $class }
  2024 100       6728  
    50          
    100          
    100          
    100          
109              
110             # Role
111             elsif ($flag eq '-role') {
112 2         3 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
113 2     2210394   15 Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  1         102  
114 2 50       210 eval "package $caller; use Role::Tiny; 1" or die $@;
  1         9  
  1         2  
  1         13  
  1         8  
  1         2  
  1         14  
115             }
116              
117             # async/await
118             elsif ($flag eq '-async_await') {
119 0         0 Carp::croak 'Future::AsyncAwait 0.52+ is required for async/await' unless ASYNC;
120 0         0 require Mojo::Promise;
121 0         0 Future::AsyncAwait->import_into($caller, future_class => 'Mojo::Promise');
122             }
123              
124             # Signatures (Perl 5.20+)
125             elsif ($flag eq '-signatures') {
126 6 50       32 Carp::croak 'Subroutine signatures require Perl 5.20+' if $] < 5.020;
127 6         2809 require experimental;
128 6         18917 experimental->import($_) for qw(signatures postderef);
129             }
130              
131             # Module
132             elsif ($flag !~ /^-/) {
133 102     341   2660 no strict 'refs';
  102         224  
  102         71540  
134 4176 100       35568 require(Mojo::Util::class_to_path($flag)) unless $flag->can('new');
135 4176         8629 push @{"${caller}::ISA"}, $flag;
  4176         48916  
136 4176     2089368   25790 Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  10950     2554212   28768  
        1834999      
        2071097      
        1927328      
        1572844      
        10519      
        10470      
        10470      
        10470      
137             }
138              
139 1         231 elsif ($flag ne '-strict') { Carp::croak "Unsupported flag: $flag" }
140             }
141             }
142              
143             sub new {
144 54404     1570689 1 852991 my $class = shift;
145 54404 100 66     333502 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  389 100       4895  
146             }
147              
148             sub tap {
149 22     1501373 1 92 my ($self, $cb) = (shift, shift);
150 22         110 $_->$cb(@_) for $self;
151 22         163 return $self;
152             }
153              
154             sub with_roles {
155 13     1519082 1 17821 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
156 13         36 my ($self, @roles) = @_;
157 13 100       40 return $self unless @roles;
158              
159 11 100       46 return Role::Tiny->create_class_with_roles($self, map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
  14 100       90  
160             unless my $class = Scalar::Util::blessed $self;
161              
162 1 100       5 return Role::Tiny->apply_roles_to_object($self, map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles);
  2         19  
163             }
164              
165             1;
166              
167             =encoding utf8
168              
169             =head1 NAME
170              
171             Mojo::Base - Minimal base class for Mojo projects
172              
173             =head1 SYNOPSIS
174              
175             package Cat;
176             use Mojo::Base -base;
177              
178             has name => 'Nyan';
179             has ['age', 'weight'] => 4;
180              
181             package Tiger;
182             use Mojo::Base 'Cat';
183              
184             has friend => sub { Cat->new };
185             has stripes => 42;
186              
187             package main;
188             use Mojo::Base -strict;
189              
190             my $mew = Cat->new(name => 'Longcat');
191             say $mew->age;
192             say $mew->age(3)->weight(5)->age;
193              
194             my $rawr = Tiger->new(stripes => 38, weight => 250);
195             say $rawr->tap(sub { $_->friend->name('Tacgnol') })->weight;
196              
197             =head1 DESCRIPTION
198              
199             L is a simple base class for L projects with fluent interfaces.
200              
201             # Automatically enables "strict", "warnings", "utf8" and Perl 5.16 features
202             use Mojo::Base -strict;
203             use Mojo::Base -base;
204             use Mojo::Base 'SomeBaseClass';
205             use Mojo::Base -role;
206              
207             All four forms save a lot of typing. Note that role support depends on L (2.000001+).
208              
209             # use Mojo::Base -strict;
210             use strict;
211             use warnings;
212             use utf8;
213             use feature ':5.16';
214             use mro;
215              
216             # use Mojo::Base -base;
217             use strict;
218             use warnings;
219             use utf8;
220             use feature ':5.16';
221             use mro;
222             push @ISA, 'Mojo::Base';
223             sub has { Mojo::Base::attr(__PACKAGE__, @_) }
224              
225             # use Mojo::Base 'SomeBaseClass';
226             use strict;
227             use warnings;
228             use utf8;
229             use feature ':5.16';
230             use mro;
231             require SomeBaseClass;
232             push @ISA, 'SomeBaseClass';
233             sub has { Mojo::Base::attr(__PACKAGE__, @_) }
234              
235             # use Mojo::Base -role;
236             use strict;
237             use warnings;
238             use utf8;
239             use feature ':5.16';
240             use mro;
241             use Role::Tiny;
242             sub has { Mojo::Base::attr(__PACKAGE__, @_) }
243              
244             On Perl 5.20+ you can also use the C<-signatures> flag with all four forms and enable support for L
245             signatures|perlsub/"Signatures">.
246              
247             # Also enable signatures
248             use Mojo::Base -strict, -signatures;
249             use Mojo::Base -base, -signatures;
250             use Mojo::Base 'SomeBaseClass', -signatures;
251             use Mojo::Base -role, -signatures;
252              
253             If you have L 0.52+ installed you can also use the C<-async_await> flag to activate the C
254             and C keywords to deal much more efficiently with promises. Note that this feature is B and might
255             change without warning!
256              
257             # Also enable async/await
258             use Mojo::Base -strict, -async_await;
259             use Mojo::Base -base, -signatures, -async_await;
260              
261             This will also disable experimental warnings on versions of Perl where this feature was still experimental.
262              
263             =head1 FLUENT INTERFACES
264              
265             Fluent interfaces are a way to design object-oriented APIs around method chaining to create domain-specific languages,
266             with the goal of making the readability of the source code close to written prose.
267              
268             package Duck;
269             use Mojo::Base -base, -signatures;
270              
271             has 'name';
272              
273             sub quack ($self) {
274             my $name = $self->name;
275             say "$name: Quack!"
276             }
277              
278             L will help you with this by having all attribute accessors created with L (or L) return
279             their invocant (C<$self>) whenever they are used to assign a new attribute value.
280              
281             Duck->new->name('Donald')->quack;
282              
283             In this case the C attribute accessor is called on the object created by Cnew>. It assigns a new
284             attribute value and then returns the C object, so the C method can be called on it afterwards. These
285             method chains can continue until one of the methods called does not return the C object.
286              
287             =head1 FUNCTIONS
288              
289             L implements the following functions, which can be imported with the C<-base> flag or by setting a base
290             class.
291              
292             =head2 has
293              
294             has 'name';
295             has ['name1', 'name2', 'name3'];
296             has name => 'foo';
297             has name => sub {...};
298             has ['name1', 'name2', 'name3'] => 'foo';
299             has ['name1', 'name2', 'name3'] => sub {...};
300             has name => sub {...}, weak => 1;
301             has name => undef, weak => 1;
302             has ['name1', 'name2', 'name3'] => sub {...}, weak => 1;
303              
304             Create attributes for hash-based objects, just like the L method.
305              
306             =head1 METHODS
307              
308             L implements the following methods.
309              
310             =head2 attr
311              
312             $object->attr('name');
313             SubClass->attr('name');
314             SubClass->attr(['name1', 'name2', 'name3']);
315             SubClass->attr(name => 'foo');
316             SubClass->attr(name => sub {...});
317             SubClass->attr(['name1', 'name2', 'name3'] => 'foo');
318             SubClass->attr(['name1', 'name2', 'name3'] => sub {...});
319             SubClass->attr(name => sub {...}, weak => 1);
320             SubClass->attr(name => undef, weak => 1);
321             SubClass->attr(['name1', 'name2', 'name3'] => sub {...}, weak => 1);
322              
323             Create attribute accessors for hash-based objects, an array reference can be used to create more than one at a time.
324             Pass an optional second argument to set a default value, it should be a constant or a callback. The callback will be
325             executed at accessor read time if there's no set value, and gets passed the current instance of the object as first
326             argument. Accessors can be chained, that means they return their invocant when they are called with an argument.
327              
328             These options are currently available:
329              
330             =over 2
331              
332             =item weak
333              
334             weak => $bool
335              
336             Weaken attribute reference to avoid L and memory leaks.
337              
338             =back
339              
340             =head2 new
341              
342             my $object = SubClass->new;
343             my $object = SubClass->new(name => 'value');
344             my $object = SubClass->new({name => 'value'});
345              
346             This base class provides a basic constructor for hash-based objects. You can pass it either a hash or a hash reference
347             with attribute values.
348              
349             =head2 tap
350              
351             $object = $object->tap(sub {...});
352             $object = $object->tap('some_method');
353             $object = $object->tap('some_method', @args);
354              
355             Tap into a method chain to perform operations on an object within the chain (also known as a K combinator or Kestrel).
356             The object will be the first argument passed to the callback, and is also available as C<$_>. The callback's return
357             value will be ignored; instead, the object (the callback's first argument) will be the return value. In this way,
358             arbitrary code can be used within (i.e., spliced or tapped into) a chained set of object method calls.
359              
360             # Longer version
361             $object = $object->tap(sub { $_->some_method(@args) });
362              
363             # Inject side effects into a method chain
364             $object->foo('A')->tap(sub { say $_->foo })->foo('B');
365              
366             =head2 with_roles
367              
368             my $new_class = SubClass->with_roles('SubClass::Role::One');
369             my $new_class = SubClass->with_roles('+One', '+Two');
370             $object = $object->with_roles('+One', '+Two');
371              
372             Create a new class with one or more L roles. If called on a class returns the new class, or if called on an
373             object reblesses the object into the new class. For roles following the naming scheme C you
374             can use the shorthand C<+RoleName>. Note that role support depends on L (2.000001+).
375              
376             # Create a new class with the role "SubClass::Role::Foo" and instantiate it
377             my $new_class = SubClass->with_roles('+Foo');
378             my $object = $new_class->new;
379              
380             =head1 SEE ALSO
381              
382             L, L, L.
383              
384             =cut