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     24561   678175 use strict;
  107         225  
  107         4231  
4 107     5238   1131 use warnings;
  107         204  
  107         6505  
5 107     5040   61103 use utf8;
  107         41252  
  107         635  
6 107     4289   5247 use feature ':5.16';
  107         325  
  107         22538  
7 107     4091   61549 use mro;
  107         83745  
  107         613  
8              
9             # No imports because we get subclassed, a lot!
10 107     4091   4265 use Carp ();
  107         200  
  107         2051  
11 107     346   536 use Scalar::Util ();
  107         205  
  107         1614  
12 107     346   53667 use Mojo::BaseUtil ();
  107         331  
  107         9115  
13              
14             # Role support requires Role::Tiny 2.000001+
15 107     346   746 use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
  107         219  
  107         242  
  107         61194  
  107         630485  
  107         21133  
16              
17             # async/await support requires Future::AsyncAwait 0.52+
18             use constant ASYNC => $ENV{MOJO_NO_ASYNC}
19             ? 0
20 107 50   346   913 : !!(eval { require Future::AsyncAwait; Future::AsyncAwait->VERSION('0.52'); 1 });
  107         240  
  107         507  
  107         62654  
  0         0  
  0         0  
21              
22             # Protect subclasses using AUTOLOAD
23       239     sub DESTROY { }
24              
25             sub attr {
26 11550     11789 1 300273 my ($self, $attrs, $value, %kv) = @_;
27 11550 50 66     74813 return unless (my $class = ref $self || $self) && $attrs;
      33        
28              
29 11550 100 100     39281 Carp::croak 'Default has to be a code reference or constant value' if ref $value && ref $value ne 'CODE';
30 11549 100       28587 Carp::croak 'Unsupported attribute option' if grep { $_ ne 'weak' } keys %kv;
  572         2767  
31              
32             # Weaken
33 11548 100       28982 if ($kv{weak}) {
34 571         955 state %weak_names;
35 571 100       1952 unless ($weak_names{$class}) {
36 569         1816 my $names = $weak_names{$class} = [];
37             my $sub = sub {
38 4240     4479   509238 my $self = shift->next::method(@_);
        1948226      
        1663132      
        1717098      
        11061      
        11061      
        11061      
        11061      
        11061      
        11061      
39 4240   66     25550 ref $self->{$_} and Scalar::Util::weaken $self->{$_} for @$names;
40 4240         13765 return $self;
41 569         2972 };
42 569         2952 Mojo::BaseUtil::monkey_patch(my $base = $class . '::_Base', 'new', $sub);
43 107     346   616 no strict 'refs';
  107         188  
  107         168836  
44 569         1078 unshift @{"${class}::ISA"}, $base;
  569         9359  
45             }
46 571 100       1759 push @{$weak_names{$class}}, ref $attrs eq 'ARRAY' ? @$attrs : $attrs;
  571         2889  
47             }
48              
49 11548 100       17083 for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
  11548         39052  
50 15212 100       64361 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         21266 my $sub;
54 15211 100       45390 if ($kv{weak}) {
    100          
    100          
55 626 100       1414 if (ref $value) {
56             $sub = sub {
57             return
58             exists $_[0]{$attr}
59             ? $_[0]{$attr}
60 29172 100 66 29411   178805 : (ref($_[0]{$attr} = $value->($_[0])) && Scalar::Util::weaken($_[0]{$attr}), $_[0]{$attr})
    100   1670266      
        1654225      
        1658728      
        11061      
        11061      
        10994      
        10994      
        10994      
        10994      
        10994      
61             if @_ == 1;
62 1287 100       5065 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
63 1287         4849 $_[0];
64 459         2349 };
65             }
66             else {
67             $sub = sub {
68 33423 100   54156   156823 return $_[0]{$attr} if @_ == 1;
        1265317      
        1424941      
        10994      
        10889      
        10680      
69 1031 100       4224 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
70 1031         2367 $_[0];
71 167         866 };
72             }
73             }
74             elsif (ref $value) {
75             $sub = sub {
76 427398 100   490232   2056474 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
    100   841290      
        1221129      
        1603817      
        2029107      
        1420783      
        1422142      
        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 30453         65778 $_[0]{$attr} = $_[1];
78 30453         81626 $_[0];
79 6768         29950 };
80             }
81             elsif (defined $value) {
82             $sub = sub {
83 72912 100   2023002   433805 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value) if @_ == 1;
    100   2088578      
        881236      
        859717      
        11061      
84 7658         27153 $_[0]{$attr} = $_[1];
85 7658         22585 $_[0];
86 3309         14535 };
87             }
88             else {
89 4508 100   2269706   17981 $sub = sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
  196204     2399022   3906755  
  39606     2558690   143475  
  39606     2159960   139923  
        2583330      
        843002      
        311362      
        324815      
        79791      
        13896      
90             }
91 15211         40677 Mojo::BaseUtil::monkey_patch($class, $attr, $sub);
92             }
93             }
94              
95             sub import {
96 11165     2380140   430084 my ($class, $caller) = (shift, caller);
97 11165 100       4610953 return unless my @flags = @_;
98              
99             # Mojo modules are strict!
100 5729         234143 $_->import for qw(strict warnings utf8);
101 5729         652312 feature->import(':5.16');
102              
103 5729         27427 while (my $flag = shift @flags) {
104              
105             # Base
106 7861 100       8488759 if ($flag eq '-base') { push @flags, $class }
  2125 100       7670  
    50          
    100          
    100          
    100          
107              
108             # Role
109             elsif ($flag eq '-role') {
110 2         4 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
111 2     2320777   15 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  1         278620  
112 2 50       292 eval "package $caller; use Role::Tiny; 1" or die $@;
  1         8  
  1         2  
  1         4  
  1         50  
  1         3  
  1         6  
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       28 Carp::croak 'Subroutine signatures require Perl 5.20+' if $] < 5.020;
125 6         3233 require experimental;
126 6         24382 experimental->import($_) for qw(signatures postderef);
127             }
128              
129             # Module
130             elsif ($flag !~ /^-/) {
131 107     346   895 no strict 'refs';
  107         193  
  107         90653  
132 4385 100       44019 require(Mojo::BaseUtil::class_to_path($flag)) unless $flag->can('new');
133 4385         10062 push @{"${caller}::ISA"}, $flag;
  4385         59126  
134 4385     2188057   32820 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  11542     2662243   773479  
        1946458      
        2167328      
        2171388      
        1669096      
        11110      
        11061      
        11061      
        11061      
135             }
136              
137 1         153 elsif ($flag ne '-strict') { Carp::croak "Unsupported flag: $flag" }
138             }
139             }
140              
141             sub new {
142 57477     1654460 1 4273635 my $class = shift;
143 57477 100 66     421467 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  408 100       6253  
144             }
145              
146             sub tap {
147 22     1573101 1 80 my ($self, $cb) = (shift, shift);
148 22         110 $_->$cb(@_) for $self;
149 22         167 return $self;
150             }
151              
152             sub with_roles {
153 13     1599000 1 27589 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
154 13         33 my ($self, @roles) = @_;
155 13 100       38 return $self unless @roles;
156              
157 11 100       32 return Role::Tiny->create_class_with_roles($self, map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
  14 100       94  
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         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