Completed
Push — master ( 41a9da...ab8452 )
by Marco
11:46
created

Extender::extend()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 10.75

Importance

Changes 14
Bugs 1 Features 1
Metric Value
c 14
b 1
f 1
dl 0
loc 30
ccs 3
cts 12
cp 0.25
rs 8.5806
cc 4
eloc 14
nc 5
nop 0
crap 10.75
1
<?php namespace Comodojo\Extender;
2
3
use \Console_Color2;
4
use \Console_Table;
5
use \Comodojo\Extender\Scheduler\Scheduler;
6
use \Comodojo\Extender\Scheduler\Schedule;
7
use \Comodojo\Extender\Runner\JobsRunner;
8
use \Comodojo\Extender\Runner\JobsResult;
9
use \Comodojo\Extender\Job\Job;
10
use \Comodojo\Extender\Log\ExtenderLogger;
11
use \Comodojo\Extender\Events;
12
use \Comodojo\Extender\TasksTable;
13
use \Exception;
14
15
/**
16
 * Extender main class
17
 *
18
 * @package     Comodojo extender
19
 * @author      Marco Giovinazzi <[email protected]>
20
 * @license     GPL-3.0+
21
 *
22
 * LICENSE:
23
 *
24
 * This program is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License as
26
 * published by the Free Software Foundation, either version 3 of the
27
 * License, or (at your option) any later version.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License
35
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
36
 */
37
38
class Extender {
39
40
    // configurable things
41
42
    /**
43
     * Max result lenght (in bytes) retrieved from parent in miltithread mode
44
     *
45
     * @var int
46
     */
47
    private $max_result_bytes_in_multithread = null;
48
49
    /**
50
     * Maximum time (in seconds) the parent will wait for child tasks to be completed (in miltithread mode)
51
     *
52
     * @var int
53
     */
54
    private $max_childs_runtime = null;
55
56
    /**
57
     * Multithread mode
58
     *
59
     * @var bool
60
     */
61
    private $multithread_mode = false;
62
63
    /**
64
     * Verbose mode, if requested via command line arg -v
65
     *
66
     * @var bool
67
     */
68
    private $verbose_mode = false;
69
70
    /**
71
     * Debug mode, if requested via command line arg -V
72
     *
73
     * @var bool
74
     */
75
    private $debug_mode = false;
76
77
    /**
78
     * Summary mode, if requested via command line arg -s
79
     *
80
     * @var bool
81
     */
82
    private $summary_mode = false;
83
84
    /**
85
     * Daemon mode, if requested via command line arg -d
86
     *
87
     * @var bool
88
     */
89
    private $daemon_mode = false;
90
91
    /**
92
     * Timestamp, relative, of current extend() cycle
93
     *
94
     * @var float
95
     */
96
    private $timestamp = null;
97
98
    /**
99
     * Timestamp, absolute, sinnce extender was initiated
100
     *
101
     * @var float
102
     */
103
    private $timestamp_absolute = null;
104
105
    /**
106
     * PID of the parent extender process
107
     *
108
     * @var int
109
     */
110
    private $parent_pid = null;
111
112
    /**
113
     * Set exender in paused mode (no job will be processed)
114
     *
115
     * @var bool
116
     */
117
    private $paused = false;
118
119
    // Helper classes
120
121
    /**
122
     * Events manager instance
123
     *
124
     * @var \Comodojo\Extender\Events
125
     */
126
    private $events = null;
127
128
    /**
129
     * Console_Color2 instance
130
     *
131
     * @var \Console_Color2
132
     */
133
    private $color = null;
134
135
    /**
136
     * Logger instance
137
     *
138
     * @var \Monolog\Logger
139
     */
140
    private $logger = null;
141
142
    /**
143
     * JobsRunner instance
144
     *
145
     * @var \Comodojo\Extender\Runner\JobsRunner
146
     */
147
    private $runner = null;
148
149
    /**
150
     * TasksTable instance
151
     *
152
     * @var \Comodojo\Extender\TasksTable
153
     */
154
    private $tasks = null;
155
156
    // checks and locks are static!
157
158
    // local archives
159
160
    /**
161
     * Failed processes, refreshed each cycle (in daemon mode)
162
     *
163
     * @var int
164
     */
165
    private $failed_processes = 0;
166
167
    /**
168
     * Completed processes
169
     *
170
     * @var int
171
     */
172
    private $completed_processes = 0;
173
174
    /**
175
     * Constructor method
176
     *
177
     * Prepare extender environment, do checks and fire extender.ready event
178
     */
179 12
    final public function __construct() {
180
181
        // check if extender is running from cli
182
183 12
        if ( Checks::cli() === false ) {
184
185
            echo "Extender runs only in php-cli, exiting";
186
187
            self::end(1);
188
189
        }
190
191
        // setup default timezone (in daemon mode, timezone warning may break extender)
192
193 12
        if ( empty(ini_get('date.timezone')) ) {
194
195 12
            date_default_timezone_set(defined('EXTENDER_TIMEZONE') ? EXTENDER_TIMEZONE : 'Europe/Rome');
196
197 12
        }
198
199
        $this->timestamp_absolute = microtime(true);
200
201 12
        $this->color = new Console_Color2();
202
203 12
        // get command line options (vsdh)
204
205
        list($this->verbose_mode, $this->debug_mode, $this->summary_mode, $this->daemon_mode, $help_mode) = self::getCommandlineOptions();
206
207
        if ( $help_mode ) {
208
209
            self::showHelp($this->color);
210
211 12
            self::end(0);
212
213
        }
214
215 12
        $this->logger = ExtenderLogger::create($this->verbose_mode, $this->debug_mode);
216
217 12
        // do checks
218
219
        $check_constants = Checks::constants();
220
221
        if ( $check_constants !== true ) {
222
223
            $this->logger->critical($check_constants);
0 ignored issues
show
Bug introduced by
It seems like $check_constants defined by \Comodojo\Extender\Checks::constants() on line 219 can also be of type boolean; however, Monolog\Logger::critical() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
224
225 12
            self::end(1);
226
227
        }
228
229
        if ( Checks::signals() === false AND $this->daemon_mode ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
230
231
            $this->logger->critical("Extender cannot run in daemon mode without PHP Process Control Extensions");
232
233 12
            self::end(1);
234
235
        }
236
237
        if ( Checks::database() === false ) {
238
239
            $this->logger->critical("Extender database not available, exiting");
240
241 12
            self::end(1);
242
243 12
        }
244
245
        $this->tasks = TasksTable::load($this->logger);
246
247 12
        $this->events = Events::load($this->logger);
248
249 12
        // setup extender parameters
250
251 12
        $this->max_result_bytes_in_multithread = defined('EXTENDER_MAX_RESULT_BYTES') ? filter_var(EXTENDER_MAX_RESULT_BYTES, FILTER_VALIDATE_INT) : 2048;
252
253
        $this->max_childs_runtime = defined('EXTENDER_MAX_CHILDS_RUNTIME') ? filter_var(EXTENDER_MAX_CHILDS_RUNTIME, FILTER_VALIDATE_INT) : 600;
254
255 12
        $this->multithread_mode = defined('EXTENDER_MULTITHREAD_ENABLED') ? filter_var(EXTENDER_MULTITHREAD_ENABLED, FILTER_VALIDATE_BOOLEAN) : false;
256
257
        // if in daemon mode, remember parent pid, setup lock and register signal handlers
258
259
        if ( $this->daemon_mode ) {
260
261
            $this->parent_pid = posix_getpid();
262
263
            Lock::register($this->parent_pid);
264
265
            $this->adjustNiceness();
266
267
            if ( Checks::signals() ) $this->registerSignals();
268
269 12
        }
270
271 12
        // init the runner
272
273
        $this->runner = new JobsRunner($this->logger, $this->getMultithreadMode(), $this->max_result_bytes_in_multithread, $this->max_childs_runtime);
274
275 12
        $this->logger->notice("Extender ready");
276
277 12
        // store initial status and queue information
278
279
        Status::dump($this->timestamp_absolute, $this->parent_pid, $this->completed_processes, $this->failed_processes, $this->paused);
0 ignored issues
show
Documentation introduced by
$this->parent_pid is of type integer, but the function expects a object<Comodojo\Extender\itn>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->failed_processes is of type integer, but the function expects a object<Comodojo\Extender\itn>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
280
281 12
        Queue::dump(0, 0);
282
283
        // we are ready to go!
284
285
    }
286
287
    /**
288
     * Set max result length (in bytes) that should be read from child tasks
289
     *
290 3
     * @param   int     $bytes  Maximum length (bytes)
291
     *
292 3
     * @return  Extender          $this
293
     */
294 3
    final public function setMaxResultLength($bytes) {
295
296
        $this->max_result_bytes_in_multithread = filter_var($bytes, FILTER_VALIDATE_INT, array("default" => 2048));
297
298
        return $this;
299
300
    }
301
302
    /**
303 3
     * Get max result length (in bytes)
304
     *
305 3
     * @return  int     Bytes parent should read (max)
306
     */
307
    final public function getMaxResultLength() {
308
309
        return $this->max_result_bytes_in_multithread;
310
311
    }
312
313
    /**
314
     * Set maximum time (in seconds) the parent will wait for child tasks to be completed (in miltithread mode)
315
     *
316
     * After $time seconds, parent will start killing tasks
317
     *
318 3
     * @param   int     $time   Maximum time (seconds)
319
     *
320 3
     * @return  Extender          $this
321
     */
322 3
    final public function setMaxChildsRuntime($time) {
323
324
        $this->max_childs_runtime = filter_var($time, FILTER_VALIDATE_INT, array("min_range" => 1, "default" => 300));
325
326
        return $this;
327
328
    }
329
330
    /**
331 3
     * Get maximum time (in seconds) the parent will wait for child tasks to be completed (in miltithread mode)
332
     *
333 3
     * @return  int     Time parent will wait for childs to be completed
334
     */
335
    final public function getMaxChildsRuntime() {
336
337
        return $this->max_childs_runtime;
338
339
    }
340
341
    /**
342
     * Set working mode (single or multithread)
343
     *
344
     * If multithread enabled, extender will use pcntl to fork child tasks
345
     *
346 3
     * @param   bool    $mode   Enable/disable multithread
347
     *
348 3
     * @return  Extender          $this
349
     */
350 3
    final public function setMultithreadMode($mode) {
351
352
        $this->multithread_mode = filter_var($mode, FILTER_VALIDATE_BOOLEAN);
353
354
        return $this;
355
356
    }
357
358
    /**
359 12
     * Get multithread mode status
360
     *
361 12
     * @return  bool    True if enabled, false if disabled
362
     */
363
    final public function getMultithreadMode() {
364
365
        return ($this->multithread_mode AND Checks::multithread()) ? true : false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
366
367
    }
368
369
    /**
370 6
     * Get daemon mode status
371
     *
372 6
     * @return  bool    True if enabled, false if disabled
373
     */
374
    final public function getDaemonMode() {
375
376
        return $this->daemon_mode;
377
378
    }
379
380
    /**
381 3
     * Get the number of completed processes
382
     *
383 3
     * @return  int
384
     */
385
    final public function getCompletedProcesses() {
386
387
        return $this->completed_processes;
388
389
    }
390
391
    /**
392 3
     * Get the number of failed processes
393
     *
394 3
     * @return int
395
     */
396
    final public function getFailedProcesses() {
397
398
        return $this->failed_processes;
399
400
    }
401
402
    /**
403 3
     * Get current version
404
     *
405 3
     * @return  string
406
     */
407
    final public function getVersion() {
408
409
        return Version::getVersion();
410
411
    }
412
413
    /**
414 6
     * Get events manager
415
     *
416 6
     * @return  \Comodojo\Extender\Events
417
     */
418
    final public function events() {
419
420
        return $this->events;
421
422
    }
423
424
    /**
425 3
     * Get console color instance
426
     *
427 3
     * @return  \Console_Color2
428
     */
429
    final public function color() {
430
431
        return $this->color;
432
433
    }
434
435
    /**
436 3
     * Get internal logger
437
     *
438 3
     * @return  \Monolog\Logger
439
     */
440
    final public function logger() {
441
442
        return $this->logger;
443
444
    }
445
446
    /**
447 3
     * Get jobs' runner
448
     *
449 3
     * @return  JobsRunner
450
     */
451
    final public function runner() {
452
453
        return $this->runner;
454
455
    }
456
457
    /**
458 6
     * Get the tasks' table
459
     *
460 6
     * @return  TasksTable
461
     */
462
    final public function tasks() {
463
464
        return $this->tasks;
465
466
    }
467
468 3
    /**
469
     * Do extend!
470 3
     *
471
     */
472
    public function extend() {
473
474
        if ( $this->getDaemonMode() ) {
475
476
            $this->logger->notice("Executing extender in daemon mode");
477
478
            $idle_time = defined("EXTENDER_IDLE_TIME") ? filter_var(EXTENDER_IDLE_TIME, FILTER_VALIDATE_INT, array(
479
                'options' => array(
480
                    'default' => 1,
481
                    'min_range' => 1
482
                )
483
            )) : 1;
484 3
485
            while (true) {
486 3
487
                $this->cycle();
488
489
                sleep($idle_time);
490 3
491
            }
492
493
        } else {
494
495
            $this->logger->notice("Executing extender in single mode");
496
497
            $this->cycle();
498
499
        }
500
501
    }
502
503
    /**
504
     * Change parent process priority according to EXTENDER_NICENESS
505
     *
506
     */
507
    final public function adjustNiceness() {
508
509
        if ( defined("EXTENDER_PARENT_NICENESS") ) {
510
511
            $niceness = proc_nice(EXTENDER_PARENT_NICENESS);
512
513
            if ( $niceness == false ) $this->logger->warning("Unable to set parent process niceness to ".EXTENDER_PARENT_NICENESS);
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
514
515
        }
516
517
    }
518
519
    /**
520
     * Register signals
521
     *
522
     */
523
    final public function registerSignals() {
524
525
        $pluggable_signals = array(
526
            SIGHUP, SIGCHLD, SIGUSR2, SIGILL, SIGTRAP, SIGABRT, SIGIOT, SIGBUS, SIGFPE,
527
            SIGSEGV, SIGPIPE, SIGALRM, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ,
528
            SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGSYS, SIGBABY
529
        );
530
531
        if ( defined('SIGPOLL') )   $pluggable_signals[] = SIGPOLL;
532
        if ( defined('SIGPWR') )    $pluggable_signals[] = SIGPWR;
533
        if ( defined('SIGSTKFLT') ) $pluggable_signals[] = SIGSTKFLT;
534
535
        // register supported signals
536
537
        pcntl_signal(SIGTERM, array($this, 'sigTermHandler'));
538
539
        pcntl_signal(SIGINT, array($this, 'sigTermHandler'));
540
541
        pcntl_signal(SIGTSTP, array($this, 'sigStopHandler'));
542
543
        pcntl_signal(SIGCONT, array($this, 'sigContHandler'));
544
545
        //pcntl_signal(SIGUSR1, array($this,'sigUsr1Handler'));
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
546
547
        // register pluggable signals
548
549
        foreach ( $pluggable_signals as $signal ) {
550
551
            pcntl_signal($signal, array($this, 'genericSignalHandler'));
552
553
        }
554 3
555
        // register shutdown function
556 3
557
        register_shutdown_function(array($this, 'shutdown'));
558
559
    }
560
561
    /**
562
     * Delete all status file after exit() called
563
     *
564
     */
565
    final public function shutdown($force = false) {
566
567
        if ( $this->parent_pid == posix_getpid() ) {
568
569
            $this->logger->info("Shutdown in progress, cleaning environment");
570 3
571
            Lock::release();
572 3
573
            Status::release();
574 3
575
            Queue::release();
576 3
577
            Planner::release();
578 3
579
        }
580 3
581
        if ( $force === true ) {
582 3
583
            $this->logger->info("Shutdown in progress, cleaning environment");
584
585
            Status::release();
586
587
            Queue::release();
588
589
            Planner::release();
590
591
        }
592
593
    }
594
595
    /**
596
     * The sigTerm handler.
597
     *
598
     * It kills everything and then exit with status 1
599
     */
600
    final public function sigTermHandler() {
601
602
        if ( $this->parent_pid == posix_getpid() ) {
603
604
            $this->logger->info("Received TERM signal, shutting down extender gracefully");
605
606
            $this->runner->killAll($this->parent_pid);
607
608
            self::end(1);
609
610
        }
611
612
    }
613
614
    /**
615
     * The sigStop handler.
616
     *
617
     * It just pauses extender execution
618
     */
619
    final public function sigStopHandler() {
620
621
        if ( $this->parent_pid == posix_getpid() ) {
622
623
            $this->logger->info("Received STOP signal, pausing extender");
624
625
            $this->paused = true;
626
627
        }
628
629
    }
630
631
    /**
632
     * The sigCont handler.
633
     *
634
     * It just resume extender execution
635
     */
636
    final public function sigContHandler() {
637
638
        if ( $this->parent_pid == posix_getpid() ) {
639
640
            $this->logger->info("Received CONT signal, resuming extender");
641
642
            $this->paused = false;
643
644
        }
645
646
    }
647
648
    /**
649
     * The generig signal handler.
650
     *
651
     * It can be used to handle custom signals
652
     */
653
    final public function genericSignalHandler($signal) {
654 3
655
        if ( $this->parent_pid == posix_getpid() ) {
656
657
            $this->logger->info("Received ".$signal." signal, firing associated event(s)");
658 3
659
            $this->events->fire("extender.signal.".$signal, "VOID", $this);
660
661
        }
662 3
663
    }
664
665
    private function cycle() {
666 3
667
      // fire extender ready event
668
669
      $this->events->fire("extender", "VOID", $this);
670 3
671
      // dispatch signals (if multithread active)
672
673
      if ( $this->getMultithreadMode() ) pcntl_signal_dispatch();
674 3
675
      // if extender is paused (SIGINT), skip to extend
676
677
      if ( $this->paused ) return;
678 3
679
      // fix relative timestamp
680 3
681
      $this->timestamp = microtime(true);
682
683
      // fire tasktable event
684
685
      $this->tasks = $this->events->fire("extender.tasks", "TASKSTABLE", $this->tasks);
686
687
      // get the next planned activity interval
688
689
      $plans = Planner::get();
690
691
      if ( !is_null($plans) AND $this->timestamp < $plans ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
692
693
          // nothing to do right now, still waiting if in daemon mode
694
695
          $this->logger->info("Next planned job: ".date('c', $plans));
696
697
          $this->logger->notice("Extender completed\n");
698
699
          if ( $this->getDaemonMode() === false ) {
700
701
              $this->shutdown(true);
702
703
              self::end(0);
704
705
          }
706 3
707
          return;
708
709
      }
710 3
711
      // if no plan is retrieved, try to retrieve it from scheduler
712 3
713
      try {
714 3
715
          // get schedules and dispatch schedule event
716
717
          list($schedules, $planned) = Scheduler::getSchedules($this->logger, $this->timestamp);
718 3
719
          // write next planned activity interval
720
721
          if ( !is_null($planned) AND $planned != 0 ) Planner::set($planned);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
722 3
723
          $scheduled = new Schedule();
724 3
725
          $scheduled->setSchedules($schedules);
726 3
727
          // expose the current shcedule via events
728 3
729
          $scheduled = $this->events->fire("extender.schedule", "SCHEDULE", $scheduled);
730 3
731
          // if no jobs in queue, exit gracefully
732 3
733
          if ( $scheduled->howMany() == 0 ) {
734 3
735
              $this->logger->info("No jobs to process right now, exiting");
736 3
737
              $this->logger->notice("Extender completed\n");
738
739
              if ( $this->getDaemonMode() === false ) {
740
741
                  $this->shutdown(true);
742
743
                  self::end(0);
744
745
              }
746
747
              return;
748
749
          }
750
751
          // compose jobs
752
753
          foreach ( $scheduled->getSchedules() as $schedule ) {
754
755
              if ( $this->tasks->isRegistered($schedule['task']) ) {
756
757
                  $job = new Job();
758
759
                  $job->setName($schedule['name'])
760
                      ->setId($schedule['id'])
761
                      ->setParameters(unserialize($schedule['params']))
762
                      ->setTask($schedule['task'])
763
                      ->setClass($this->tasks->getClass($schedule['task']));
764
765
                  $this->runner->addJob($job);
766
767
              } else {
768
769
                  $this->logger->warning("Skipping job due to unknown task", array(
770
                      "ID"     => $schedule['id'],
771
                      "NAME"   => $schedule['name'],
772
                      "TASK"   => $schedule['task']
773
                  ));
774
775
              }
776
777
          }
778
779
          // lauch runner
780
781
          $result = $this->runner->run();
782
783
          // free runner for next cycle
784
785
          $this->runner->free();
786
787
          // compose results
788
789
          $results = new JobsResult($result);
790
791
          // update schedules
792
793
          Scheduler::updateSchedules($this->logger, $result);
794
795
          // increment counters
796
797
          foreach ( $result as $r ) {
798
799
              if ( $r[2] ) $this->completed_processes++;
800
801
              else $this->failed_processes++;
802
803
          }
804
805
      } catch (Exception $e) {
806
807
          $this->logger->error($e->getMessage());
808
809
          if ( $this->getDaemonMode() === false ) {
810
811
              self::end(1);
812
813
          }
814
815
      }
816
817
      // fire result event
818
819
      $this->events->fire("extender.result", "VOID", $results);
0 ignored issues
show
Bug introduced by
The variable $results does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
820
821
      $this->logger->notice("Extender completed\n");
822
823
      // show summary (if -s)
824
825
      if ( $this->summary_mode ) self::showSummary($this->timestamp, $result, $this->color);
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
826
827
      Status::dump($this->timestamp_absolute, $this->parent_pid, $this->completed_processes, $this->failed_processes, $this->paused);
0 ignored issues
show
Documentation introduced by
$this->parent_pid is of type integer, but the function expects a object<Comodojo\Extender\itn>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->failed_processes is of type integer, but the function expects a object<Comodojo\Extender\itn>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
828
829
      if ( $this->getDaemonMode() === false ) {
830
831
          $this->shutdown(true);
832
833
          self::end(0);
834
835
      }
836
837
    }
838
839
    private static function showHelp($color) {
840
841
        echo Version::getDescription();
842
843
        echo "\nVersion: ".$color->convert("%g".Version::getVersion()."%n");
844
845
        echo "\n\nAvailable options:";
846
847
        echo "\n------------------";
848
849
        echo "\n".$color->convert("%g -v %n").": verbose mode";
850
851
        echo "\n".$color->convert("%g -V %n").": debug mode";
852 12
853
        echo "\n".$color->convert("%g -s %n").": show summary of executed jobs (if any)";
854 12
855
        echo "\n".$color->convert("%g -d %n").": run extender in daemon mode";
856
857 12
        echo "\n".$color->convert("%g -h %n").": show this help";
858 12
859 12
        echo "\n\n";
860 12
861 12
    }
862 12
863
    private static function getCommandlineOptions() {
864
865
        $options = getopt("svdVh");
866
867
        return array(
868
            array_key_exists('v', $options) ? true : false,
869
            array_key_exists('V', $options) ? true : false,
870
            array_key_exists('s', $options) ? true : false,
871
            array_key_exists('d', $options) ? true : false,
872
            array_key_exists('h', $options) ? true : false
873
        );
874
875
    }
876
877
    /**
878
     * @param double $timestamp
879
     */
880
    private static function showSummary($timestamp, $completed_processes, $color) {
881
882
        $header_string = "\n\n --- Comodojo Extender Summary --- ".date('c', $timestamp)."\n\n";
883
884
        $tbl = new Console_Table(CONSOLE_TABLE_ALIGN_LEFT, CONSOLE_TABLE_BORDER_ASCII, 1, null, true);
885
886
        $tbl->setHeaders(array(
887
            'Pid',
888
            'Name',
889
            'Log Id',
890
            'Success',
891
            'Result (truncated)',
892
            'Time elapsed'
893
        ));
894
895
        foreach ( $completed_processes as $key => $completed_process ) {
896
897
            $pid = $completed_process[0];
898
899
            $name = $completed_process[1];
900
901
            $success = $color->convert($completed_process[2] ? "%gYES%n" : "%rNO%n");
902
903
            $result = str_replace(array("\r", "\n"), " ", $completed_process[5]);
904
905
            $result = strlen($result) >= 80 ? substr($result, 0, 80)."..." : $result;
906
907
            $elapsed = empty($completed_process[4]) ? "--" : ($completed_process[4] - $completed_process[3]);
908
909
            $worklog_id = $completed_process[7];
910
911
            $tbl->addRow(array(
912
                $pid,
913
                $name,
914
                $worklog_id,
915
                $success,
916
                $result,
917
                $elapsed
918
            ));
919
920 3
        }
921
922 3
        $footer_string = "\n\nTotal script runtime: ".(microtime(true) - $timestamp)." seconds\r\n\n";
923
924 3
        print $header_string.$tbl->getTable().$footer_string;
925
926 3
    }
927
928
    /**
929
     * @param integer $returnCode
930
     */
931 View Code Duplication
    private static function end($returnCode) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
932
933
        if ( defined('COMODOJO_PHPUNIT_TEST') && @constant('COMODOJO_PHPUNIT_TEST') === true ) {
934
935
            if ( $returnCode === 1 ) throw new Exception("PHPUnit Test Exception");
936
937
            else return $returnCode;
938
939
        } else {
940
941
            exit($returnCode);
942
943
        }
944
945
    }
946
947
}
948