File Coverage

blib/lib/Mojo/Base.pm
Criterion Covered Total %
statement 124 129 96.1
branch 73 78 93.5
condition 12 18 66.6
subroutine 105 105 100.0
pod 4 4 100.0
total 318 334 95.2


line stmt bran cond sub pod time code
1             package Mojo::Base;
2              
3 107     24379   658840 use strict;
  107         212  
  107         4166  
4 107     5237   1147 use warnings;
  107         217  
  107         6451  
5 107     5039   59845 use utf8;
  107         33569  
  107         621  
6 107     4289   5565 use feature ':5.16';
  107         286  
  107         22337  
7 107     4091   78340 use mro;
  107         80873  
  107         622  
8              
9             # No imports because we get subclassed, a lot!
10 107     4091   4412 use Carp ();
  107         222  
  107         2073  
11 107     346   502 use Scalar::Util ();
  107         180  
  107         1607  
12 107     346   55394 use Mojo::BaseUtil ();
  107         331  
  107         9266  
13              
14             # Role support requires Role::Tiny 2.000001+
15 107     346   799 use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
  107         215  
  107         180  
  107         72896  
  107         651540  
  107         21221  
16              
17             # async/await support requires Future::AsyncAwait 0.52+
18             use constant ASYNC => $ENV{MOJO_NO_ASYNC}
19             ? 0
20 107 50   346   969 : !!(eval { require Future::AsyncAwait; Future::AsyncAwait->VERSION('0.52'); 1 });
  107         189  
  107         500  
  107         65141  
  0         0  
  0         0  
21              
22             # Protect subclasses using AUTOLOAD
23       239     sub DESTROY { }
24              
25             sub attr {
26 11550     11789 1 298170 my ($self, $attrs, $value, %kv) = @_;
27 11550 50 66     76238 return unless (my $class = ref $self || $self) && $attrs;
      33        
28              
29 11550 100 100     41792 Carp::croak 'Default has to be a code reference or constant value' if ref $value && ref $value ne 'CODE';
30 11549 100       29659 Carp::croak 'Unsupported attribute option' if grep { $_ ne 'weak' } keys %kv;
  572         2941  
31              
32             # Weaken
33 11548 100       29825 if ($kv{weak}) {
34 571         1041 state %weak_names;
35 571 100       2098 unless ($weak_names{$class}) {
36 569         2055 my $names = $weak_names{$class} = [];
37             my $sub = sub {
38 4239     4478   538371 my $self = shift->next::method(@_);
        1947251      
        1662240      
        1716200      
        11061      
        11061      
        11061      
        11061      
        11061      
        11061      
39 4239   66     29610 ref $self->{$_} and Scalar::Util::weaken $self->{$_} for @$names;
40 4239         18811 return $self;
41 569         5188 };
42 569         3257 Mojo::BaseUtil::monkey_patch(my $base = $class . '::_Base', 'new', $sub);
43 107     346   704 no strict 'refs';
  107         220  
  107         180067  
44 569         1358 unshift @{"${class}::ISA"}, $base;
  569         11130  
45             }
46 571 100       1907 push @{$weak_names{$class}}, ref $attrs eq 'ARRAY' ? @$attrs : $attrs;
  571         3015  
47             }
48              
49 11548 100       18106 for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
  11548         48414  
50 15212 100       69861 Carp::croak qq{Attribute "$attr" invalid} unless $attr =~ /^[a-zA-Z_]\w*$/;
51              
52             # Very performance-sensitive code with lots of micro-optimizations
53 15211         22464 my $sub;
54 15211 100       45807 if ($kv{weak}) {
    100          
    100          
55 626 100       1664 if (ref $value) {
56             $sub = sub {
57             return
58             exists $_[0]{$attr}
59             ? $_[0]{$attr}
60 27794 100 66 28033   192680 : (ref($_[0]{$attr} = $value->($_[0])) && Scalar::Util::weaken($_[0]{$attr}), $_[0]{$attr})
    100   1669361      
        1653334      
        1657837      
        11061      
        11061      
        10994      
        10994      
        10994      
        10994      
        10994      
61             if @_ == 1;
62 1286 100       5205 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
63 1286         5340 $_[0];
64 459         2711 };
65             }
66             else {
67             $sub = sub {
68 33423 100   54146   173291 return $_[0]{$attr} if @_ == 1;
        1264604      
        1424268      
        10994      
        10889      
        10680      
69 1031 100       4074 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
70 1031         2513 $_[0];
71 167         911 };
72             }
73             }
74             elsif (ref $value) {
75             $sub = sub {
76 427123 100   488579   2225110 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
    100   839521      
        1219166      
        1601654      
        2026747      
        1420203      
        1421661      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10680      
        10485      
        10485      
        10485      
        10485      
        10485      
        10485      
        7560      
        3798      
        2413      
77 30409         78971 $_[0]{$attr} = $_[1];
78 30409         82855 $_[0];
79 6768         34383 };
80             }
81             elsif (defined $value) {
82             $sub = sub {
83 72902 100   2020644   439749 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value) if @_ == 1;
    100   2087503      
        880915      
        859396      
        11061      
84 7658         26771 $_[0]{$attr} = $_[1];
85 7658         24458 $_[0];
86 3309         16240 };
87             }
88             else {
89 4508 100   2268401   21559 $sub = sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
  196066     2397919   4370629  
  39589     2557541   160523  
  39589     2159008   145121  
        2582275      
        842750      
        311294      
        324655      
        79777      
        13890      
90             }
91 15211         41411 Mojo::BaseUtil::monkey_patch($class, $attr, $sub);
92             }
93             }
94              
95             sub import {
96 11165     2379120   392116 my ($class, $caller) = (shift, caller);
97 11165 100       4799045 return unless my @flags = @_;
98              
99             # Mojo modules are strict!
100 5729         263430 $_->import for qw(strict warnings utf8);
101 5729         723112 feature->import(':5.16');
102              
103 5729         29639 while (my $flag = shift @flags) {
104              
105             # Base
106 7861 100       9284431 if ($flag eq '-base') { push @flags, $class }
  2125 100       8264  
    50          
    100          
    100          
    100          
107              
108             # Role
109             elsif ($flag eq '-role') {
110 2         2 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
111 2     2319784   12 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  1         213140  
112 2 50       217 eval "package $caller; use Role::Tiny; 1" or die $@;
  1         7  
  1         1  
  1         4  
  1         5  
  1         2  
  1         3  
113             }
114              
115             # async/await
116             elsif ($flag eq '-async_await') {
117 0         0 Carp::croak 'Future::AsyncAwait 0.52+ is required for async/await' unless ASYNC;
118 0         0 require Mojo::Promise;
119 0         0 Future::AsyncAwait->import_into($caller, future_class => 'Mojo::Promise');
120             }
121              
122             # Signatures (Perl 5.20+)
123             elsif ($flag eq '-signatures') {
124 6 50       27 Carp::croak 'Subroutine signatures require Perl 5.20+' if $] < 5.020;
125 6         3217 require experimental;
126 6         22655 experimental->import($_) for qw(signatures postderef);
127             }
128              
129             # Module
130             elsif ($flag !~ /^-/) {
131 107     346   953 no strict 'refs';
  107         214  
  107         90652  
132 4385 100       49747 require(Mojo::BaseUtil::class_to_path($flag)) unless $flag->can('new');
133 4385         10903 push @{"${caller}::ISA"}, $flag;
  4385         64942  
134 4385     2187075   34962 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  11542     2661302   893972  
        1945491      
        2166191      
        2170269      
        1668218      
        11110      
        11061      
        11061      
        11061      
135             }
136              
137 1         270 elsif ($flag ne '-strict') { Carp::croak "Unsupported flag: $flag" }
138             }
139             }
140              
141             sub new {
142 57415     1653504 1 4107728 my $class = shift;
143 57415 100 66     433608 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  405 100       5957  
144             }
145              
146             sub tap {
147 22     1572218 1 83 my ($self, $cb) = (shift, shift);
148 22         100 $_->$cb(@_) for $self;
149 22         166 return $self;
150             }
151              
152             sub with_roles {
153 13     1598109 1 26684 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
154 13         41 my ($self, @roles) = @_;
155 13 100       40 return $self unless @roles;
156              
157 11 100       49 return Role::Tiny->create_class_with_roles($self, map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
  14 100       104  
158             unless my $class = Scalar::Util::blessed $self;
159              
160 1 100       4 return Role::Tiny->apply_roles_to_object($self, map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles);
  2         19  
161             }
162              
163             1;
164              
165             =encoding utf8
166              
167             =head1 NAME
168              
169             Mojo::Base - Minimal base class for Mojo projects
170              
171             =head1 SYNOPSIS
172              
173             package Cat;
174             use Mojo::Base -base;
175              
176             has name => 'Nyan';
177             has ['age', 'weight'] => 4;
178              
179             package Tiger;
180             use Mojo::Base 'Cat';
181              
182             has friend => sub { Cat->new };
183             has stripes => 42;
184              
185             package main;
186             use Mojo::Base -strict;
187              
188             my $mew = Cat->new(name => 'Longcat');
189             say $mew->age;
190             say $mew->age(3)->weight(5)->age;
191              
192             my $rawr = Tiger->new(stripes => 38, weight => 250);
193             say $rawr->tap(sub { $_->friend->name('Tacgnol') })->weight;
194              
195             =head1 DESCRIPTION
196              
197             L is a simple base class for L projects with fluent interfaces.
198              
199             # Automatically enables "strict", "warnings", "utf8" and Perl 5.16 features
200             use Mojo::Base -strict;
201             use Mojo::Base -base;
202             use Mojo::Base 'SomeBaseClass';
203             use Mojo::Base -role;
204              
205             All four forms save a lot of typing. Note that role support depends on L (2.000001+).
206              
207             # use Mojo::Base -strict;
208             use strict;
209             use warnings;
210             use utf8;
211             use feature ':5.16';
212             use mro;
213              
214             # use Mojo::Base -base;
215             use strict;
216             use warnings;
217             use utf8;
218             use feature ':5.16';
219             use mro;
220             push @ISA, 'Mojo::Base';
221             sub has { Mojo::Base::attr(__PACKAGE__, @_) }
222              
223             # use Mojo::Base 'SomeBaseClass';
224             use strict;
225             use warnings;
226             use utf8;
227             use feature ':5.16';
228             use mro;
229             require SomeBaseClass;
230             push @ISA, 'SomeBaseClass';
231             sub has { Mojo::Base::attr(__PACKAGE__, @_) }
232              
233             # use Mojo::Base -role;
234             use strict;
235             use warnings;
236             use utf8;
237             use feature ':5.16';
238             use mro;
239             use Role::Tiny;
240             sub has { Mojo::Base::attr(__PACKAGE__, @_) }
241              
242             On Perl 5.20+ you can also use the C<-signatures> flag with all four forms and enable support for L
243             signatures|perlsub/"Signatures">.
244              
245             # Also enable signatures
246             use Mojo::Base -strict, -signatures;
247             use Mojo::Base -base, -signatures;
248             use Mojo::Base 'SomeBaseClass', -signatures;
249             use Mojo::Base -role, -signatures;
250              
251             If you have L 0.52+ installed you can also use the C<-async_await> flag to activate the C
252             and C keywords to deal much more efficiently with promises.
253              
254             # Also enable async/await
255             use Mojo::Base -strict, -async_await;
256             use Mojo::Base -base, -signatures, -async_await;
257              
258             This will also disable experimental warnings on versions of Perl where this feature was still experimental.
259              
260             =head1 FLUENT INTERFACES
261              
262             Fluent interfaces are a way to design object-oriented APIs around method chaining to create domain-specific languages,
263             with the goal of making the readability of the source code close to written prose.
264              
265             package Duck;
266             use Mojo::Base -base, -signatures;
267              
268             has 'name';
269              
270             sub quack ($self) {
271             my $name = $self->name;
272             say "$name: Quack!"
273             }
274              
275             L will help you with this by having all attribute accessors created with L (or L) return
276             their invocant (C<$self>) whenever they are used to assign a new attribute value.
277              
278             Duck->new->name('Donald')->quack;
279              
280             In this case the C attribute accessor is called on the object created by Cnew>. It assigns a new
281             attribute value and then returns the C object, so the C method can be called on it afterwards. These
282             method chains can continue until one of the methods called does not return the C object.
283              
284             =head1 FUNCTIONS
285              
286             L implements the following functions, which can be imported with the C<-base> flag or by setting a base
287             class.
288              
289             =head2 has
290              
291             has 'name';
292             has ['name1', 'name2', 'name3'];
293             has name => 'foo';
294             has name => sub {...};
295             has ['name1', 'name2', 'name3'] => 'foo';
296             has ['name1', 'name2', 'name3'] => sub {...};
297             has name => sub {...}, weak => 1;
298             has name => undef, weak => 1;
299             has ['name1', 'name2', 'name3'] => sub {...}, weak => 1;
300              
301             Create attributes for hash-based objects, just like the L method.
302              
303             =head1 METHODS
304              
305             L implements the following methods.
306              
307             =head2 attr
308              
309             $object->attr('name');
310             SubClass->attr('name');
311             SubClass->attr(['name1', 'name2', 'name3']);
312             SubClass->attr(name => 'foo');
313             SubClass->attr(name => sub {...});
314             SubClass->attr(['name1', 'name2', 'name3'] => 'foo');
315             SubClass->attr(['name1', 'name2', 'name3'] => sub {...});
316             SubClass->attr(name => sub {...}, weak => 1);
317             SubClass->attr(name => undef, weak => 1);
318             SubClass->attr(['name1', 'name2', 'name3'] => sub {...}, weak => 1);
319              
320             Create attribute accessors for hash-based objects, an array reference can be used to create more than one at a time.
321             Pass an optional second argument to set a default value, it should be a constant or a callback. The callback will be
322             executed at accessor read time if there's no set value, and gets passed the current instance of the object as first
323             argument. Accessors can be chained, that means they return their invocant when they are called with an argument.
324              
325             These options are currently available:
326              
327             =over 2
328              
329             =item weak
330              
331             weak => $bool
332              
333             Weaken attribute reference to avoid L and memory leaks.
334              
335             =back
336              
337             =head2 new
338              
339             my $object = SubClass->new;
340             my $object = SubClass->new(name => 'value');
341             my $object = SubClass->new({name => 'value'});
342              
343             This base class provides a basic constructor for hash-based objects. You can pass it either a hash or a hash reference
344             with attribute values.
345              
346             =head2 tap
347              
348             $object = $object->tap(sub {...});
349             $object = $object->tap('some_method');
350             $object = $object->tap('some_method', @args);
351              
352             Tap into a method chain to perform operations on an object within the chain (also known as a K combinator or Kestrel).
353             The object will be the first argument passed to the callback, and is also available as C<$_>. The callback's return
354             value will be ignored; instead, the object (the callback's first argument) will be the return value. In this way,
355             arbitrary code can be used within (i.e., spliced or tapped into) a chained set of object method calls.
356              
357             # Longer version
358             $object = $object->tap(sub { $_->some_method(@args) });
359              
360             # Inject side effects into a method chain
361             $object->foo('A')->tap(sub { say $_->foo })->foo('B');
362              
363             =head2 with_roles
364              
365             my $new_class = SubClass->with_roles('SubClass::Role::One');
366             my $new_class = SubClass->with_roles('+One', '+Two');
367             $object = $object->with_roles('+One', '+Two');
368              
369             Create a new class with one or more L roles. If called on a class returns the new class, or if called on an
370             object reblesses the object into the new class. For roles following the naming scheme C you
371             can use the shorthand C<+RoleName>. Note that role support depends on L (2.000001+).
372              
373             # Create a new class with the role "SubClass::Role::Foo" and instantiate it
374             my $new_class = SubClass->with_roles('+Foo');
375             my $object = $new_class->new;
376              
377             =head1 SEE ALSO
378              
379             L, L, L.
380              
381             =cut