Completed
Pull Request — master (#113)
by
unknown
01:39
created

RecurrenceRule   C

Complexity

Total Complexity 63

Size/Duplication

Total Lines 510
Duplicated Lines 9.41 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 31%

Importance

Changes 0
Metric Value
wmc 63
lcom 1
cbo 1
dl 48
loc 510
ccs 31
cts 100
cp 0.31
rs 5.8893
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getEscapedValue() 0 4 1
F buildParameterBag() 0 60 15
A setCount() 0 6 1
A getCount() 0 4 1
A setUntil() 0 6 1
A getUntil() 0 4 1
A setFreq() 0 10 2
A getFreq() 0 4 1
A setInterval() 0 6 1
A getInterval() 0 4 1
A setWkst() 0 6 1
D setBySetPos() 0 48 17
A setByMonth() 12 12 4
A setByWeekNo() 0 8 1
A setByYearDay() 0 8 1
A setByMonthDay() 0 8 1
A setByDay() 0 8 1
A setByHour() 12 12 4
A setByMinute() 12 12 4
A setBySecond() 12 12 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RecurrenceRule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RecurrenceRule, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the eluceo/iCal package.
5
 *
6
 * (c) Markus Poerschke <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Eluceo\iCal\Property\Event;
13
14
use Eluceo\iCal\ParameterBag;
15
use Eluceo\iCal\Property\ValueInterface;
16
use InvalidArgumentException;
17
18
/**
19
 * Implementation of Recurrence Rule.
20
 *
21
 * @see https://tools.ietf.org/html/rfc5545#section-3.8.5.3
22
 */
23
class RecurrenceRule implements ValueInterface
24
{
25
    const FREQ_YEARLY = 'YEARLY';
26
    const FREQ_MONTHLY = 'MONTHLY';
27
    const FREQ_WEEKLY = 'WEEKLY';
28
    const FREQ_DAILY = 'DAILY';
29
    const FREQ_HOURLY = 'HOURLY';
30
    const FREQ_MINUTELY = 'MINUTELY';
31
    const FREQ_SECONDLY = 'SECONDLY';
32
33
    const WEEKDAY_SUNDAY = 'SU';
34
    const WEEKDAY_MONDAY = 'MO';
35
    const WEEKDAY_TUESDAY = 'TU';
36
    const WEEKDAY_WEDNESDAY = 'WE';
37
    const WEEKDAY_THURSDAY = 'TH';
38
    const WEEKDAY_FRIDAY = 'FR';
39
    const WEEKDAY_SATURDAY = 'SA';
40
41
    /**
42
     * The frequency of an Event.
43
     *
44
     * @var string
45
     */
46
    protected $freq = self::FREQ_YEARLY;
47
48
    /**
49
     * BYSETPOS must require use of other BY*.
50
     *
51
     * @var bool
52
     */
53
    protected $canUseBySetPos = false;
54
55
    /**
56
     * @var null|int
57
     */
58
    protected $interval = 1;
59
60
    /**
61
     * @var null|int
62
     */
63
    protected $count = null;
64
65
    /**
66
     * @var null|\DateTimeInterface
67
     */
68
    protected $until = null;
69
70
    /**
71
     * @var null|string
72
     */
73
    protected $wkst;
74
75
    /**
76
     * @var null|array
77
     */
78
    protected $bySetPos = null;
79
80
    /**
81
     * @var null|string
82
     */
83
    protected $byMonth;
84
85
    /**
86
     * @var null|string
87
     */
88
    protected $byWeekNo;
89
90
    /**
91
     * @var null|string
92
     */
93
    protected $byYearDay;
94
95
    /**
96
     * @var null|string
97
     */
98
    protected $byMonthDay;
99
100
    /**
101
     * @var null|string
102
     */
103
    protected $byDay;
104
105
    /**
106
     * @var null|string
107
     */
108
    protected $byHour;
109
110
    /**
111
     * @var null|string
112
     */
113
    protected $byMinute;
114
115 1
    /**
116
     * @var null|string
117 1
     */
118
    protected $bySecond;
119
120
    public function getEscapedValue(): string
121
    {
122
        return $this->buildParameterBag()->toString();
123 1
    }
124
125 1
    /**
126
     * @return ParameterBag
127 1
     */
128
    protected function buildParameterBag()
129 1
    {
130
        $parameterBag = new ParameterBag();
131
132
        $parameterBag->setParam('FREQ', $this->freq);
133 1
134
        if (null !== $this->interval) {
135
            $parameterBag->setParam('INTERVAL', $this->interval);
136
        }
137 1
138 1
        if (null !== $this->count) {
139 1
            $parameterBag->setParam('COUNT', $this->count);
140
        }
141 1
142
        if (null != $this->until) {
143
            $parameterBag->setParam('UNTIL', $this->until->format('Ymd\THis\Z'));
144
        }
145 1
146
        if (null !== $this->wkst) {
147
            $parameterBag->setParam('WKST', $this->wkst);
148
        }
149 1
150
        if (null !== $this->bySetPos && $this->canUseBySetPos) {
151
            $parameterBag->setParam('BYSETPOS', $this->bySetPos);
152
        }
153 1
154
        if (null !== $this->byMonth) {
155
            $parameterBag->setParam('BYMONTH', explode(',', $this->byMonth));
156
        }
157 1
158
        if (null !== $this->byWeekNo) {
159
            $parameterBag->setParam('BYWEEKNO', explode(',', $this->byWeekNo));
160
        }
161 1
162
        if (null !== $this->byYearDay) {
163
            $parameterBag->setParam('BYYEARDAY', explode(',', $this->byYearDay));
164
        }
165 1
166
        if (null !== $this->byMonthDay) {
167
            $parameterBag->setParam('BYMONTHDAY', explode(',', $this->byMonthDay));
168
        }
169 1
170
        if (null !== $this->byDay) {
171
            $parameterBag->setParam('BYDAY', explode(',', $this->byDay));
172
        }
173 1
174
        if (null !== $this->byHour) {
175
            $parameterBag->setParam('BYHOUR', explode(',', $this->byHour));
176
        }
177 1
178
        if (null !== $this->byMinute) {
179
            $parameterBag->setParam('BYMINUTE', explode(',', $this->byMinute));
180
        }
181
182
        if (null !== $this->bySecond) {
183
            $parameterBag->setParam('BYSECOND', explode(',', $this->bySecond));
184
        }
185
186
        return $parameterBag;
187
    }
188
189
    /**
190
     * @param int|null $count
191
     *
192
     * @return $this
193
     */
194
    public function setCount($count)
195
    {
196
        $this->count = $count;
197
198
        return $this;
199
    }
200
201
    /**
202
     * @return int|null
203
     */
204
    public function getCount()
205 1
    {
206
        return $this->count;
207 1
    }
208
209 1
    /**
210
     * @param \DateTimeInterface|null $until
211
     *
212
     * @return $this
213
     */
214
    public function setUntil(\DateTimeInterface $until = null)
215
    {
216
        $this->until = $until;
217
218
        return $this;
219
    }
220
221
    /**
222
     * @return \DateTimeInterface|null
223
     */
224
    public function getUntil()
225
    {
226
        return $this->until;
227
    }
228
229
    /**
230
     * The FREQ rule part identifies the type of recurrence rule.  This
231
     * rule part MUST be specified in the recurrence rule.  Valid values
232
     * include.
233
     *
234
     * SECONDLY, to specify repeating events based on an interval of a second or more;
235
     * MINUTELY, to specify repeating events based on an interval of a minute or more;
236
     * HOURLY, to specify repeating events based on an interval of an hour or more;
237
     * DAILY, to specify repeating events based on an interval of a day or more;
238
     * WEEKLY, to specify repeating events based on an interval of a week or more;
239 1
     * MONTHLY, to specify repeating events based on an interval of a month or more;
240
     * YEARLY, to specify repeating events based on an interval of a year or more.
241 1
     *
242 1
     * @param string $freq
243 1
     *
244
     * @return $this
245
     *
246
     * @throws \InvalidArgumentException
247 1
     */
248
    public function setFreq($freq)
249
    {
250
        if (@constant('static::FREQ_' . $freq) !== null) {
251
            $this->freq = $freq;
252
        } else {
253
            throw new \InvalidArgumentException("The Frequency {$freq} is not supported.");
254
        }
255
256
        return $this;
257
    }
258
259
    /**
260
     * @return string
261
     */
262
    public function getFreq()
263
    {
264
        return $this->freq;
265
    }
266 1
267
    /**
268 1
     * The INTERVAL rule part contains a positive integer representing at
269
     * which intervals the recurrence rule repeats.
270 1
     *
271
     * @param int|null $interval
272
     *
273
     * @return $this
274
     */
275
    public function setInterval($interval)
276
    {
277
        $this->interval = $interval;
278
279
        return $this;
280
    }
281
282
    /**
283
     * @return int|null
284
     */
285
    public function getInterval()
286
    {
287
        return $this->interval;
288
    }
289
290
    /**
291
     * The WKST rule part specifies the day on which the workweek starts.
292
     * Valid values are MO, TU, WE, TH, FR, SA, and SU.
293
     *
294
     * @param string $value
295
     *
296
     * @return $this
297
     */
298
    public function setWkst($value)
299
    {
300
        $this->wkst = $value;
301
302
        return $this;
303
    }
304
305
    /**
306
     * The BYSETPOS filters one interval of events by the specified position.
307
     * A positive position will start from the beginning and go forward while
308
     * a negative position will start at the end and move backward.
309
     *
310
     * Valid values are a comma separated string or an array of integers
311
     * from 1 to 366 or negative integers from -1 to -366.
312
     *
313
     * @param null|int|string|array $value
314
     *
315
     * @throws InvalidArgumentException
316
     *
317
     * @return $this
318
     */
319
    public function setBySetPos($value)
320
    {
321
        if (null === $value) {
322
            $this->bySetPos = value;
323
324
            return $this;
325
        }
326
327
        if (!(is_string($value) || is_array($value) || is_int($value))) {
328
            throw new InvalidArgumentException('Invalid value for BYSETPOS');
329
        }
330
331
        $list = $value;
332
333
        if (is_int($value)) {
334
            if ($value === 0 || $value < -366 || $value > 366) {
335
                throw new InvalidArgumentException('Invalid value for BYSETPOS');
336
            }
337
            $this->bySetPos = [$value];
338
339
            return $this;
340
        }
341
342
        if (is_string($value)) {
343
            $list = explode(',', $value);
344
        }
345
346
        $output = [];
347
348
        foreach ($list as $item) {
0 ignored issues
show
Bug introduced by
The expression $list of type string|array|integer is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
349
            if (is_string($item)) {
350
                if (!preg_match('/^ *-?[0-9]* *$/', $item)) {
351
                    throw new InvalidArgumentException('Invalid value for BYSETPOS');
352
                }
353
                $item = intval($item);
354
            }
355
356
            if (!is_int($item) || $item === 0 || $item < -366 || $item > 366) {
357
                throw new InvalidArgumentException('Invalid value for BYSETPOS');
358
            }
359
360
            $output[] = $item;
361
        }
362
363
        $this->bySetPos = $output;
364
365
        return $this;
366
    }
367
368
    /**
369
     * The BYMONTH rule part specifies a COMMA-separated list of months of the year.
370
     * Valid values are 1 to 12.
371
     *
372
     * @param int $month
373
     *
374
     * @throws InvalidArgumentException
375
     *
376
     * @return $this
377
     */
378 View Code Duplication
    public function setByMonth($month)
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...
379
    {
380
        if (!is_integer($month) || $month < 0 || $month > 12) {
381
            throw new InvalidArgumentException('Invalid value for BYMONTH');
382
        }
383
384
        $this->byMonth = $month;
0 ignored issues
show
Documentation Bug introduced by
It seems like $month of type integer is incompatible with the declared type null|string of property $byMonth.

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...
385
386
        $this->canUseBySetPos = true;
387
388
        return $this;
389
    }
390
391
    /**
392
     * The BYWEEKNO rule part specifies a COMMA-separated list of ordinals specifying weeks of the year.
393
     * Valid values are 1 to 53 or -53 to -1.
394
     *
395
     * @param int $value
396
     *
397
     * @return $this
398
     */
399
    public function setByWeekNo($value)
400
    {
401
        $this->byWeekNo = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value of type integer is incompatible with the declared type null|string of property $byWeekNo.

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...
402
403
        $this->canUseBySetPos = true;
404
405
        return $this;
406
    }
407
408
    /**
409
     * The BYYEARDAY rule part specifies a COMMA-separated list of days of the year.
410
     * Valid values are 1 to 366 or -366 to -1.
411
     *
412
     * @param int $day
413
     *
414
     * @return $this
415
     */
416
    public function setByYearDay($day)
417
    {
418
        $this->byYearDay = $day;
0 ignored issues
show
Documentation Bug introduced by
It seems like $day of type integer is incompatible with the declared type null|string of property $byYearDay.

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...
419
420
        $this->canUseBySetPos = true;
421
422
        return $this;
423
    }
424
425
    /**
426
     * The BYMONTHDAY rule part specifies a COMMA-separated list of days of the month.
427
     * Valid values are 1 to 31 or -31 to -1.
428
     *
429
     * @param int $day
430
     *
431
     * @return $this
432
     */
433
    public function setByMonthDay($day)
434
    {
435
        $this->byMonthDay = $day;
0 ignored issues
show
Documentation Bug introduced by
It seems like $day of type integer is incompatible with the declared type null|string of property $byMonthDay.

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...
436
437
        $this->canUseBySetPos = true;
438
439
        return $this;
440
    }
441
442
    /**
443
     * The BYDAY rule part specifies a COMMA-separated list of days of the week;.
444
     *
445
     * SU indicates Sunday; MO indicates Monday; TU indicates Tuesday;
446
     * WE indicates Wednesday; TH indicates Thursday; FR indicates Friday; and SA indicates Saturday.
447
     *
448
     * Each BYDAY value can also be preceded by a positive (+n) or negative (-n) integer.
449
     * If present, this indicates the nth occurrence of a specific day within the MONTHLY or YEARLY "RRULE".
450
     *
451
     * @param string $day
452
     *
453
     * @return $this
454
     */
455
    public function setByDay(string $day)
456
    {
457
        $this->byDay = $day;
458
459
        $this->canUseBySetPos = true;
460
461
        return $this;
462
    }
463
464
    /**
465
     * The BYHOUR rule part specifies a COMMA-separated list of hours of the day.
466
     * Valid values are 0 to 23.
467
     *
468
     * @param int $value
469
     *
470
     * @return $this
471
     *
472
     * @throws \InvalidArgumentException
473
     */
474 View Code Duplication
    public function setByHour($value)
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...
475
    {
476
        if (!is_integer($value) || $value < 0 || $value > 23) {
477
            throw new \InvalidArgumentException('Invalid value for BYHOUR');
478
        }
479
480
        $this->byHour = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value of type integer is incompatible with the declared type null|string of property $byHour.

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...
481
482
        $this->canUseBySetPos = true;
483
484
        return $this;
485
    }
486
487
    /**
488
     * The BYMINUTE rule part specifies a COMMA-separated list of minutes within an hour.
489
     * Valid values are 0 to 59.
490
     *
491
     * @param int $value
492
     *
493
     * @return $this
494
     *
495
     * @throws \InvalidArgumentException
496
     */
497 View Code Duplication
    public function setByMinute($value)
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...
498
    {
499
        if (!is_integer($value) || $value < 0 || $value > 59) {
500
            throw new \InvalidArgumentException('Invalid value for BYMINUTE');
501
        }
502
503
        $this->byMinute = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value of type integer is incompatible with the declared type null|string of property $byMinute.

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...
504
505
        $this->canUseBySetPos = true;
506
507
        return $this;
508
    }
509
510
    /**
511
     * The BYSECOND rule part specifies a COMMA-separated list of seconds within a minute.
512
     * Valid values are 0 to 60.
513
     *
514
     * @param int $value
515
     *
516
     * @return $this
517
     *
518
     * @throws \InvalidArgumentException
519
     */
520 View Code Duplication
    public function setBySecond($value)
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...
521
    {
522
        if (!is_integer($value) || $value < 0 || $value > 60) {
523
            throw new \InvalidArgumentException('Invalid value for BYSECOND');
524
        }
525
526
        $this->bySecond = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value of type integer is incompatible with the declared type null|string of property $bySecond.

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...
527
528
        $this->canUseBySetPos = true;
529
530
        return $this;
531
    }
532
}
533