Completed
Push — develop ( b4fd40...785f8f )
by greg
03:10
created

Cron::setSuccessLogLifetime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
namespace PlaygroundCore\Service;
3
4
use PlaygroundCore\Entity;
5
use PlaygroundCore\Exception;
6
use PlaygroundCore\Mapper;
7
use PlaygroundCore\Service\Registry;
8
use Doctrine\ORM\EntityManager;
9
use Zend\ServiceManager\ServiceManager;
10
use ZfcBase\EventManager\EventProvider;
11
use Zend\ServiceManager\ServiceLocatorInterface;
12
13
/**
14
 * main Cron class
15
 *
16
 * handle cron job registration, validation, scheduling, running, and cleanup
17
 *
18
 * @author heartsentwined <[email protected]>
19
 * @license GPL http://opensource.org/licenses/gpl-license.php
20
 */
21
class Cron extends EventProvider
22
{
23
    /**
24
     * how long ahead to schedule cron jobs
25
     *
26
     * @var int (minute)
27
     */
28
    protected $scheduleAhead = 60;
29
30
    /**
31
     * the Doctrine ORM Entity Manager
32
     *
33
     * @var EntityManager
34
     */
35
    protected $em;
36
37
    /**
38
     * List of cron jobs to be managed
39
     */
40
    protected $cronjobs;
41
42
    /**
43
     * how long before a scheduled job is considered missed
44
     *
45
     * @var int (minute)
46
     */
47
    protected $scheduleLifetime = 60;
48
49
    /**
50
     * maximum running time of each cron job
51
     *
52
     * @var int (minute)
53
     */
54
    protected $maxRunningTime = 60;
55
56
    /**
57
     * how long to keep successfully completed cron job logs
58
     *
59
     * @var int (minute)
60
     */
61
    protected $successLogLifetime = 300;
62
63
    /**
64
     * how long to keep failed (missed / error) cron job logs
65
     *
66
     * @var int (minute)
67
     */
68
    protected $failureLogLifetime = 10080;
69
70
    /**
71
     * set of pending cron jobs
72
     *
73
     * wrapped the Repo function here to implement a (crude) cache feature
74
     *
75
     * @var array of Entity\Cronjob
76
     */
77
    protected $pending;
78
79
    /**
80
     *
81
     * @var ServiceManager
82
     */
83
    protected $serviceLocator;
84
85
    public function __construct(ServiceLocatorInterface $locator)
86
    {
87
        $this->serviceLocator = $locator;
0 ignored issues
show
Documentation Bug introduced by
$locator is of type object<Zend\ServiceManag...erviceLocatorInterface>, but the property $serviceLocator was declared to be of type object<Zend\ServiceManager\ServiceManager>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
88
    }
89
90
    /**
91
     * main entry function
92
     *
93
     * 1. schedule new cron jobs
94
     * 2. process cron jobs
95
     * 3. cleanup old logs
96
     *
97
     * @return self
98
     */
99
    public function run()
100
    {
101
        $this->schedule()
102
        ->process()
103
        ->cleanup();
104
105
        return $this;
106
    }
107
108
    /**
109
     * trigger an event and fetch crons
110
     * Return array
111
     */
112
    public function getCronjobs()
113
    {
114
        if (!$this->cronjobs) {
115
            $cronjobs = array();
116
117
            $results = $this->serviceLocator
118
                ->get('application')
119
                ->getEventManager()
120
                ->trigger(__FUNCTION__, $this, array(
121
                        'cronjobs' => $cronjobs
122
                ));
123
124
            if ($results) {
125
                foreach ($results as $key => $cron) {
126
                    foreach ($cron as $id => $conf) {
127
                        $cronjobs[$id] = $conf;
128
                    }
129
                }
130
            }
131
132
            $this->setCronjobs($cronjobs);
133
        }
134
135
        return $this->cronjobs;
136
    }
137
138
    /**
139
     *
140
     */
141
    public function setCronjobs($cronjobs)
142
    {
143
        $this->cronjobs = $cronjobs;
144
145
        return $this;
146
    }
147
148
    public function getPending()
149
    {
150
        if (!$this->pending) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->pending of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
151
            $this->pending = $this->getEm()
152
                ->getRepository('PlaygroundCore\Entity\Cronjob')
153
                ->getPending();
154
        }
155
156
        return $this->pending;
157
    }
158
159
    public function resetPending()
160
    {
161
        $this->pending = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $pending.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
162
163
        return $this;
164
    }
165
166
    /**
167
     * run cron jobs
168
     *
169
     * @return self
170
     */
171
    public function process()
172
    {
173
        $em = $this->getEm();
174
        $cronRegistry = $this->getCronjobs();
175
        $pending = $this->getPending();
176
        $scheduleLifetime = $this->scheduleLifetime * 60; //convert min to sec
177
178
        $now = new \DateTime;
179
        foreach ($pending as $job) {
180
            $scheduleTime = $job->getScheduleTime();
181
182
            if ($scheduleTime > $now) {
183
                continue;
184
            }
185
186
            try {
187
                $errorStatus = Mapper\Cronjob::STATUS_ERROR;
188
189
                $missedTime = clone $now;
190
                $timestamp = $missedTime->getTimestamp();
191
                $timestamp -= $scheduleLifetime;
192
                $missedTime->setTimestamp($timestamp);
193
194
                if ($scheduleTime < $missedTime) {
195
                    $errorStatus = Mapper\Cronjob::STATUS_MISSED;
196
                    throw new Exception\RuntimeException(
197
                        'too late for job'
198
                    );
199
                }
200
201
                $code = $job->getCode();
202
203
                if (!isset($cronRegistry[$code])) {
204
                    throw new Exception\RuntimeException(sprintf(
205
                        'job "%s" undefined in cron registry',
206
                        $code
207
                    ));
208
                }
209
210
                if (!$this->tryLockJob($job)) {
211
                    //another cron started this job intermittently. skip.
212
                    continue;
213
                }
214
215
                //run job now
216
                $callback = $cronRegistry[$code]['callback'];
217
                $args = $cronRegistry[$code]['args'];
218
219
                $job->setExecuteTime(new \DateTime);
220
                $em->persist($job);
221
222
                call_user_func_array($callback, $args);
223
224
                $job
225
                    ->setStatus(Mapper\Cronjob::STATUS_SUCCESS)
226
                    ->setFinishTime(new \DateTime);
227
            } catch (\Exception $e) {
228
                $job
229
                    ->setStatus($errorStatus)
230
                    ->setErrorMsg($e->getMessage())
231
                    ->setStackTrace($e->getTraceAsString());
232
            }
233
234
            $em->persist($job);
235
        }
236
        $em->flush();
237
        
238
        return $this;
239
    }
240
241
    /**
242
     * schedule cron jobs
243
     *
244
     * @return self
245
     */
246
    public function schedule()
247
    {
248
        $em = $this->getEm();
249
        $pending = $this->getPending();
250
        $exists = array();
251
        foreach ($pending as $job) {
252
            $identifier = $job->getCode();
253
            $identifier .= $job->getScheduleTime()->getTimeStamp();
254
            $exists[$identifier] = true;
255
        }
256
257
        $scheduleAhead = $this->getScheduleAhead() * 60;
258
259
        $cronRegistry = $this->getCronjobs();
260
261
        foreach ($cronRegistry as $code => $item) {
262
            $now = time();
263
            $timeAhead = $now + $scheduleAhead;
264
265
            for ($time = $now; $time < $timeAhead; $time += 60) {
266
                $scheduleTime = new \DateTime();
267
                $scheduleTime->setTimestamp($time);
268
                $scheduleTime->setTime(
269
                    $scheduleTime->format('H'),
270
                    $scheduleTime->format('i')
271
                );
272
                $scheduleTimestamp = $scheduleTime->getTimestamp();
273
274
                $identifier = $code . $scheduleTimestamp;
275
                if (isset($exists[$identifier])) {
276
                    //already scheduled
277
                    continue;
278
                }
279
280
                $job = new Entity\Cronjob;
281
                if ($this->matchTime(
282
                    $scheduleTimestamp,
283
                    $item['frequency']
284
                )) {
285
                    $job
286
                        ->setCode($code)
287
                        ->setStatus(Mapper\Cronjob::STATUS_PENDING)
288
                        ->setCreateTime(new \DateTime)
289
                        ->setScheduleTime($scheduleTime);
290
                    $em->persist($job);
291
                    $exists[$identifier] = true;
292
                }
293
            }
294
        }
295
296
        $em->flush();
297
298
        return $this;
299
    }
300
301
    /**
302
     * perform various cleanup work
303
     *
304
     * @return self
305
     */
306
    public function cleanup()
307
    {
308
        $this
309
            ->recoverRunning()
310
            ->cleanLog();
311
312
        return $this;
313
    }
314
315
    public function recoverRunning()
316
    {
317
        $em = $this->getEm();
318
        $running = $em->getRepository('PlaygroundCore\Entity\Cronjob')
319
            ->getRunning();
320
        $expiryTime = time() - $this->getMaxRunningTime() * 60;
321
322
        foreach ($running as $job) {
323
            if ($job->getExecuteTime()
324
                && $job->getExecuteTime()->getTimestamp() < $expiryTime) {
325
                $job
326
                    ->setStatus(Mapper\Cronjob::STATUS_PENDING)
327
                    ->setErrorMsg(null)
328
                    ->setStackTrace(null)
329
                    ->setScheduleTime(new \DateTime)
330
                    ->setExecuteTime(null);
331
            }
332
        }
333
334
        $this->getEm()->flush();
335
336
        return $this;
337
    }
338
339
    /**
340
     * delete old cron job logs
341
     *
342
     * @return self
343
     */
344
    public function cleanLog()
345
    {
346
        $em = $this->getEm();
347
        $lifetime = array(
348
            Mapper\Cronjob::STATUS_SUCCESS  =>
349
                $this->getSuccessLogLifetime() * 60,
350
            Mapper\Cronjob::STATUS_MISSED   =>
351
                $this->getFailureLogLifetime() * 60,
352
            Mapper\Cronjob::STATUS_ERROR    =>
353
                $this->getFailureLogLifetime() * 60,
354
        );
355
356
        $history = $em->getRepository('PlaygroundCore\Entity\Cronjob')
357
            ->getHistory();
358
359
        $now = time();
360
        foreach ($history as $job) {
361
            if ($job->getExecuteTime()
362
                && $job->getExecuteTime()->getTimestamp()
363
                    < $now - $lifetime[$job->getStatus()]) {
364
                $em->remove($job);
365
            }
366
        }
367
368
        $em->flush();
369
370
        return $this;
371
    }
372
373
    /**
374
     * wrapper function
375
     * @see Registry::register()
376
     */
377
    public static function register(
378
        $code,
379
        $frequency,
380
        $callback,
381
        array $args = array()
382
    ) {
383
        Registry::register($code, $frequency, $callback, $args);
384
    }
385
386
    /**
387
     * try to acquire a lock on a cron job
388
     *
389
     * set a job to 'running' only if it is currently 'pending'
390
     *
391
     * @param  Entity\Cronjob $job
392
     * @return bool
393
     */
394
    public function tryLockJob(Entity\Cronjob $job)
395
    {
396
        $em = $this->getEm();
397
        if ($job->getStatus() === Mapper\Cronjob::STATUS_PENDING) {
398
            $job->setStatus(Mapper\Cronjob::STATUS_RUNNING);
399
            $em->persist($job);
400
            $em->flush();
401
402
            // flush() succeeded if reached here;
403
            // otherwise an Exception would have been thrown
404
            return true;
405
        }
406
407
        return false;
408
    }
409
410
    /**
411
     * determine whether a given time falls within the given cron expr
412
     *
413
     * @param string|numeric $time
414
     *      timestamp or strtotime()-compatible string
415
     * @param string $expr
416
     *      any valid cron expression, in addition supporting:
417
     *      range: '0-5'
418
     *      range + interval: '10-59/5'
419
     *      comma-separated combinations of these: '1,4,7,10-20'
420
     *      English months: 'january'
421
     *      English months (abbreviated to three letters): 'jan'
422
     *      English weekdays: 'monday'
423
     *      English weekdays (abbreviated to three letters): 'mon'
424
     *      These text counterparts can be used in all places where their
425
     *          numerical counterparts are allowed, e.g. 'jan-jun/2'
426
     *      A full example:
427
     *          '0-5,10-59/5 * 2-10,15-25 january-june/2 mon-fri' -
428
     *          every minute between minute 0-5 + every 5th min between 10-59
429
     *          every hour
430
     *          every day between day 2-10 and day 15-25
431
     *          every 2nd month between January-June
432
     *          Monday-Friday
433
     * @throws Exception\InvalidArgumentException on invalid cron expression
434
     * @return bool
435
     */
436
    public static function matchTime($time, $expr)
437
    {
438
        $cronExpr = preg_split('/\s+/', $expr, null, PREG_SPLIT_NO_EMPTY);
439
        if (count($cronExpr) !== 5) {
440
            throw new Exception\InvalidArgumentException(sprintf(
441
                'cron expression should have exactly 5 arguments, "%s" given',
442
                $expr
443
            ));
444
        }
445
446
        if (is_string($time)) {
447
            $time = strtotime($time);
448
        }
449
450
        $date = getdate($time);
451
452
        return self::matchTimeComponent($cronExpr[0], $date['minutes'])
453
        && self::matchTimeComponent($cronExpr[1], $date['hours'])
454
        && self::matchTimeComponent($cronExpr[2], $date['mday'])
455
        && self::matchTimeComponent($cronExpr[3], $date['mon'])
456
        && self::matchTimeComponent($cronExpr[4], $date['wday']);
457
    }
458
459
    /**
460
     * match a cron expression component to a given corresponding date/time
461
     *
462
     * In the expression, * * * * *, each component
463
     *      *[1] *[2] *[3] *[4] *[5]
464
     * will correspond to a getdate() component
465
     * 1. $date['minutes']
466
     * 2. $date['hours']
467
     * 3. $date['mday']
468
     * 4. $date['mon']
469
     * 5. $date['wday']
470
     *
471
     * @see self::exprToNumeric() for additional valid string values
472
     *
473
     * @param  string                             $expr
474
     * @param  numeric                            $num
475
     * @throws Exception\InvalidArgumentException on invalid expression
476
     * @return bool
477
     */
478
    public static function matchTimeComponent($expr, $num)
479
    {
480
        //handle all match
481
        if ($expr === '*') {
482
            return true;
483
        }
484
485
        //handle multiple options
486
        if (strpos($expr, ',') !== false) {
487
            $args = explode(',', $expr);
488
            foreach ($args as $arg) {
489
                if (self::matchTimeComponent($arg, $num)) {
490
                    return true;
491
                }
492
            }
493
494
            return false;
495
        }
496
497
        //handle modulus
498
        if (strpos($expr, '/') !== false) {
499
            $arg = explode('/', $expr);
500
            if (count($arg) !== 2) {
501
                throw new Exception\InvalidArgumentException(sprintf(
502
                    'invalid cron expression component: '
503
                    . 'expecting match/modulus, "%s" given',
504
                    $expr
505
                ));
506
            }
507
            if (!is_numeric($arg[1])) {
508
                throw new Exception\InvalidArgumentException(sprintf(
509
                    'invalid cron expression component: '
510
                    . 'expecting numeric modulus, "%s" given',
511
                    $expr
512
                ));
513
            }
514
515
            $expr = $arg[0];
516
            $mod = $arg[1];
517
        } else {
518
            $mod = 1;
519
        }
520
521
        //handle all match by modulus
522
        if ($expr === '*') {
523
            $from = 0;
524
            $to   = 60;
525
        } //handle range
526
        elseif (strpos($expr, '-') !== false) {
527
            $arg = explode('-', $expr);
528
            if (count($arg) !== 2) {
529
                throw new Exception\InvalidArgumentException(sprintf(
530
                    'invalid cron expression component: '
531
                    . 'expecting from-to structure, "%s" given',
532
                    $expr
533
                ));
534
            }
535 View Code Duplication
            if (!is_numeric($arg[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
536
                $from = self::exprToNumeric($arg[0]);
537
            } else {
538
                $from = $arg[0];
539
            }
540 View Code Duplication
            if (!is_numeric($arg[1])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
541
                $to = self::exprToNumeric($arg[1]);
542
            } else {
543
                $to = $arg[1];
544
            }
545
        } //handle regular token
546
        else {
547
            $from = self::exprToNumeric($expr);
548
            $to = $from;
549
        }
550
551
        if ($from === false || $to === false) {
552
            throw new Exception\InvalidArgumentException(sprintf(
553
                'invalid cron expression component: '
554
                . 'expecting numeric or valid string, "%s" given',
555
                $expr
556
            ));
557
        }
558
559
        return ($num >= $from) && ($num <= $to) && ($num % $mod === 0);
560
    }
561
562
    /**
563
     * parse a string month / weekday expression to its numeric equivalent
564
     *
565
     * @param string|numeric $value
566
     *      accepts, case insensitive,
567
     *      - Jan - Dec
568
     *      - Sun - Sat
569
     *      - (or their long forms - only the first three letters important)
570
     * @return int|false
571
     */
572
    public static function exprToNumeric($value)
573
    {
574
        static $data = array(
575
                'jan'   => 1,
576
                'feb'   => 2,
577
                'mar'   => 3,
578
                'apr'   => 4,
579
                'may'   => 5,
580
                'jun'   => 6,
581
                'jul'   => 7,
582
                'aug'   => 8,
583
                'sep'   => 9,
584
                'oct'   => 10,
585
                'nov'   => 11,
586
                'dec'   => 12,
587
588
                'sun'   => 0,
589
                'mon'   => 1,
590
                'tue'   => 2,
591
                'wed'   => 3,
592
                'thu'   => 4,
593
                'fri'   => 5,
594
                'sat'   => 6,
595
        );
596
597
        if (is_numeric($value)) {
598
            if (in_array((int) $value, $data, true)) {
599
                return $value;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $value; (integer|double|string) is incompatible with the return type documented by PlaygroundCore\Service\Cron::exprToNumeric of type integer|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
600
            } else {
601
                return false;
602
            }
603
        }
604
605
        if (is_string($value)) {
606
            $value = strtolower(substr($value, 0, 3));
607
            if (isset($data[$value])) {
608
                return $data[$value];
609
            }
610
        }
611
612
        return false;
613
    }
614
615
    public function setScheduleAhead($scheduleAhead)
616
    {
617
        $this->scheduleAhead = $scheduleAhead;
618
619
        return $this;
620
    }
621
622
    public function getScheduleAhead()
623
    {
624
        return $this->scheduleAhead;
625
    }
626
627
    public function setScheduleLifetime($scheduleLifetime)
628
    {
629
        $this->scheduleLifetime = $scheduleLifetime;
630
631
        return $this;
632
    }
633
634
    public function getScheduleLifeTime()
635
    {
636
        return $this->scheduleLifeTime;
0 ignored issues
show
Bug introduced by
The property scheduleLifeTime does not seem to exist. Did you mean scheduleLifetime?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
637
    }
638
639
    public function setMaxRunningTime($maxRunningTime)
640
    {
641
        $this->maxRunningTime = $maxRunningTime;
642
643
        return $this;
644
    }
645
646
    public function getMaxRunningtime()
647
    {
648
        return $this->maxRunningTime;
649
    }
650
651
    public function setSuccessLogLifetime($successLogLifetime)
652
    {
653
        $this->successLogLifetime = $successLogLifetime;
654
655
        return $this;
656
    }
657
    public function getSuccessLogLifetime()
658
    {
659
        return $this->successLogLifetime;
660
    }
661
662
    public function setFailureLogLifetime($failureLogLifetime)
663
    {
664
        $this->failureLogLifetime = $failureLogLifetime;
665
666
        return $this;
667
    }
668
669
    public function getFailureLogLifetime()
670
    {
671
        return $this->failureLogLifetime;
672
    }
673
674
    public function setEm(EntityManager $em)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
675
    {
676
        $this->em = $em;
677
678
        return $this;
679
    }
680
681
    public function getEm()
682
    {
683
        if (!$this->em) {
684
            $this->setEm($this->serviceLocator->get('playgroundcore_doctrine_em'));
0 ignored issues
show
Documentation introduced by
$this->serviceLocator->g...roundcore_doctrine_em') is of type object|array, but the function expects a object<Doctrine\ORM\EntityManager>.

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...
685
        }
686
687
        return $this->em;
688
    }
689
}
690