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     24574   467486 use strict;
  107         159  
  107         3187  
4 107     5251   785 use warnings;
  107         154  
  107         4520  
5 107     5053   42253 use utf8;
  107         23849  
  107         557  
6 107     4302   4049 use feature ':5.16';
  107         224  
  107         16997  
7 107     4104   46298 use mro;
  107         58962  
  107         509  
8              
9             # No imports because we get subclassed, a lot!
10 107     4104   3377 use Carp ();
  107         169  
  107         1277  
11 107     346   338 use Scalar::Util ();
  107         136  
  107         1015  
12 107     346   40769 use Mojo::BaseUtil ();
  107         323  
  107         7075  
13              
14             # Role support requires Role::Tiny 2.000001+
15 107     346   510 use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
  107         141  
  107         146  
  107         46409  
  107         441566  
  107         16229  
16              
17             # async/await support requires Future::AsyncAwait 0.52+
18             use constant ASYNC => $ENV{MOJO_NO_ASYNC}
19             ? 0
20 107 50   346   691 : !!(eval { require Future::AsyncAwait; Future::AsyncAwait->VERSION('0.52'); 1 });
  107         133  
  107         360  
  107         43664  
  0         0  
  0         0  
21              
22             # Protect subclasses using AUTOLOAD
23       239     sub DESTROY { }
24              
25             sub attr {
26 11550     11789 1 173210 my ($self, $attrs, $value, %kv) = @_;
27 11550 50 66     48304 return unless (my $class = ref $self || $self) && $attrs;
      33        
28              
29 11550 100 100     25717 Carp::croak 'Default has to be a code reference or constant value' if ref $value && ref $value ne 'CODE';
30 11549 100       18602 Carp::croak 'Unsupported attribute option' if grep { $_ ne 'weak' } keys %kv;
  572         1867  
31              
32             # Weaken
33 11548 100       19858 if ($kv{weak}) {
34 571         649 state %weak_names;
35 571 100       1329 unless ($weak_names{$class}) {
36 569         1312 my $names = $weak_names{$class} = [];
37             my $sub = sub {
38 4253     4492   332072 my $self = shift->next::method(@_);
        1957881      
        1671511      
        1725925      
        11061      
        11061      
        11061      
        11061      
        11061      
        11061      
39 4253   66     17124 ref $self->{$_} and Scalar::Util::weaken $self->{$_} for @$names;
40 4253         8966 return $self;
41 569         2142 };
42 569         2139 Mojo::BaseUtil::monkey_patch(my $base = $class . '::_Base', 'new', $sub);
43 107     346   502 no strict 'refs';
  107         127  
  107         109223  
44 569         811 unshift @{"${class}::ISA"}, $base;
  569         7119  
45             }
46 571 100       1283 push @{$weak_names{$class}}, ref $attrs eq 'ARRAY' ? @$attrs : $attrs;
  571         2074  
47             }
48              
49 11548 100       11647 for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
  11548         28859  
50 15212 100       41267 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         14265 my $sub;
54 15211 100       28138 if ($kv{weak}) {
    100          
    100          
55 626 100       959 if (ref $value) {
56             $sub = sub {
57             return
58             exists $_[0]{$attr}
59             ? $_[0]{$attr}
60 34561 100 66 34800   135831 : (ref($_[0]{$attr} = $value->($_[0])) && Scalar::Util::weaken($_[0]{$attr}), $_[0]{$attr})
    100   1678945      
        1662591      
        1667136      
        11061      
        11061      
        10994      
        10994      
        10994      
        10994      
        10994      
61             if @_ == 1;
62 1294 100       3571 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
63 1294         3595 $_[0];
64 459         1592 };
65             }
66             else {
67             $sub = sub {
68 33516 100   54358   100368 return $_[0]{$attr} if @_ == 1;
        1271631      
        1431853      
        10994      
        10889      
        10680      
69 1032 100       2601 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
70 1032         1582 $_[0];
71 167         570 };
72             }
73             }
74             elsif (ref $value) {
75             $sub = sub {
76 429572 100   497888   1318575 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
    100   850893      
        1232691      
        1617651      
        2045103      
        1427916      
        1429189      
        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 30648         43608 $_[0]{$attr} = $_[1];
78 30648         53246 $_[0];
79 6768         17939 };
80             }
81             elsif (defined $value) {
82             $sub = sub {
83 73168 100   2038442   282723 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value) if @_ == 1;
    100   2099261      
        885523      
        864004      
        11061      
84 7683         17647 $_[0]{$attr} = $_[1];
85 7683         14531 $_[0];
86 3309         8620 };
87             }
88             else {
89 4508 100   2280910   12072 $sub = sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
  197185     2411416   2668182  
  39746     2571937   98883  
  39746     2171045   100648  
        2596315      
        847060      
        312685      
        326224      
        80005      
        13896      
90             }
91 15211         25047 Mojo::BaseUtil::monkey_patch($class, $attr, $sub);
92             }
93             }
94              
95             sub import {
96 11165     2392298   295325 my ($class, $caller) = (shift, caller);
97 11165 100       3587202 return unless my @flags = @_;
98              
99             # Mojo modules are strict!
100 5729         168353 $_->import for qw(strict warnings utf8);
101 5729         461435 feature->import(':5.16');
102              
103 5729         17906 while (my $flag = shift @flags) {
104              
105             # Base
106 7861 100       5283430 if ($flag eq '-base') { push @flags, $class }
  2125 100       5189  
    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     2332731   10 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  1         151553  
112 2 50       187 eval "package $caller; use Role::Tiny; 1" or die $@;
  1         6  
  1         2  
  1         4  
  1         6  
  1         14  
  1         4  
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       21 Carp::croak 'Subroutine signatures require Perl 5.20+' if $] < 5.020;
125 6         2159 require experimental;
126 6         15057 experimental->import($_) for qw(signatures postderef);
127             }
128              
129             # Module
130             elsif ($flag !~ /^-/) {
131 107     346   736 no strict 'refs';
  107         140  
  107         60631  
132 4385 100       32251 require(Mojo::BaseUtil::class_to_path($flag)) unless $flag->can('new');
133 4385         7098 push @{"${caller}::ISA"}, $flag;
  4385         43912  
134 4385     2199116   24277 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  11542     2676052   424230  
        1956671      
        2178470      
        2182800      
        1677493      
        11110      
        11061      
        11061      
        11061      
135             }
136              
137 1         156 elsif ($flag ne '-strict') { Carp::croak "Unsupported flag: $flag" }
138             }
139             }
140              
141             sub new {
142 57725     1662928 1 2600854 my $class = shift;
143 57725 100 66     276418 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  408 100       4002  
144             }
145              
146             sub tap {
147 22     1581253 1 130 my ($self, $cb) = (shift, shift);
148 22         85 $_->$cb(@_) for $self;
149 22         109 return $self;
150             }
151              
152             sub with_roles {
153 13     1607017 1 19553 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
154 13         23 my ($self, @roles) = @_;
155 13 100       30 return $self unless @roles;
156              
157 11 100       24 return Role::Tiny->create_class_with_roles($self, map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
  14 100       79  
158             unless my $class = Scalar::Util::blessed $self;
159              
160 1 100       3 return Role::Tiny->apply_roles_to_object($self, map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles);
  2         13  
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