Completed
Pull Request — master (#13)
by Jitendra
01:53
created

Expression::__construct()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 3
nc 2
nop 1
1
<?php
2
3
/*
4
 * This file is part of the PHP-CRON-EXPR package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc\Cron;
13
14
/**
15
 * Cron Expression Parser.
16
 *
17
 * This class checks if a cron expression is due to run on given timestamp (or default now).
18
 * Acknowledgement: The initial idea came from {@link http://stackoverflow.com/a/5727346}.
19
 *
20
 * @author Jitendra Adhikari <[email protected]>
21
 */
22
class Expression
23
{
24
    /** @var Expression */
25
    protected static $instance;
26
27
    /** @var SegmentChecker */
28
    protected $checker;
29
30
    protected static $expressions = [
31
        '@yearly'    => '0 0 1 1 *',
32
        '@annually'  => '0 0 1 1 *',
33
        '@monthly'   => '0 0 1 * *',
34
        '@weekly'    => '0 0 * * 0',
35
        '@daily'     => '0 0 * * *',
36
        '@hourly'    => '0 * * * *',
37
        '@always'    => '* * * * *',
38
        '@5minutes'  => '*/5 * * * *',
39
        '@10minutes' => '*/10 * * * *',
40
        '@15minutes' => '*/15 * * * *',
41
        '@30minutes' => '0,30 * * * *',
42
    ];
43
44
    protected static $literals = [
45
        'sun' => 0,
46
        'mon' => 1,
47
        'tue' => 2,
48
        'wed' => 3,
49
        'thu' => 4,
50
        'fri' => 5,
51
        'sat' => 6,
52
        'jan' => 1,
53
        'feb' => 2,
54
        'mar' => 3,
55
        'apr' => 4,
56
        'may' => 5,
57
        'jun' => 6,
58
        'jul' => 7,
59
        'aug' => 8,
60
        'sep' => 9,
61
        'oct' => 10,
62
        'nov' => 11,
63
        'dec' => 12,
64
    ];
65
66
    public function __construct(SegmentChecker $checker = null)
67
    {
68
        $this->checker = $checker ?: new SegmentChecker;
69
70
        if (null === static::$instance) {
71
            static::$instance = $this;
72
        }
73
    }
74
75
    public function instance()
76
    {
77
        if (null === static::$instance) {
78
            static::$instance = new static;
79
        }
80
81
        return static::$instance;
82
    }
83
84
    /**
85
     * Parse cron expression to decide if it can be run on given time (or default now).
86
     *
87
     * @param string $expr The cron expression.
88
     * @param int    $time The timestamp to validate the cron expr against. Defaults to now.
89
     *
90
     * @return bool
91
     */
92
    public static function isDue($expr, $time = null)
93
    {
94
        return static::instance()->isCronDue($expr, $time);
0 ignored issues
show
Bug Best Practice introduced by
The method Ahc\Cron\Expression::instance() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

94
        return static::/** @scrutinizer ignore-call */ instance()->isCronDue($expr, $time);
Loading history...
95
    }
96
97
    /**
98
     * Filter only the jobs that are due.
99
     *
100
     * @param array $jobs Jobs with cron exprs. [job1 => cron-expr1, job2 => cron-expr2, ...]
101
     * @param mixed $time The timestamp to validate the cron expr against. Defaults to now.
102
     *
103
     * @return array Due job names: [job1name, ...];
104
     */
105
    public static function getDues(array $jobs, $time = null)
106
    {
107
        return static::instance()->filter($jobs, $time);
0 ignored issues
show
Bug Best Practice introduced by
The method Ahc\Cron\Expression::instance() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
        return static::/** @scrutinizer ignore-call */ instance()->filter($jobs, $time);
Loading history...
108
    }
109
110
    /**
111
     * Instance call.
112
     *
113
     * Parse cron expression to decide if it can be run on given time (or default now).
114
     *
115
     * @param string $expr The cron expression.
116
     * @param mixed  $time The timestamp to validate the cron expr against. Defaults to now.
117
     *
118
     * @return bool
119
     */
120
    public function isCronDue($expr, $time = null)
121
    {
122
        list($expr, $times) = $this->process($expr, $time);
123
124
        foreach ($expr as $pos => $segment) {
125
            if ($segment === '*' || $segment === '?') {
126
                continue;
127
            }
128
129
            if (!$this->checker->checkDue($segment, $pos, $times)) {
130
                return false;
131
            }
132
        }
133
134
        return true;
135
    }
136
137
    /**
138
     * Filter only the jobs that are due.
139
     *
140
     * @param array $jobs Jobs with cron exprs. [job1 => cron-expr1, job2 => cron-expr2, ...]
141
     * @param mixed $time The timestamp to validate the cron expr against. Defaults to now.
142
     *
143
     * @return array Due job names: [job1name, ...];
144
     */
145
    public function filter(array $jobs, $time = null)
146
    {
147
        $dues = $cache = [];
148
        $time = $this->normalizeTime($time);
149
150
        foreach ($jobs as $name => $expr) {
151
            $expr = $this->normalizeExpr($expr);
152
153
            if (!isset($cache[$expr])) {
154
                $cache[$expr] = $this->isCronDue($expr, $time);
155
            }
156
157
            if ($cache[$expr]) {
158
                $dues[] = $name;
159
            }
160
        }
161
162
        return $dues;
163
    }
164
165
    /**
166
     * Process and prepare input.
167
     *
168
     * @param string $expr
169
     * @param mixed  $time
170
     *
171
     * @return array
172
     */
173
    protected function process($expr, $time)
174
    {
175
        $expr = $this->normalizeExpr($expr);
176
        $expr = \str_ireplace(\array_keys(static::$literals), \array_values(static::$literals), $expr);
177
        $expr = \explode(' ', $expr);
178
179
        if (\count($expr) < 5 || \count($expr) > 6) {
180
            throw new \UnexpectedValueException(
181
                'Cron $expr should have 5 or 6 segments delimited by space'
182
            );
183
        }
184
185
        $time  = static::normalizeTime($time);
0 ignored issues
show
Bug Best Practice introduced by
The method Ahc\Cron\Expression::normalizeTime() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

185
        /** @scrutinizer ignore-call */ 
186
        $time  = static::normalizeTime($time);
Loading history...
186
        $times = \array_map('intval', \explode(' ', \date('i G j n w Y t d m N', $time)));
187
188
        return [$expr, $times];
189
    }
190
191
    protected function normalizeTime($time)
192
    {
193
        if (empty($time)) {
194
            $time = \time();
195
        } elseif (\is_string($time)) {
196
            $time = \strtotime($time);
197
        } elseif ($time instanceof \DateTime) {
198
            $time = $time->getTimestamp();
199
        }
200
201
        return $time;
202
    }
203
204
    protected function normalizeExpr($expr)
205
    {
206
        if (isset(static::$expressions[$expr])) {
207
            $expr = static::$expressions[$expr];
208
        }
209
210
        return $expr;
211
    }
212
}
213