Completed
Push — master ( 355f41...200862 )
by Mickael
06:25
created

GearmanExecute   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 419
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 10
dl 0
loc 419
ccs 91
cts 91
cp 1
rs 8.3157
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 29 2
A handleSystemSignal() 0 4 1
A setContainer() 0 6 1
A setEventDispatcher() 0 6 1
A setOutput() 0 6 1
A executeJob() 0 8 2
C callJob() 0 58 10
B createJob() 0 30 3
C runJob() 0 65 10
B addServers() 0 19 5
A executeWorker() 0 9 2
B handleJob() 0 33 5

How to fix   Complexity   

Complex Class

Complex classes like GearmanExecute often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GearmanExecute, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Gearman Bundle for Symfony2 / Symfony3
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 *
9
 * Feel free to edit as you please, and have fun.
10
 *
11
 * @author Marc Morera <[email protected]>
12
 */
13
14
namespace Mkk\GearmanBundle\Service;
15
16
use Symfony\Component\Console\Output\NullOutput;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
19
use Symfony\Component\DependencyInjection\ContainerInterface;
20
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
21
use Symfony\Component\OptionsResolver\OptionsResolver;
22
use Mkk\GearmanBundle\Command\Util\GearmanOutputAwareInterface;
23
use Mkk\GearmanBundle\Event\GearmanWorkExecutedEvent;
24
use Mkk\GearmanBundle\Event\GearmanWorkStartingEvent;
25
use Mkk\GearmanBundle\GearmanEvents;
26
use Mkk\GearmanBundle\Service\Abstracts\AbstractGearmanService;
27
use Mkk\GearmanBundle\Exceptions\ServerConnectionException;
28
29
/**
30
 * Gearman execute methods. All Worker methods
31
 */
32
class GearmanExecute extends AbstractGearmanService
33
{
34
    /**
35
     * @var ContainerInterface
36
     *
37
     * Container instance
38
     */
39
    private $container;
40
41
    /**
42
     * @var EventDispatcherInterface
43
     *
44
     * EventDispatcher instance
45
     */
46
    protected $eventDispatcher;
47
48
    /**
49
     * @var OutputInterface
50
     *
51
     * Output instance
52
     */
53
    protected $output;
54
55
    /**
56
     * @var OptionsResolver
57
     */
58
    protected $executeOptionsResolver;
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $executeOptionsResolver exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
59
60
    /**
61
     * Boolean to track if a system signal has been received
62
     * @var boolean
63
     */
64
    protected $stopWorkSignalReceived;
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $stopWorkSignalReceived exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
65
66
    /**
67
    * Bucket with worker objects configuration for PECL
68
    * @var array
69
    */
70
    protected $workersBucket = array();
71
72
    /**
73
     * Construct method
74
     *
75
     * @param GearmanCacheWrapper $gearmanCacheWrapper GearmanCacheWrapper
76
     * @param array               $defaultSettings     The default settings for the bundle
77
     */
78 2
    public function __construct(GearmanCacheWrapper $gearmanCacheWrapper, array $defaultSettings)
79
    {
80 2
        parent::__construct($gearmanCacheWrapper, $defaultSettings);
81
82 2
        $this->executeOptionsResolver = new OptionsResolver();
83 2
        $this->executeOptionsResolver
84 2
            ->setDefaults(array(
85 2
                'iterations'             => null,
86
                'minimum_execution_time' => null,
87
                'timeout'                => null,
88
            ))
89 2
	    ->setAllowedTypes('iterations', array('null', 'scalar'))
90 2
            ->setAllowedTypes('minimum_execution_time', array('null', 'scalar'))
91 2
            ->setAllowedTypes('timeout', array('null', 'scalar'));
92
        
93
94 2
        $this->stopWorkSignalReceived = false;
95
96
        /**
97
         * If the pcntl_signal exists, subscribe to the terminate and restart events for graceful worker stops.
98
         */
99 2
        if(false !== function_exists('pcntl_signal'))
100
        {
101
            declare(ticks = 1);
102 2
            pcntl_signal(SIGTERM, array($this,"handleSystemSignal"));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal handleSystemSignal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
103 2
            pcntl_signal(SIGHUP,  array($this,"handleSystemSignal"));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal handleSystemSignal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
104
105
        }
106 2
    }
107
108
    /**
109
     * Toggles that work should be stopped, we only subscribe to SIGTERM and SIGHUP
110
     * @param int $signo Signal number
111
     */
112
    public function handleSystemSignal($signo)
0 ignored issues
show
Unused Code introduced by
The parameter $signo is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
113
    {
114
        $this->stopWorkSignalReceived = true;
115
    }
116
117
    /**
118
     * Set container
119
     *
120
     * @param ContainerInterface $container Container
121
     *
122
     * @return GearmanExecute self Object
123
     */
124 1
    public function setContainer(ContainerInterface $container)
125
    {
126 1
        $this->container = $container;
127
128 1
        return $this;
129
    }
130
131
    /**
132
     * Set event dispatcher
133
     *
134
     * @param EventDispatcherInterface $eventDispatcher
135
     *
136
     * @return GearmanExecute self Object
137
     */
138 2
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
139
    {
140 2
        $this->eventDispatcher = $eventDispatcher;
141
142 2
        return $this;
143
    }
144
145
    /**
146
     * Set output
147
     *
148
     * @param OutputInterface $output
149
     *
150
     * @return GearmanExecute self Object
151
     */
152 12
    public function setOutput(OutputInterface $output)
153
    {
154 12
        $this->output = $output;
155
156 12
        return $this;
157
    }
158
159
    /**
160
     * Executes a job given a jobName and given settings and annotations of job
161
     *
162
     * @param string $jobName Name of job to be executed
163
     * @param array $options Array of options passed to the callback
164
     * @param \GearmanWorker $gearmanWorker Worker instance to use
0 ignored issues
show
Documentation introduced by
Should the type for parameter $gearmanWorker not be null|\GearmanWorker?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
165
     */
166 1
    public function executeJob($jobName, array $options = array(), \GearmanWorker $gearmanWorker = null)
167
    {
168 1
        $worker = $this->getJob($jobName);
169
170 1
        if (false !== $worker) {
171 1
            $this->callJob($worker, $options, $gearmanWorker);
172
        }
173 1
    }
174
175
    /**
176
     * Given a worker, execute GearmanWorker function defined by job.
177
     *
178
     * @param array $worker Worker definition
179
     * @param array $options Array of options passed to the callback
180
     * @param \GearmanWorker $gearmanWorker Worker instance to use
0 ignored issues
show
Documentation introduced by
Should the type for parameter $gearmanWorker not be null|\GearmanWorker?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
181
     *
182
     * @throws ServerConnectionException if a connection to a server was not possible.
183
     *
184
     * @return GearmanExecute self Object
185
     */
186 1
    private function callJob(Array $worker, array $options = array(), \GearmanWorker $gearmanWorker = null)
187
    {
188 1
        if(is_null($gearmanWorker)) {
189
            $gearmanWorker = new \GearmanWorker;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $gearmanWorker. This often makes code more readable.
Loading history...
190
        }
191
192 1
        if (isset($worker['job'])) {
193
194 1
            $jobs = array($worker['job']);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 17 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
195 1
            $iterations = $worker['job']['iterations'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
196 1
            $minimumExecutionTime = $worker['job']['minimumExecutionTime'];
197 1
            $timeout = $worker['job']['timeout'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 14 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
198 1
            $successes = $this->addServers($gearmanWorker, $worker['job']['servers']);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
199
200
        } else {
201
202
            $jobs = $worker['jobs'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 17 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
203
            $iterations = $worker['iterations'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
204
            $minimumExecutionTime = $worker['minimumExecutionTime'];
205
            $timeout = $worker['timeout'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 14 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
206
            $successes = $this->addServers($gearmanWorker, $worker['servers']);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
207
        }
208
209 1
        $options = $this->executeOptionsResolver->resolve($options);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $options. This often makes code more readable.
Loading history...
210
211 1
        $iterations           = $options['iterations']             ?: $iterations;
212 1
        $minimumExecutionTime = $options['minimum_execution_time'] ?: $minimumExecutionTime;
213 1
        $timeout              = $options['timeout']                ?: $timeout;
214
215 1
        if (count($successes) < 1) {
216
            if ($minimumExecutionTime > 0) {
217
                sleep($minimumExecutionTime);
218
            }
219
            throw new ServerConnectionException('Worker was unable to connect to any server.');
220
        }
221
222 1
        $objInstance = $this->createJob($worker);
223
224
        /**
225
         * Start the timer before running the worker.
226
         */
227 1
        $time = time();
228 1
        $this->runJob($gearmanWorker, $objInstance, $jobs, $iterations, $timeout);
229
230
        /**
231
         * If there is a minimum expected duration, wait out the remaining period if there is any.
232
         */
233 1
        if ($minimumExecutionTime > 0) {
234
            $now = time();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
235
            $remaining = $minimumExecutionTime - ($now - $time);
236
237
            if ($remaining > 0) {
238
                sleep($remaining);
239
            }
240
        }
241
242 1
        return $this;
243
    }
244
245
    /**
246
     * Given a worker settings, return Job instance
247
     *
248
     * @param array $worker Worker settings
249
     *
250
     * @return Object Job instance
251
     */
252 1
    private function createJob(array $worker)
253
    {
254
        /**
255
         * If service is defined, we must retrieve this class with dependency injection
256
         *
257
         * Otherwise we just create it with a simple new()
258
         */
259 1
        if ($worker['service']) {
260
261
            $objInstance = $this->container->get($worker['service']);
262
263
        } else {
264
265 1
            $objInstance = new $worker['className'];
266
267
            /**
268
             * If instance of given object is instanceof
269
             * ContainerAwareInterface, we inject full container by calling
270
             * container setter.
271
             *
272
             * @see https://github.com/mmoreram/gearman-bundle/pull/12
273
             */
274 1
            if ($objInstance instanceof ContainerAwareInterface) {
275
276
                $objInstance->setContainer($this->container);
277
            }
278
        }
279
280 1
        return $objInstance;
281
    }
282
283
    /**
284
     * Given a GearmanWorker and an instance of Job, run it
285
     *
286
     * @param \GearmanWorker $gearmanWorker Gearman Worker
287
     * @param Object         $objInstance   Job instance
288
     * @param array          $jobs          Array of jobs to subscribe
289
     * @param integer        $iterations    Number of iterations
290
     * @param integer        $timeout       Timeout
0 ignored issues
show
Documentation introduced by
Should the type for parameter $timeout not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
291
     *
292
     * @return GearmanExecute self Object
0 ignored issues
show
Documentation introduced by
Should the return type not be GearmanExecute|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
293
     */
294 1
    private function runJob(\GearmanWorker $gearmanWorker, $objInstance, array $jobs, $iterations, $timeout = null)
295
    {
296
        /**
297
         * Set the output of this instance, this should allow workers to use the console output.
298
         */
299 1
        if ($objInstance instanceof GearmanOutputAwareInterface) {
300
            $objInstance->setOutput($this->output ? : new NullOutput());
301
        }
302
303
        /**
304
         * Every job defined in worker is added into GearmanWorker
305
         */
306 1
        foreach ($jobs as $job) {
307
308
            /**
309
            * worker needs to have it's context into separated memory space;
310
            * if it's passed as a value, then garbage collector remove the target
311
            * what causes a segfault (see https://github.com/wcgallego/pecl-gearman/issues/19)
312
            */
313 1
            $this->workersBucket[$job['realCallableName']] = array(
314 1
                'job_object_instance' => $objInstance,
315 1
                'job_method' => $job['methodName'],
316 1
                'jobs' => $jobs
317
            );
318
319 1
            $gearmanWorker->addFunction(
320 1
                $job['realCallableName'],
321 1
                array($this, 'handleJob')
322
            );
323
        }
324
325
        /**
326
         * If iterations value is 0, is like worker will never die
327
         */
328 1
        $alive = (0 === $iterations);
329
330 1
        if ($timeout > 0) {
331
            $gearmanWorker->setTimeout($timeout * 1000);
332
        }
333
334
        /**
335
         * Executes GearmanWorker with all jobs defined
336
         */
337 1
        while (false === $this->stopWorkSignalReceived && $gearmanWorker->work()) {
338
339 1
            $iterations--;
340
341 1
            $event = new GearmanWorkExecutedEvent($jobs, $iterations, $gearmanWorker->returnCode());
342 1
            $this->eventDispatcher->dispatch(GearmanEvents::GEARMAN_WORK_EXECUTED, $event);
343
344 1
            if ($gearmanWorker->returnCode() != GEARMAN_SUCCESS) {
345
346
                break;
347
            }
348
349
            /**
350
             * Only finishes its execution if alive is false and iterations
351
             * arrives to 0
352
             */
353 1
            if (!$alive && $iterations <= 0) {
354
355 1
                break;
356
            }
357
        }
358 1
    }
359
360
    /**
361
     * Adds into worker all defined Servers.
362
     * If any is defined, performs default method
363
     *
364
     * @param \GearmanWorker $gmworker Worker to perform configuration
365
     * @param array          $servers  Servers array
366
     *
367
     * @throws ServerConnectionException if a connection to a server was not possible.
368
     *
369
     * @return array         Successfully added servers
370
     */
371 1
    private function addServers(\GearmanWorker $gmworker, array $servers)
372
    {
373 1
        $successes = array();
374
375 1
        if (!empty($servers)) {
376
377
            foreach ($servers as $server) {
378
                if (@$gmworker->addServer($server['host'], $server['port'])) {
379
                    $successes[] = $server;
380
                }
381
            }
382
        } else {
383 1
            if (@$gmworker->addServer()) {
384 1
                $successes[] = array('127.0.0.1', 4730);
385
            }
386
        }
387
388 1
        return $successes;
389
    }
390
391
    /**
392
     * Executes a worker given a workerName subscribing all his jobs inside and
393
     * given settings and annotations of worker and jobs
394
     *
395
     * @param string $workerName Name of worker to be executed
396
     */
397
    public function executeWorker($workerName, array $options = array())
398
    {
399
        $worker = $this->getWorker($workerName);
400
401
        if (false !== $worker) {
402
403
            $this->callJob($worker, $options);
404
        }
405
    }
406
407
    /**
408
     * Wrapper function handler for all registered functions
409
     * This allows us to do some nice logging when jobs are started/finished
410
     *
411
     * @see https://github.com/brianlmoon/GearmanManager/blob/ffc828dac2547aff76cb4962bb3fcc4f454ec8a2/GearmanPeclManager.php#L95-206
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 133 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
412
     *
413
     * @param \GearmanJob $job
414
     *
415
     * @return mixed
416
     */
417 1
    public function handleJob(\GearmanJob $job)
418
    {
419 1
        if(!isset($this->workersBucket[$job->functionName()])){
420
            $context = false;
421
        }else{
422 1
            $context = $this->workersBucket[$job->functionName()];
423
        }
424
425
        if (
426 1
            !is_array($context)
427 1
            || !array_key_exists('job_object_instance', $context)
428 1
            || !array_key_exists('job_method', $context)
429
        ) {
430
            throw new \InvalidArgumentException('$context shall be an array with job_object_instance and job_method key.');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
431
        }
432
433 1
        $event = new GearmanWorkStartingEvent($context['jobs']);
434 1
        $this->eventDispatcher->dispatch(GearmanEvents::GEARMAN_WORK_STARTING, $event);
435
436 1
        $result = call_user_func_array(
437 1
            array($context['job_object_instance'], $context['job_method']),
438 1
            array($job, $context)
439
        );
440
441
        /**
442
         * Workaround for PECL bug #17114
443
         * http://pecl.php.net/bugs/bug.php?id=17114
444
         */
445 1
        $type = gettype($result);
446 1
        settype($result, $type);
447
448 1
        return $result;
449
    }
450
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
451