Issues (35)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

Entity/Job.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * Copyright 2012 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\JobQueueBundle\Entity;
20
21
use Doctrine\Common\Collections\ArrayCollection;
22
use Doctrine\ORM\Mapping as ORM;
23
use JMS\JobQueueBundle\Exception\InvalidStateTransitionException;
24
use JMS\JobQueueBundle\Exception\LogicException;
25
use Symfony\Component\ErrorHandler\Exception\FlattenException;
26
27
/**
28
 * @ORM\Entity
29
 * @ORM\Table(name = "jms_jobs", indexes = {
30
 *     @ORM\Index("cmd_search_index", columns = {"command"}),
31
 *     @ORM\Index("sorting_index", columns = {"state", "priority", "id"}),
32
 * })
33
 * @ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
34
 *
35
 * @author Johannes M. Schmitt <[email protected]>
36
 */
37
class Job
38
{
39
    /** State if job is inserted, but not yet ready to be started. */
40
    const STATE_NEW = 'new';
41
42
    /**
43
     * State if job is inserted, and might be started.
44
     *
45
     * It is important to note that this does not automatically mean that all
46
     * jobs of this state can actually be started, but you have to check
47
     * isStartable() to be absolutely sure.
48
     *
49
     * In contrast to NEW, jobs of this state at least might be started,
50
     * while jobs of state NEW never are allowed to be started.
51
     */
52
    const STATE_PENDING = 'pending';
53
54
    /** State if job was never started, and will never be started. */
55
    const STATE_CANCELED = 'canceled';
56
57
    /** State if job was started and has not exited, yet. */
58
    const STATE_RUNNING = 'running';
59
60
    /** State if job exists with a successful exit code. */
61
    const STATE_FINISHED = 'finished';
62
63
    /** State if job exits with a non-successful exit code. */
64
    const STATE_FAILED = 'failed';
65
66
    /** State if job exceeds its configured maximum runtime. */
67
    const STATE_TERMINATED = 'terminated';
68
69
    /**
70
     * State if an error occurs in the runner command.
71
     *
72
     * The runner command is the command that actually launches the individual
73
     * jobs. If instead an error occurs in the job command, this will result
74
     * in a state of FAILED.
75
     */
76
    const STATE_INCOMPLETE = 'incomplete';
77
78
    const DEFAULT_QUEUE = 'default';
79
    const MAX_QUEUE_LENGTH = 50;
80
81
    const PRIORITY_LOW = -5;
82
    const PRIORITY_DEFAULT = 0;
83
    const PRIORITY_HIGH = 5;
84
85
    /** @ORM\Id @ORM\GeneratedValue(strategy = "AUTO") @ORM\Column(type = "bigint", options = {"unsigned": true}) */
86
    private $id;
87
88
    /** @ORM\Column(type = "string", length = 15) */
89
    private $state;
90
91
    /** @ORM\Column(type = "string", length = Job::MAX_QUEUE_LENGTH) */
92
    private $queue;
93
94
    /** @ORM\Column(type = "smallint") */
95
    private $priority = 0;
96
97
    /** @ORM\Column(type = "datetime", name="createdAt") */
98
    private $createdAt;
99
100
    /** @ORM\Column(type = "datetime", name="startedAt", nullable = true) */
101
    private $startedAt;
102
103
    /** @ORM\Column(type = "datetime", name="checkedAt", nullable = true) */
104
    private $checkedAt;
105
106
    /** @ORM\Column(type = "string", name="workerName", length = 50, nullable = true) */
107
    private $workerName;
108
109
    /** @ORM\Column(type = "datetime", name="executeAfter", nullable = true) */
110
    private $executeAfter;
111
112
    /** @ORM\Column(type = "datetime", name="closedAt", nullable = true) */
113
    private $closedAt;
114
115
    /** @ORM\Column(type = "string") */
116
    private $command;
117
118
    /** @ORM\Column(type = "json") */
119
    private $args = [];
120
121
    /**
122
     * @ORM\ManyToMany(targetEntity = "Job", fetch = "EAGER")
123
     * @ORM\JoinTable(name="jms_job_dependencies",
124
     *     joinColumns = { @ORM\JoinColumn(name = "source_job_id", referencedColumnName = "id") },
125
     *     inverseJoinColumns = { @ORM\JoinColumn(name = "dest_job_id", referencedColumnName = "id")}
126
     * )
127
     */
128
    private $dependencies;
129
130
    /** @ORM\Column(type = "text", nullable = true) */
131
    private $output;
132
133
    /** @ORM\Column(type = "text", name="errorOutput", nullable = true) */
134
    private $errorOutput;
135
136
    /** @ORM\Column(type = "smallint", name="exitCode", nullable = true, options = {"unsigned": true}) */
137
    private $exitCode;
138
139
    /** @ORM\Column(type = "smallint", name="maxRuntime", options = {"unsigned": true}) */
140
    private $maxRuntime = 0;
141
142
    /** @ORM\Column(type = "smallint", name="maxRetries", options = {"unsigned": true}) */
143
    private $maxRetries = 0;
144
145
    /**
146
     * @ORM\ManyToOne(targetEntity = "Job", inversedBy = "retryJobs")
147
     * @ORM\JoinColumn(name="originalJob_id", referencedColumnName="id")
148
     */
149
    private $originalJob;
150
151
    /** @ORM\OneToMany(targetEntity = "Job", mappedBy = "originalJob", cascade = {"persist", "remove", "detach", "refresh"}) */
152
    private $retryJobs;
153
154
    /** @ORM\Column(type = "jms_job_safe_object", name="stackTrace", nullable = true) */
155
    private $stackTrace;
156
157
    /** @ORM\Column(type = "smallint", nullable = true, options = {"unsigned": true}) */
158
    private $runtime;
159
160
    /** @ORM\Column(type = "integer", name="memoryUsage", nullable = true, options = {"unsigned": true}) */
161
    private $memoryUsage;
162
163
    /** @ORM\Column(type = "integer", name="memoryUsageReal", nullable = true, options = {"unsigned": true}) */
164
    private $memoryUsageReal;
165
166
    /**
167
     * This may store any entities which are related to this job, and are
168
     * managed by Doctrine.
169
     *
170
     * It is effectively a many-to-any association.
171
     */
172
    private $relatedEntities;
173
174
    public static function create($command, array $args = array(), $confirmed = true, $queue = self::DEFAULT_QUEUE, $priority = self::PRIORITY_DEFAULT)
175
    {
176
        return new self($command, $args, $confirmed, $queue, $priority);
177
    }
178
179 30
    public static function isNonSuccessfulFinalState($state)
180
    {
181 30
        return in_array($state, array(self::STATE_CANCELED, self::STATE_FAILED, self::STATE_INCOMPLETE, self::STATE_TERMINATED), true);
182
    }
183
184
    public static function getStates()
185
    {
186
        return array(
187
            self::STATE_NEW,
188
            self::STATE_PENDING,
189
            self::STATE_CANCELED,
190
            self::STATE_RUNNING,
191
            self::STATE_FINISHED,
192
            self::STATE_FAILED,
193
            self::STATE_TERMINATED,
194
            self::STATE_INCOMPLETE
195
        );
196
    }
197
198 74
    public function __construct($command, array $args = array(), $confirmed = true, $queue = self::DEFAULT_QUEUE, $priority = self::PRIORITY_DEFAULT)
199
    {
200 74
        if (trim($queue) === '') {
201
            throw new \InvalidArgumentException('$queue must not be empty.');
202
        }
203 74
        if (strlen($queue) > self::MAX_QUEUE_LENGTH) {
204
            throw new \InvalidArgumentException(sprintf('The maximum queue length is %d, but got "%s" (%d chars).', self::MAX_QUEUE_LENGTH, $queue, strlen($queue)));
205
        }
206
207 74
        $this->command = $command;
208 74
        $this->args = $args;
209 74
        $this->state = $confirmed ? self::STATE_PENDING : self::STATE_NEW;
210 74
        $this->queue = $queue;
211 74
        $this->priority = $priority * -1;
212 74
        $this->createdAt = new \DateTime();
213 74
        $this->executeAfter = new \DateTime();
214 74
        $this->executeAfter = $this->executeAfter->modify('-1 second');
215 74
        $this->dependencies = new ArrayCollection();
216 74
        $this->retryJobs = new ArrayCollection();
217 74
        $this->relatedEntities = new ArrayCollection();
218 74
    }
219
220 8
    public function __clone()
221
    {
222 8
        $this->state = self::STATE_PENDING;
223 8
        $this->createdAt = new \DateTime();
224 8
        $this->startedAt = null;
225 8
        $this->checkedAt = null;
226 8
        $this->closedAt = null;
227 8
        $this->workerName = null;
228 8
        $this->output = null;
229 8
        $this->errorOutput = null;
230 8
        $this->exitCode = null;
231 8
        $this->stackTrace = null;
232 8
        $this->runtime = null;
233 8
        $this->memoryUsage = null;
234 8
        $this->memoryUsageReal = null;
235 8
        $this->relatedEntities = new ArrayCollection();
236 8
    }
237
238 44
    public function getId()
239
    {
240 44
        return $this->id;
241
    }
242
243 36
    public function getState()
244
    {
245 36
        return $this->state;
246
    }
247
248 28
    public function setWorkerName($workerName)
249
    {
250 28
        $this->workerName = $workerName;
251
252 28
        return $this;
253
    }
254
255 2
    public function getWorkerName()
256
    {
257 2
        return $this->workerName;
258
    }
259
260 4
    public function getPriority()
261
    {
262 4
        return $this->priority * -1;
263
    }
264
265 30
    public function isInFinalState()
266
    {
267 30
        return !$this->isNew() && !$this->isPending() && !$this->isRunning();
268
    }
269
270 28
    public function isStartable()
271
    {
272 28
        foreach ($this->dependencies as $dep) {
273 4
            if ($dep->getState() !== self::STATE_FINISHED) {
274 4
                return false;
275
            }
276
        }
277
278 28
        return true;
279
    }
280
281 50
    public function setState($newState)
282
    {
283 50
        if ($newState === $this->state) {
284 2
            return $this;
285
        }
286
287 50
        switch ($this->state) {
288 50
            case self::STATE_NEW:
289 View Code Duplication
                if (!in_array($newState, array(self::STATE_PENDING, self::STATE_CANCELED), true)) {
0 ignored issues
show
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...
290
                    throw new InvalidStateTransitionException($this, $newState, array(self::STATE_PENDING, self::STATE_CANCELED));
291
                }
292
293
                if (self::STATE_CANCELED === $newState) {
294
                    $this->closedAt = new \DateTime();
295
                }
296
297
                break;
298
299 50
            case self::STATE_PENDING:
300 50 View Code Duplication
                if (!in_array($newState, array(self::STATE_RUNNING, self::STATE_CANCELED), true)) {
0 ignored issues
show
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...
301 2
                    throw new InvalidStateTransitionException($this, $newState, array(self::STATE_RUNNING, self::STATE_CANCELED));
302
                }
303
304 48
                if ($newState === self::STATE_RUNNING) {
305 46
                    $this->startedAt = new \DateTime();
306 46
                    $this->checkedAt = new \DateTime();
307 6
                } else if ($newState === self::STATE_CANCELED) {
308 6
                    $this->closedAt = new \DateTime();
309
                }
310
311 48
                break;
312
313 34
            case self::STATE_RUNNING:
314 34
                if (!in_array($newState, array(self::STATE_FINISHED, self::STATE_FAILED, self::STATE_TERMINATED, self::STATE_INCOMPLETE))) {
315
                    throw new InvalidStateTransitionException($this, $newState, array(self::STATE_FINISHED, self::STATE_FAILED, self::STATE_TERMINATED, self::STATE_INCOMPLETE));
316
                }
317
318 34
                $this->closedAt = new \DateTime();
319
320 34
                break;
321
322
            case self::STATE_FINISHED:
323
            case self::STATE_FAILED:
324
            case self::STATE_TERMINATED:
325
            case self::STATE_INCOMPLETE:
326
                throw new InvalidStateTransitionException($this, $newState);
327
328
            default:
329
                throw new LogicException('The previous cases were exhaustive. Unknown state: ' . $this->state);
330
        }
331
332 48
        $this->state = $newState;
333
334 48
        return $this;
335
    }
336
337 2
    public function getCreatedAt()
338
    {
339 2
        return $this->createdAt;
340
    }
341
342
    public function getClosedAt()
343
    {
344
        return $this->closedAt;
345
    }
346
347
    public function getExecuteAfter()
348
    {
349
        return $this->executeAfter;
350
    }
351
352 4
    public function setExecuteAfter(\DateTime $executeAfter)
353
    {
354 4
        $this->executeAfter = $executeAfter;
355
356 4
        return $this;
357
    }
358
359 28
    public function getCommand()
360
    {
361 28
        return $this->command;
362
    }
363
364 28
    public function getArgs()
365
    {
366 28
        return (array) $this->args;
367
    }
368
369 2
    public function getRelatedEntities()
370
    {
371 2
        return $this->relatedEntities;
372
    }
373
374 30
    public function isClosedNonSuccessful()
375
    {
376 30
        return self::isNonSuccessfulFinalState($this->state);
377
    }
378
379 2
    public function findRelatedEntity($class)
380
    {
381 2
        foreach ($this->relatedEntities as $entity) {
382 2
            if ($entity instanceof $class) {
383 2
                return $entity;
384
            }
385
        }
386
387
        return null;
388
    }
389
390 4
    public function addRelatedEntity($entity)
391
    {
392 4
        if (!is_object($entity)) {
393
            throw new \RuntimeException(sprintf('$entity must be an object.'));
394
        }
395
396 4
        if ($this->relatedEntities->contains($entity)) {
397
            return $this;
398
        }
399
400 4
        $this->relatedEntities->add($entity);
401
402 4
        return $this;
403
    }
404
405 6
    public function getDependencies()
406
    {
407 6
        return $this->dependencies;
408
    }
409
410 2
    public function hasDependency(Job $job)
411
    {
412 2
        return $this->dependencies->contains($job);
413
    }
414
415 20
    public function addDependency(Job $job)
416
    {
417 20
        if ($this->dependencies->contains($job)) {
418 2
            return $this;
419
        }
420
421 20
        if ($this->mightHaveStarted()) {
422 2
            throw new \LogicException('You cannot add dependencies to a job which might have been started already.');
423
        }
424
425 18
        $this->dependencies->add($job);
426
427 18
        return $this;
428
    }
429
430
    public function getRuntime()
431
    {
432
        return $this->runtime;
433
    }
434
435 22
    public function setRuntime($time)
436
    {
437 22
        $this->runtime = (int) $time;
438
439 22
        return $this;
440
    }
441
442 2
    public function getMemoryUsage()
443
    {
444 2
        return $this->memoryUsage;
445
    }
446
447 2
    public function getMemoryUsageReal()
448
    {
449 2
        return $this->memoryUsageReal;
450
    }
451
452 6
    public function addOutput($output)
453
    {
454 6
        $this->output .= $output;
455
456 6
        return $this;
457
    }
458
459 6
    public function addErrorOutput($output)
460
    {
461 6
        $this->errorOutput .= $output;
462
463 6
        return $this;
464
    }
465
466 24
    public function setOutput($output)
467
    {
468 24
        $this->output = $output;
469
470 24
        return $this;
471
    }
472
473 24
    public function setErrorOutput($output)
474
    {
475 24
        $this->errorOutput = $output;
476
477 24
        return $this;
478
    }
479
480 6
    public function getOutput()
481
    {
482 6
        return $this->output;
483
    }
484
485 6
    public function getErrorOutput()
486
    {
487 6
        return $this->errorOutput;
488
    }
489
490 22
    public function setExitCode($code)
491
    {
492 22
        $this->exitCode = $code;
493
494 22
        return $this;
495
    }
496
497 2
    public function getExitCode()
498
    {
499 2
        return $this->exitCode;
500
    }
501
502 6
    public function setMaxRuntime($time)
503
    {
504 6
        $this->maxRuntime = (int) $time;
505
506 6
        return $this;
507
    }
508
509 26
    public function getMaxRuntime()
510
    {
511 26
        return $this->maxRuntime;
512
    }
513
514 28
    public function getStartedAt()
515
    {
516 28
        return $this->startedAt;
517
    }
518
519
    public function getMaxRetries()
520
    {
521
        return $this->maxRetries;
522
    }
523
524 8
    public function setMaxRetries($tries)
525
    {
526 8
        $this->maxRetries = (int) $tries;
527
528 8
        return $this;
529
    }
530
531 14
    public function isRetryAllowed()
532
    {
533
        // If no retries are allowed, we can bail out directly, and we
534
        // do not need to initialize the retryJobs relation.
535 14
        if (0 === $this->maxRetries) {
536 10
            return false;
537
        }
538
539 6
        return count($this->retryJobs) < $this->maxRetries;
540
    }
541
542 6
    public function getOriginalJob()
543
    {
544 6
        if (null === $this->originalJob) {
545 2
            return $this;
546
        }
547
548 6
        return $this->originalJob;
549
    }
550
551 8
    public function setOriginalJob(Job $job)
552
    {
553 8
        if (self::STATE_PENDING !== $this->state) {
554
            throw new \LogicException($this . ' must be in state "PENDING".');
555
        }
556
557 8
        if (null !== $this->originalJob) {
558
            throw new \LogicException($this . ' already has an original job set.');
559
        }
560
561 8
        $this->originalJob = $job;
562
563 8
        return $this;
564
    }
565
566 8
    public function addRetryJob(Job $job)
567
    {
568 8
        if (self::STATE_RUNNING !== $this->state) {
569
            throw new \LogicException('Retry jobs can only be added to running jobs.');
570
        }
571
572 8
        $job->setOriginalJob($this);
573 8
        $this->retryJobs->add($job);
574
575 8
        return $this;
576
    }
577
578 36
    public function getRetryJobs()
579
    {
580 36
        return $this->retryJobs;
581
    }
582
583 32
    public function isRetryJob()
584
    {
585 32
        return null !== $this->originalJob;
586
    }
587
588
    public function isRetried()
589
    {
590
        foreach ($this->retryJobs as $job) {
591
            /** @var Job $job */
592
593
            if (!$job->isInFinalState()) {
594
                return true;
595
            }
596
        }
597
598
        return false;
599
    }
600
601 6
    public function checked()
602
    {
603 6
        $this->checkedAt = new \DateTime();
604 6
    }
605
606 2
    public function getCheckedAt()
607
    {
608 2
        return $this->checkedAt;
609
    }
610
611
    public function setStackTrace(FlattenException $ex)
612
    {
613
        $this->stackTrace = $ex;
614
615
        return $this;
616
    }
617
618 2
    public function getStackTrace()
619
    {
620 2
        return $this->stackTrace;
621
    }
622
623 28
    public function getQueue()
624
    {
625 28
        return $this->queue;
626
    }
627
628 30
    public function isNew()
629
    {
630 30
        return self::STATE_NEW === $this->state;
631
    }
632
633 30
    public function isPending()
634
    {
635 30
        return self::STATE_PENDING === $this->state;
636
    }
637
638
    public function isCanceled()
639
    {
640
        return self::STATE_CANCELED === $this->state;
641
    }
642
643 28
    public function isRunning()
644
    {
645 28
        return self::STATE_RUNNING === $this->state;
646
    }
647
648
    public function isTerminated()
649
    {
650
        return self::STATE_TERMINATED === $this->state;
651
    }
652
653
    public function isFailed()
654
    {
655
        return self::STATE_FAILED === $this->state;
656
    }
657
658
    public function isFinished()
659
    {
660
        return self::STATE_FINISHED === $this->state;
661
    }
662
663
    public function isIncomplete()
664
    {
665
        return self::STATE_INCOMPLETE === $this->state;
666
    }
667
668 24
    public function __toString()
669
    {
670 24
        return sprintf('Job(id = %s, command = "%s")', $this->id, $this->command);
671
    }
672
673 20
    private function mightHaveStarted()
674
    {
675 20
        if (null === $this->id) {
676 18
            return false;
677
        }
678
679 2
        if (self::STATE_NEW === $this->state) {
680
            return false;
681
        }
682
683 2
        if (self::STATE_PENDING === $this->state && !$this->isStartable()) {
684
            return false;
685
        }
686
687 2
        return true;
688
    }
689
}
690