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     24380   470824 use strict;
  107         164  
  107         3217  
4 107     5238   826 use warnings;
  107         160  
  107         4580  
5 107     5040   43021 use utf8;
  107         24206  
  107         526  
6 107     4289   3883 use feature ':5.16';
  107         243  
  107         17356  
7 107     4091   45356 use mro;
  107         58954  
  107         525  
8              
9             # No imports because we get subclassed, a lot!
10 107     4091   3479 use Carp ();
  107         149  
  107         1340  
11 107     346   346 use Scalar::Util ();
  107         138  
  107         1079  
12 107     346   40783 use Mojo::BaseUtil ();
  107         263  
  107         7340  
13              
14             # Role support requires Role::Tiny 2.000001+
15 107     346   522 use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
  107         169  
  107         155  
  107         46603  
  107         446238  
  107         16052  
16              
17             # async/await support requires Future::AsyncAwait 0.52+
18             use constant ASYNC => $ENV{MOJO_NO_ASYNC}
19             ? 0
20 107 50   346   664 : !!(eval { require Future::AsyncAwait; Future::AsyncAwait->VERSION('0.52'); 1 });
  107         130  
  107         375  
  107         42857  
  0         0  
  0         0  
21              
22             # Protect subclasses using AUTOLOAD
23       239     sub DESTROY { }
24              
25             sub attr {
26 11550     11789 1 167178 my ($self, $attrs, $value, %kv) = @_;
27 11550 50 66     49569 return unless (my $class = ref $self || $self) && $attrs;
      33        
28              
29 11550 100 100     25680 Carp::croak 'Default has to be a code reference or constant value' if ref $value && ref $value ne 'CODE';
30 11549 100       18536 Carp::croak 'Unsupported attribute option' if grep { $_ ne 'weak' } keys %kv;
  572         1917  
31              
32             # Weaken
33 11548 100       19438 if ($kv{weak}) {
34 571         695 state %weak_names;
35 571 100       1303 unless ($weak_names{$class}) {
36 569         1305 my $names = $weak_names{$class} = [];
37             my $sub = sub {
38 4240     4479   346688 my $self = shift->next::method(@_);
        1949915      
        1664361      
        1718327      
        11061      
        11061      
        11061      
        11061      
        11061      
        11061      
39 4240   66     17148 ref $self->{$_} and Scalar::Util::weaken $self->{$_} for @$names;
40 4240         8927 return $self;
41 569         2153 };
42 569         2113 Mojo::BaseUtil::monkey_patch(my $base = $class . '::_Base', 'new', $sub);
43 107     346   475 no strict 'refs';
  107         127  
  107         109769  
44 569         746 unshift @{"${class}::ISA"}, $base;
  569         7139  
45             }
46 571 100       1280 push @{$weak_names{$class}}, ref $attrs eq 'ARRAY' ? @$attrs : $attrs;
  571         1979  
47             }
48              
49 11548 100       11630 for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
  11548         32474  
50 15212 100       40647 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         14103 my $sub;
54 15211 100       26656 if ($kv{weak}) {
    100          
    100          
55 626 100       973 if (ref $value) {
56             $sub = sub {
57             return
58             exists $_[0]{$attr}
59             ? $_[0]{$attr}
60 33909 100 66 34148   133560 : (ref($_[0]{$attr} = $value->($_[0])) && Scalar::Util::weaken($_[0]{$attr}), $_[0]{$attr})
    100   1671495      
        1655454      
        1659957      
        11061      
        11061      
        10994      
        10994      
        10994      
        10994      
        10994      
61             if @_ == 1;
62 1287 100       3540 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
63 1287         3566 $_[0];
64 459         1576 };
65             }
66             else {
67             $sub = sub {
68 33422 100   54174   100235 return $_[0]{$attr} if @_ == 1;
        1266148      
        1425932      
        10994      
        10889      
        10680      
69 1031 100       2781 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
70 1031         1612 $_[0];
71 167         563 };
72             }
73             }
74             elsif (ref $value) {
75             $sub = sub {
76 427802 100   495372   1296285 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
    100   846810      
        1227041      
        1610122      
        2035804      
        1421967      
        1423314      
        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 30481         42647 $_[0]{$attr} = $_[1];
78 30481         53337 $_[0];
79 6768         17705 };
80             }
81             elsif (defined $value) {
82             $sub = sub {
83 72949 100   2029736   285186 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value) if @_ == 1;
    100   2090618      
        881862      
        860343      
        11061      
84 7662         17752 $_[0]{$attr} = $_[1];
85 7662         15625 $_[0];
86 3309         10101 };
87             }
88             else {
89 4508 100   2271660   11825 $sub = sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
  196305     2401222   2669982  
  39613     2560973   99469  
  39613     2161851   97785  
        2585387      
        843699      
        311513      
        324984      
        79822      
        13890      
90             }
91 15211         25551 Mojo::BaseUtil::monkey_patch($class, $attr, $sub);
92             }
93             }
94              
95             sub import {
96 11167     2382116   292823 my ($class, $caller) = (shift, caller);
97 11167 100       3557730 return unless my @flags = @_;
98              
99             # Mojo modules are strict!
100 5731         170867 $_->import for qw(strict warnings utf8);
101 5731         460497 feature->import(':5.16');
102              
103 5731         17699 while (my $flag = shift @flags) {
104              
105             # Base
106 7863 100       5136568 if ($flag eq '-base') { push @flags, $class }
  2125 100       5333  
    50          
    100          
    100          
    100          
107              
108             # Role
109             elsif ($flag eq '-role') {
110 2         3 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
111 2     2322745   9 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  1         143048  
112 2 50       225 eval "package $caller; use Role::Tiny; 1" or die $@;
  1         8  
  1         1  
  1         4  
  1         18  
  1         2  
  1         2  
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         3821 require experimental;
126 6         15715 experimental->import($_) for qw(signatures postderef);
127             }
128              
129             # Module
130             elsif ($flag !~ /^-/) {
131 107     346   717 no strict 'refs';
  107         133  
  107         58978  
132 4385 100       33202 require(Mojo::BaseUtil::class_to_path($flag)) unless $flag->can('new');
133 4385         7060 push @{"${caller}::ISA"}, $flag;
  4385         43474  
134 4385     2189907   24436 Mojo::BaseUtil::monkey_patch($caller, 'has', sub { attr($caller, @_) });
  11542     2664889   424971  
        1947822      
        2169001      
        2173061      
        1670294      
        11110      
        11061      
        11061      
        11061      
135             }
136              
137 1         214 elsif ($flag ne '-strict') { Carp::croak "Unsupported flag: $flag" }
138             }
139             }
140              
141             sub new {
142 57496     1655671 1 2604108 my $class = shift;
143 57496 100 66     280216 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  409 100       3866  
144             }
145              
146             sub tap {
147 22     1574293 1 47 my ($self, $cb) = (shift, shift);
148 22         121 $_->$cb(@_) for $self;
149 22         112 return $self;
150             }
151              
152             sub with_roles {
153 13     1600192 1 18689 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
154 13         27 my ($self, @roles) = @_;
155 13 100       30 return $self unless @roles;
156              
157 11 100       25 return Role::Tiny->create_class_with_roles($self, map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
  14 100       89  
158             unless my $class = Scalar::Util::blessed $self;
159              
160 1 100       2 return Role::Tiny->apply_roles_to_object($self, map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles);
  2         14  
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