File Coverage

blib/lib/Gearman/Driver/Loader.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Gearman::Driver::Loader;
2              
3 1     1   1736 use Moose::Role;
  0            
  0            
4             use Module::Runtime;
5             use Module::Find;
6             use Try::Tiny;
7              
8             =head1 NAME
9              
10             Gearman::Driver::Loader - Loads worker classes
11              
12             =head1 DESCRIPTION
13              
14             This module is responsible for loading worker classes and doing
15             the introspection on them (looking for job method attributes etc).
16             All methods and attributes are internally used in L<Gearman::Driver>
17             but this module (implemented as L<Moose::Role>) might be of
18             interest to use outside of L<Gearman::Driver>.
19              
20             =head1 ATTRIBUTES
21              
22             =head2 namespaces
23              
24             Will be passed to L<Module::Find> C<findallmod> method to load worker
25             modules. Each one of those modules has to be inherited from
26             L<Gearman::Driver::Worker> or a subclass of it. It's also possible
27             to use the full package name to load a single module/file. There is
28             also a method L<get_namespaces|Gearman::Driver/get_namespaces> which
29             returns a sorted list of all namespaces.
30              
31             See also: L</wanted>.
32              
33             =over 4
34              
35             =item * isa: C<ArrayRef>
36              
37             =item * required: C<True>
38              
39             =back
40              
41             =cut
42              
43             has 'namespaces' => (
44             default => sub { [] },
45             documentation => 'Example: --namespaces My::Workers --namespaces My::OtherWorkers',
46             handles => { get_namespaces => 'sort' },
47             is => 'rw',
48             isa => 'ArrayRef[Str]',
49             required => 0,
50             traits => [qw(Array)],
51             );
52              
53             =head2 wanted
54              
55             =over 4
56              
57             =item * isa: C<CodeRef>
58              
59             =item * required: C<False>
60              
61             =back
62              
63             This CodeRef will be called on each of the modules found in your
64             L</namespace>. The first and only parameter to this sub is the name
65             of the module. If a true value is returned, the module will be
66             loaded and checked if it's a valid L<Gearman::Driver::Worker>
67             subclass.
68              
69             Let's say you have a namespace called C<My::Project>:
70              
71             =over 4
72              
73             =item * My::Project::Web
74              
75             =item * My::Project::Web::Controller::Root
76              
77             =item * My::Project::Web::Controller::Admin
78              
79             =item * My::Project::Web::Controller::User
80              
81             =item * My::Project::Web::Model::DBIC
82              
83             =item * My::Project::Worker::ScaleImage
84              
85             =item * My::Project::Worker::RemoveUser
86              
87             =back
88              
89             To avoid every module being loaded and inspected being a
90             L<Gearman::Driver::Worker> subclass you can use C<wanted>
91             to only load classes having C<Worker> in the package name:
92              
93             my $driver = Gearman::Driver->new(
94             interval => 0,
95             namespaces => [qw(My::Project)],
96             wanted => sub {
97             return 1 if /Worker/;
98             return 0;
99             },
100             );
101              
102             This would only load:
103              
104             =over 4
105              
106             =item * My::Project::Worker::ScaleImage
107              
108             =item * My::Project::Worker::RemoveUser
109              
110             =back
111              
112             =cut
113              
114             has 'wanted' => (
115             is => 'rw',
116             isa => 'CodeRef',
117             predicate => 'has_wanted',
118             );
119              
120             =head2 lib
121              
122             This is just for convenience to extend C<@INC> from command line
123             using C<gearman_driver.pl>:
124              
125             gearman_driver.pl --lib ./lib --lib /custom/lib --namespaces My::Workers
126              
127             =over 4
128              
129             =item * isa: C<Str>
130              
131             =back
132              
133             =cut
134              
135             has 'lib' => (
136             default => sub { [] },
137             documentation => 'Example: --lib ./lib --lib /custom/lib',
138             is => 'rw',
139             isa => 'ArrayRef[Str]',
140             );
141              
142             =head2 modules
143              
144             Every worker module loaded by L<Module::Find> will be added to this
145             list. There are also two methods:
146             L<get_modules|Gearman::Driver/get_modules> and
147             L<has_modules|Gearman::Driver/has_modules>.
148              
149             =over 4
150              
151             =item * isa: C<ArrayRef>
152              
153             =item * readonly: C<True>
154              
155             =back
156              
157             =cut
158              
159             has 'modules' => (
160             default => sub { [] },
161             handles => {
162             add_module => 'push',
163             get_modules => 'sort',
164             has_modules => 'count',
165             },
166             is => 'ro',
167             isa => 'ArrayRef[Str]',
168             traits => [qw(Array)],
169             );
170              
171             =head1 METHODS
172              
173             =head2 get_namespaces
174              
175             Returns a sorted list of L<namespaces|Gearman::Driver/namespaces>.
176              
177             =head2 get_modules
178              
179             Returns a sorted list of L<modules|Gearman::Driver/modules>.
180              
181             =head2 has_modules
182              
183             Returns the count of L<modules|Gearman::Driver/modules>.
184              
185             =head2 is_valid_worker_subclass
186              
187             Parameters: C<$package>
188              
189             Checks if the given C<$package> is a valid subclass of
190             L<Gearman::Driver::Worker>.
191              
192             =cut
193              
194             sub is_valid_worker_subclass {
195             my ( $self, $package ) = @_;
196             return 0 unless $package;
197             return 0 unless $package->can('meta');
198             return 0 unless $package->meta->can('linearized_isa');
199             return 0 unless grep $_ eq 'Gearman::Driver::Worker', $package->meta->linearized_isa;
200             return 1;
201             }
202              
203             =head2 has_job_method
204              
205             Parameters: C<$package>
206              
207             Checks if the given C<$package> has a valid job method.
208              
209             =cut
210              
211             sub has_job_method {
212             my ( $self, $package ) = @_;
213             return 0 unless $package;
214             return 0 unless $package->meta->can('get_nearest_methods_with_attributes');
215             foreach my $method ( $package->meta->get_nearest_methods_with_attributes ) {
216             next unless grep $_ eq 'Job', @{ $method->attributes };
217             return 1;
218             }
219             return 0;
220             }
221              
222             =head2 load_namespaces
223              
224             Loops over all L</namespaces> and uses L<findallmod|Module::Find>
225             to generate a list of modules to load. It verifies the module is
226             L</wanted> before it's being loaded using
227             L<Module::Runtime::use_module|Module::Runtime>. After loading
228             L</is_valid_worker_subclass> and L<has_wanted> is used to verify it.
229             After all tests have passed the modules are L<added|/add_module>.
230             So finally the loader is ready and can be queried with L<get_modules>
231             for example.
232              
233             =cut
234              
235             sub load_namespaces {
236             my ($self) = @_;
237              
238             my @modules = ();
239             foreach my $ns ( $self->get_namespaces ) {
240             my @modules_ns = findallmod $ns;
241              
242             # Module::Find::findallmod($ns) does not load $ns itself
243             push @modules_ns, $ns;
244              
245             if ( $self->has_wanted ) {
246             @modules_ns = grep { $self->wanted->($_) } @modules_ns;
247             }
248              
249             push @modules, @modules_ns;
250             }
251              
252             foreach my $module (@modules) {
253             Module::Runtime::use_module($module);
254             next unless $self->is_valid_worker_subclass($module);
255             next unless $self->has_job_method($module);
256             $self->add_module($module);
257             }
258             }
259              
260             =head1 AUTHOR
261              
262             See L<Gearman::Driver>.
263              
264             =head1 COPYRIGHT AND LICENSE
265              
266             See L<Gearman::Driver>.
267              
268             =head1 SEE ALSO
269              
270             =over 4
271              
272             =item * L<Gearman::Driver>
273              
274             =item * L<Gearman::Driver::Adaptor>
275              
276             =item * L<Gearman::Driver::Console>
277              
278             =item * L<Gearman::Driver::Console::Client>
279              
280             =item * L<Gearman::Driver::Job>
281              
282             =item * L<Gearman::Driver::Job::Method>
283              
284             =item * L<Gearman::Driver::Observer>
285              
286             =item * L<Gearman::Driver::Worker>
287              
288             =back
289              
290             =cut
291              
292             no Moose::Role;
293              
294             1;