CronExpression::setExpression()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 9.7666
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
/**
4
 * NOTICE OF LICENSE
5
 *
6
 * This source file is subject to the Open Software License (OSL 3.0)
7
 * that is available through the world-wide-web at this URL:
8
 * http://opensource.org/licenses/osl-3.0.php
9
 *
10
 * Some of this work is derived from mtdowling/cron-expression which is copyrighted as:
11
 * Copyright (c) 2011 Michael Dowling <[email protected]> and contributors
12
 * The licence of this work can be found here: https://github.com/mtdowling/cron-expression/blob/master/LICENSE
13
 *
14
 * Some limitations might apply.
15
 *
16
 * PHP version 5
17
 *
18
 * @category  Library
19
 * @package   Microcron
20
 * @author    Bernhard Wick <[email protected]>
21
 * @copyright 2015 TechDivision GmbH - <[email protected]>
22
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
23
 * @link      http://www.appserver.io/
24
 */
25
26
namespace AppserverIo\Microcron;
27
28
use Cron\CronExpression as SimpleCron;
29
use Cron\FieldFactory as SimpleFieldFactory;
30
31
/**
32
 * AppserverIo\Microcron
33
 *
34
 * CRON expression parser that can determine whether or not a CRON expression is
35
 * due to run, the next run date and previous run date of a CRON expression.
36
 * This class is designed to be used every second
37
 *
38
 * Schedule parts must map to:
39
 * minute [0-59], hour [0-23], day of month, month [1-12|JAN-DEC], day of week
40
 * [1-7|MON-SUN], and an optional year.
41
 *
42
 * @category  Library
43
 * @package   Microcron
44
 * @author    Bernhard Wick <[email protected]>
45
 * @copyright 2015 TechDivision GmbH - <[email protected]>
46
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
47
 * @link      http://www.appserver.io/
48
 */
49
class CronExpression extends SimpleCron
50
{
51
    /**
52
     * Constants which define the position of semantic pieces of a DateTime object
53
     *
54
     * @var string
55
     */
56
    const SECOND = 0;
57
    const MINUTE = 1;
58
    const HOUR = 2;
59
    const DAY = 3;
60
    const MONTH = 4;
61
    const WEEKDAY = 5;
62
    const YEAR = 6;
63
64
    /**
65
     * CRON expression parts
66
     *
67
     * @var array $cronParts
68
     */
69
    protected $cronParts;
70
71
    /**
72
     *  CRON field factory
73
     *
74
     * @var FieldFactory $fieldFactory
75
     */
76
    protected $fieldFactory;
77
78
    /**
79
     * Order in which to test of cron parts
80
     *
81
     * @var array $order
82
     */
83
    protected static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE, self::SECOND);
84
85
    /**
86
     * Parse a CRON expression
87
     *
88
     * @param string             $expression   CRON expression (e.g. '8 * * * *')
89
     * @param SimpleFieldFactory $fieldFactory Factory to create cron fields
90
     */
91 8
    public function __construct($expression, SimpleFieldFactory $fieldFactory)
92
    {
93 8
        $this->fieldFactory = $fieldFactory;
0 ignored issues
show
Documentation Bug introduced by
$fieldFactory is of type object<Cron\FieldFactory>, but the property $fieldFactory was declared to be of type object<AppserverIo\Microcron\FieldFactory>. 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...
94 8
        $this->setExpression($expression);
95 7
    }
96
97
    /**
98
     * Factory method to create a new CronExpression.
99
     * There are  several special predefined values which can be used to substitute the
100
     * CRON expression. You might see them below
101
     *
102
     * @param string             $expression   The CRON expression to create
103
     * @param SimpleFieldFactory $fieldFactory (optional) Field factory to use
104
     *
105
     *      @yearly, @annually) - Run once a year, midnight, Jan. 1 - 0 0 0 1 1 *
106
     *      @monthly - Run once a month, midnight, first of month - 0 0 0 1 * *
107
     *      @weekly - Run once a week, midnight on Sun - 0 0 0 * * 0
108
     *      @daily - Run once a day, midnight - 0 0 0 * * *
109
     *      @hourly - Run once an hour, first minute - 0 0 * * * *
110
     *      @byMinute - Run once a minute, first second - 0 * * * * *
111
     *      @bySecond - Run once a second - * * * * * *
112
     *
113
     * @return CronExpression
114
     */
115 3
    public static function factory($expression, SimpleFieldFactory $fieldFactory = null)
116
    {
117
        $mappings = array(
118 3
            '@yearly' => '0 0 0 1 1 *',
119
            '@annually' => '0 0 0 1 1 *',
120
            '@monthly' => '0 0 0 1 * *',
121
            '@weekly' => '0 0 0 * * 0',
122
            '@daily' => '0 0 0 * * *',
123
            '@hourly' => '0 0 * * * *',
124
            '@byMinute' => '0 * * * * *',
125
            '@bySecond' => '* * * * * *'
126
        );
127
128 3
        if (isset($mappings[$expression])) {
129 3
            $expression = $mappings[$expression];
130
        }
131
132 3
        return new static($expression, $fieldFactory ?: new FieldFactory());
133
    }
134
135
    /**
136
     * Get all or part of the CRON expression
137
     *
138
     * @param string $part (optional) Specify the part to retrieve or NULL to
139
     *                     get the full cron schedule string.
140
     *
141
     * @return string|null Returns the CRON expression, a part of the
142
     *      CRON expression, or NULL if the part was specified but not found
143
     */
144 7
    public function getExpression($part = null)
145
    {
146 7
        if (null === $part) {
147 1
            return implode(' ', $this->cronParts);
148 7
        } elseif (array_key_exists($part, $this->cronParts)) {
149 7
            return $this->cronParts[$part];
150
        }
151
152 3
        return null;
153
    }
154
155
    /**
156
     * Get the next or previous run date of the expression relative to a date
157
     *
158
     * @param string|\DateTime $currentTime      (optional) Relative calculation date
159
     * @param int              $nth              (optional) Number of matches to skip before returning
160
     * @param bool             $invert           (optional) Set to TRUE to go backwards in time
161
     * @param bool             $allowCurrentDate (optional) Set to TRUE to return the
162
     *                                           current date if it matches the cron expression
163
     *
164
     * @return \DateTime
165
     * @throws \RuntimeException on too many iterations
166
     */
167 59
    protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false)
168
    {
169 59
        if ($currentTime instanceof \DateTime) {
170 11
            $currentDate = $currentTime;
171
        } else {
172 57
            $currentDate = new \DateTime($currentTime ?: 'now');
173 57
            $currentDate->setTimezone(new \DateTimeZone(date_default_timezone_get()));
174
        }
175
176 59
        $currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), $currentDate->format('s'));
177 59
        $nextRun = clone $currentDate;
178 59
        $nth = (int) $nth;
179
180
        // Set a hard limit to bail on an impossible date
181 59
        for ($i = 0; $i < 1000; $i++) {
182 59
            foreach (self::$order as $position) {
183 59
                $part = $this->getExpression($position);
184 59
                if (null === $part) {
185 58
                    continue;
186
                }
187
188 59
                $satisfied = false;
189
                // Get the field object used to validate this part
190 59
                $field = $this->fieldFactory->getField($position);
191
                // Check if this is singular or a list
192 59
                if (strpos($part, ',') === false) {
193 59
                    $satisfied = $field->isSatisfiedBy($nextRun, $part);
194
                } else {
195 6
                    foreach (array_map('trim', explode(',', $part)) as $listPart) {
196 6
                        if ($field->isSatisfiedBy($nextRun, $listPart)) {
197 6
                            $satisfied = true;
198 6
                            break;
199
                        }
200
                    }
201
                }
202
203
                // If the field is not satisfied, then start over
204 59
                if (!$satisfied) {
205 45
                    $field->increment($nextRun, $invert);
206 59
                    continue 2;
207
                }
208
            }
209
210
            // Skip this match if needed
211 59
            if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
212 4
                $this->fieldFactory->getField(0)->increment($nextRun, $invert);
213 4
                continue;
214
            }
215
216 59
            return $nextRun;
217
        }
218
219
        // @codeCoverageIgnoreStart
220
        throw new \RuntimeException('Impossible CRON expression');
221
        // @codeCoverageIgnoreEnd
222
    }
223
224
    /**
225
     * Determine if the CRON is due to run based, on the current date or a
226
     * specific date
227
     *
228
     * @param string|\DateTime $currentTime (optional) Relative calculation date
229
     *
230
     * @return bool Returns TRUE if the cron is due to run or FALSE if not
231
     */
232 55
    public function isDue($currentTime = null)
233
    {
234 55
        if (null === $currentTime || 'now' === $currentTime) {
235 1
            $currentDate = date('Y-m-d H:i:s');
236 1
            $currentTime = strtotime($currentDate);
237 55
        } elseif ($currentTime instanceof \DateTime) {
238 9
            $currentDate = $currentTime->format('Y-m-d H:i:s');
239 9
            $currentTime = strtotime($currentDate);
240
        } else {
241 47
            $currentTime = new \DateTime($currentTime);
242 47
            $currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), $currentTime->format('s'));
243 47
            $currentDate = $currentTime->format('Y-m-d H:i:s');
244 47
            $currentTime = $currentTime->getTimeStamp();
245
        }
246
247 55
        return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime;
248
    }
249
250
    /**
251
     * Set or change the CRON expression
252
     *
253
     * @param string $expression CRON expression (e.g. 8 * * * *)
254
     *
255
     * @return CronExpression
256
     * @throws \InvalidArgumentException if not a valid CRON expression
257
     */
258 3
    public function setExpression($expression)
259
    {
260 3
        $this->cronParts = preg_split('/\s/', $expression, -1, PREG_SPLIT_NO_EMPTY);
261 3
        if (count($this->cronParts) < 6) {
262 1
            throw new \InvalidArgumentException(
263 1
                $expression . ' is not a valid CRON expression'
264
            );
265
        }
266
267 2
        foreach ($this->cronParts as $position => $part) {
268 2
            $this->setPart($position, $part);
269
        }
270
271 2
        return $this;
272
    }
273
274
    /**
275
     * Set part of the CRON expression
276
     *
277
     * @param int    $position The position of the CRON expression to set
278
     * @param string $value    The value to set
279
     *
280
     * @return \Cron\CronExpression
281
     * @throws \InvalidArgumentException if the value is not valid for the part
282
     */
283 3
    public function setPart($position, $value)
284
    {
285 3
        if (!$this->fieldFactory->getField($position)->validate($value)) {
286 1
            throw new \InvalidArgumentException(
287 1
                'Invalid CRON field value ' . $value . ' as position ' . $position
288
            );
289
        }
290
291 3
        $this->cronParts[$position] = $value;
292
293 3
        return $this;
294
    }
295
}
296