1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spatie\OpeningHours; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use DateTimeZone; |
7
|
|
|
use DateTimeImmutable; |
8
|
|
|
use DateTimeInterface; |
9
|
|
|
use Spatie\OpeningHours\Exceptions\MaximumLimitExceeded; |
10
|
|
|
use Spatie\OpeningHours\Helpers\Arr; |
11
|
|
|
use Spatie\OpeningHours\Helpers\DataTrait; |
12
|
|
|
use Spatie\OpeningHours\Exceptions\Exception; |
13
|
|
|
use Spatie\OpeningHours\Exceptions\InvalidDate; |
14
|
|
|
use Spatie\OpeningHours\Exceptions\InvalidDayName; |
15
|
|
|
|
16
|
|
|
class OpeningHours |
17
|
|
|
{ |
18
|
|
|
const DEFAULT_DAY_LIMIT = 8; |
19
|
|
|
|
20
|
|
|
use DataTrait; |
21
|
|
|
|
22
|
|
|
/** @var \Spatie\OpeningHours\Day[] */ |
23
|
|
|
protected $openingHours = []; |
24
|
|
|
|
25
|
|
|
/** @var \Spatie\OpeningHours\OpeningHoursForDay[] */ |
26
|
|
|
protected $exceptions = []; |
27
|
|
|
|
28
|
|
|
/** @var callable[] */ |
29
|
|
|
protected $filters = []; |
30
|
|
|
|
31
|
|
|
/** @var DateTimeZone|null */ |
32
|
|
|
protected $timezone = null; |
33
|
|
|
|
34
|
|
|
/** @var bool Allow for overflowing time ranges which overflow into the next day */ |
35
|
|
|
protected $overflow; |
36
|
|
|
|
37
|
|
|
/** @var int Number of days to try before abandoning the search of the next close/open time */ |
38
|
|
|
protected $dayLimit = null; |
39
|
|
|
|
40
|
|
|
public function __construct($timezone = null) |
41
|
|
|
{ |
42
|
|
|
if ($timezone instanceof DateTimeZone) { |
43
|
|
|
$this->timezone = $timezone; |
44
|
|
|
} elseif (is_string($timezone)) { |
45
|
|
|
$this->timezone = new DateTimeZone($timezone); |
46
|
|
|
} elseif ($timezone) { |
47
|
|
|
throw new \InvalidArgumentException('Invalid Timezone'); |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
$this->openingHours = Day::mapDays(function () { |
51
|
|
|
return new OpeningHoursForDay(); |
52
|
|
|
}); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @param string[][] $data |
57
|
|
|
* @param string|DateTimeZone|null $timezone |
58
|
|
|
* |
59
|
|
|
* @return static |
60
|
|
|
*/ |
61
|
|
|
public static function create(array $data, $timezone = null): self |
62
|
|
|
{ |
63
|
|
|
return (new static($timezone))->fill($data); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @param array $data |
68
|
|
|
* |
69
|
|
|
* @return array |
70
|
|
|
*/ |
71
|
|
|
public static function mergeOverlappingRanges(array $data) |
72
|
|
|
{ |
73
|
|
|
$result = []; |
74
|
|
|
$ranges = []; |
75
|
|
|
foreach ($data as $key => $value) { |
76
|
|
|
$value = is_array($value) |
77
|
|
|
? static::mergeOverlappingRanges($value) |
78
|
|
|
: (is_string($value) ? TimeRange::fromString($value) : $value); |
79
|
|
|
|
80
|
|
|
if ($value instanceof TimeRange) { |
81
|
|
|
$newRanges = []; |
82
|
|
|
foreach ($ranges as $range) { |
83
|
|
|
if ($value->format() === $range->format()) { |
84
|
|
|
continue 2; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
if ($value->overlaps($range) || $range->overlaps($value)) { |
88
|
|
|
$value = TimeRange::fromList([$value, $range]); |
89
|
|
|
|
90
|
|
|
continue; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
$newRanges[] = $range; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$newRanges[] = $value; |
97
|
|
|
$ranges = $newRanges; |
98
|
|
|
|
99
|
|
|
continue; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$result[$key] = $value; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
foreach ($ranges as $range) { |
106
|
|
|
$result[] = $range; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
return $result; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @param string[][] $data |
114
|
|
|
* @param string|DateTimeZone|null $timezone |
115
|
|
|
* |
116
|
|
|
* @return static |
117
|
|
|
*/ |
118
|
|
|
public static function createAndMergeOverlappingRanges(array $data, $timezone = null) |
119
|
|
|
{ |
120
|
|
|
return static::create(static::mergeOverlappingRanges($data), $timezone); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @param array $data |
125
|
|
|
* |
126
|
|
|
* @return bool |
127
|
|
|
*/ |
128
|
|
|
public static function isValid(array $data): bool |
129
|
|
|
{ |
130
|
|
|
try { |
131
|
|
|
static::create($data); |
132
|
|
|
|
133
|
|
|
return true; |
134
|
|
|
} catch (Exception $exception) { |
135
|
|
|
return false; |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Set the number of days to try before abandoning the search of the next close/open time. |
141
|
|
|
* |
142
|
|
|
* @param int $dayLimit number of days |
143
|
|
|
*/ |
144
|
|
|
public function setDayLimit(int $dayLimit) |
145
|
|
|
{ |
146
|
|
|
$this->dayLimit = $dayLimit; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Get the number of days to try before abandoning the search of the next close/open time. |
151
|
|
|
* |
152
|
|
|
* @return int |
153
|
|
|
*/ |
154
|
|
|
public function getDayLimit(): int |
155
|
|
|
{ |
156
|
|
|
return $this->dayLimit ?: static::DEFAULT_DAY_LIMIT; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
public function setFilters(array $filters) |
160
|
|
|
{ |
161
|
|
|
$this->filters = $filters; |
162
|
|
|
|
163
|
|
|
return $this; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
public function getFilters(): array |
167
|
|
|
{ |
168
|
|
|
return $this->filters; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function fill(array $data) |
172
|
|
|
{ |
173
|
|
|
list($openingHours, $exceptions, $metaData, $filters, $overflow) = $this->parseOpeningHoursAndExceptions($data); |
174
|
|
|
|
175
|
|
|
$this->overflow = $overflow; |
176
|
|
|
|
177
|
|
|
foreach ($openingHours as $day => $openingHoursForThisDay) { |
178
|
|
|
$this->setOpeningHoursFromStrings($day, $openingHoursForThisDay); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
$this->setExceptionsFromStrings($exceptions); |
182
|
|
|
|
183
|
|
|
return $this->setFilters($filters)->setData($metaData); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
public function forWeek(): array |
187
|
|
|
{ |
188
|
|
|
return $this->openingHours; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
public function forWeekCombined(): array |
192
|
|
|
{ |
193
|
|
|
$equalDays = []; |
194
|
|
|
$allOpeningHours = $this->openingHours; |
195
|
|
|
$uniqueOpeningHours = array_unique($allOpeningHours); |
196
|
|
|
$nonUniqueOpeningHours = $allOpeningHours; |
197
|
|
|
|
198
|
|
|
foreach ($uniqueOpeningHours as $day => $value) { |
199
|
|
|
$equalDays[$day] = ['days' => [$day], 'opening_hours' => $value]; |
200
|
|
|
unset($nonUniqueOpeningHours[$day]); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
foreach ($uniqueOpeningHours as $uniqueDay => $uniqueValue) { |
204
|
|
|
foreach ($nonUniqueOpeningHours as $nonUniqueDay => $nonUniqueValue) { |
205
|
|
|
if ((string) $uniqueValue === (string) $nonUniqueValue) { |
206
|
|
|
$equalDays[$uniqueDay]['days'][] = $nonUniqueDay; |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
return $equalDays; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
public function forDay(string $day): OpeningHoursForDay |
215
|
|
|
{ |
216
|
|
|
$day = $this->normalizeDayName($day); |
217
|
|
|
|
218
|
|
|
return $this->openingHours[$day]; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
public function forDate(DateTimeInterface $date): OpeningHoursForDay |
222
|
|
|
{ |
223
|
|
|
$date = $this->applyTimezone($date); |
224
|
|
|
|
225
|
|
|
foreach ($this->filters as $filter) { |
226
|
|
|
$result = $filter($date); |
227
|
|
|
|
228
|
|
|
if (is_array($result)) { |
229
|
|
|
return OpeningHoursForDay::fromStrings($result); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return $this->exceptions[$date->format('Y-m-d')] ?? ($this->exceptions[$date->format('m-d')] ?? $this->forDay(Day::onDateTime($date))); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
public function exceptions(): array |
237
|
|
|
{ |
238
|
|
|
return $this->exceptions; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
public function isOpenOn(string $day): bool |
242
|
|
|
{ |
243
|
|
|
return count($this->forDay($day)) > 0; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
public function isClosedOn(string $day): bool |
247
|
|
|
{ |
248
|
|
|
return ! $this->isOpenOn($day); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
public function isOpenAt(DateTimeInterface $dateTime): bool |
252
|
|
|
{ |
253
|
|
|
$dateTime = $this->applyTimezone($dateTime); |
254
|
|
|
|
255
|
|
|
if ($this->overflow) { |
256
|
|
|
$yesterdayDateTime = $dateTime; |
257
|
|
|
if (! ($yesterdayDateTime instanceof DateTimeImmutable)) { |
258
|
|
|
$yesterdayDateTime = clone $yesterdayDateTime; |
259
|
|
|
} |
260
|
|
|
$dateTimeMinus1Day = $yesterdayDateTime->sub(new \DateInterval('P1D')); |
261
|
|
|
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day); |
262
|
|
|
if ($openingHoursForDayBefore->isOpenAtNight(Time::fromDateTime($dateTimeMinus1Day))) { |
263
|
|
|
return true; |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
$openingHoursForDay = $this->forDate($dateTime); |
268
|
|
|
|
269
|
|
|
return $openingHoursForDay->isOpenAt(Time::fromDateTime($dateTime)); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
public function isClosedAt(DateTimeInterface $dateTime): bool |
273
|
|
|
{ |
274
|
|
|
return ! $this->isOpenAt($dateTime); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
public function isOpen(): bool |
278
|
|
|
{ |
279
|
|
|
return $this->isOpenAt(new DateTime()); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
public function isClosed(): bool |
283
|
|
|
{ |
284
|
|
|
return $this->isClosedAt(new DateTime()); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
public function nextOpen(DateTimeInterface $dateTime): DateTimeInterface |
288
|
|
|
{ |
289
|
|
|
if (! ($dateTime instanceof DateTimeImmutable)) { |
290
|
|
|
$dateTime = clone $dateTime; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
$openingHoursForDay = $this->forDate($dateTime); |
294
|
|
|
$nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime)); |
295
|
|
|
$tries = $this->getDayLimit(); |
296
|
|
|
|
297
|
|
View Code Duplication |
while ($nextOpen === false || $nextOpen->hours() >= 24) { |
|
|
|
|
298
|
|
|
if (--$tries < 0) { |
299
|
|
|
throw MaximumLimitExceeded::forString( |
300
|
|
|
'No open date/time found in the next '.$this->getDayLimit().' days,'. |
301
|
|
|
' use $openingHours->setDayLimit() to increase the limit.' |
302
|
|
|
); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
$dateTime = $dateTime |
306
|
|
|
->modify('+1 day') |
307
|
|
|
->setTime(0, 0, 0); |
308
|
|
|
|
309
|
|
|
if ($this->isOpenAt($dateTime) && ! $openingHoursForDay->isOpenAt(Time::fromString('23:59'))) { |
310
|
|
|
return $dateTime; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
$openingHoursForDay = $this->forDate($dateTime); |
314
|
|
|
|
315
|
|
|
$nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime)); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
if ($dateTime->format('H:i') === '00:00' && $this->isOpenAt((clone $dateTime)->modify('-1 second'))) { |
319
|
|
|
return $this->nextOpen($dateTime->modify('+1 minute')); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
$nextDateTime = $nextOpen->toDateTime(); |
323
|
|
|
$dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0); |
324
|
|
|
|
325
|
|
|
return $dateTime; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
public function nextClose(DateTimeInterface $dateTime): DateTimeInterface |
329
|
|
|
{ |
330
|
|
|
if (! ($dateTime instanceof DateTimeImmutable)) { |
331
|
|
|
$dateTime = clone $dateTime; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
$nextClose = null; |
335
|
|
|
if ($this->overflow) { |
336
|
|
|
$yesterday = $dateTime; |
337
|
|
|
if (! ($dateTime instanceof DateTimeImmutable)) { |
338
|
|
|
$yesterday = clone $dateTime; |
339
|
|
|
} |
340
|
|
|
$dateTimeMinus1Day = $yesterday->sub(new \DateInterval('P1D')); |
341
|
|
|
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day); |
342
|
|
|
if ($openingHoursForDayBefore->isOpenAtNight(Time::fromDateTime($dateTimeMinus1Day))) { |
343
|
|
|
$nextClose = $openingHoursForDayBefore->nextClose(Time::fromDateTime($dateTime)); |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
$openingHoursForDay = $this->forDate($dateTime); |
348
|
|
|
if (! $nextClose) { |
349
|
|
|
$nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime)); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
$tries = $this->getDayLimit(); |
353
|
|
|
|
354
|
|
View Code Duplication |
while ($nextClose === false || $nextClose->hours() >= 24) { |
|
|
|
|
355
|
|
|
if (--$tries < 0) { |
356
|
|
|
throw MaximumLimitExceeded::forString( |
357
|
|
|
'No close date/time found in the next '.$this->getDayLimit().' days,'. |
358
|
|
|
' use $openingHours->setDayLimit() to increase the limit.' |
359
|
|
|
); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
$dateTime = $dateTime |
363
|
|
|
->modify('+1 day') |
364
|
|
|
->setTime(0, 0, 0); |
365
|
|
|
|
366
|
|
|
if ($this->isClosedAt($dateTime) && $openingHoursForDay->isOpenAt(Time::fromString('23:59'))) { |
367
|
|
|
return $dateTime; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
$openingHoursForDay = $this->forDate($dateTime); |
371
|
|
|
|
372
|
|
|
$nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime)); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
$nextDateTime = $nextClose->toDateTime(); |
376
|
|
|
$dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0); |
377
|
|
|
|
378
|
|
|
return $dateTime; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
public function regularClosingDays(): array |
382
|
|
|
{ |
383
|
|
|
return array_keys($this->filter(function (OpeningHoursForDay $openingHoursForDay) { |
384
|
|
|
return $openingHoursForDay->isEmpty(); |
385
|
|
|
})); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
public function regularClosingDaysISO(): array |
389
|
|
|
{ |
390
|
|
|
return Arr::map($this->regularClosingDays(), [Day::class, 'toISO']); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
public function exceptionalClosingDates(): array |
394
|
|
|
{ |
395
|
|
|
$dates = array_keys($this->filterExceptions(function (OpeningHoursForDay $openingHoursForDay) { |
396
|
|
|
return $openingHoursForDay->isEmpty(); |
397
|
|
|
})); |
398
|
|
|
|
399
|
|
|
return Arr::map($dates, function ($date) { |
400
|
|
|
return DateTime::createFromFormat('Y-m-d', $date); |
401
|
|
|
}); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
public function setTimezone($timezone) |
405
|
|
|
{ |
406
|
|
|
$this->timezone = new DateTimeZone($timezone); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
protected function parseOpeningHoursAndExceptions(array $data): array |
410
|
|
|
{ |
411
|
|
|
$metaData = Arr::pull($data, 'data', null); |
412
|
|
|
$exceptions = []; |
413
|
|
|
$filters = Arr::pull($data, 'filters', []); |
414
|
|
|
$overflow = (bool) Arr::pull($data, 'overflow', false); |
415
|
|
|
|
416
|
|
|
foreach (Arr::pull($data, 'exceptions', []) as $key => $exception) { |
417
|
|
|
if (is_callable($exception)) { |
418
|
|
|
$filters[] = $exception; |
419
|
|
|
|
420
|
|
|
continue; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
$exceptions[$key] = $exception; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
$openingHours = []; |
427
|
|
|
|
428
|
|
|
foreach ($data as $day => $openingHoursData) { |
429
|
|
|
$openingHours[$this->normalizeDayName($day)] = $openingHoursData; |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
return [$openingHours, $exceptions, $metaData, $filters, $overflow]; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
protected function setOpeningHoursFromStrings(string $day, array $openingHours) |
436
|
|
|
{ |
437
|
|
|
$day = $this->normalizeDayName($day); |
438
|
|
|
|
439
|
|
|
$data = null; |
440
|
|
|
|
441
|
|
|
if (isset($openingHours['data'])) { |
442
|
|
|
$data = $openingHours['data']; |
443
|
|
|
unset($openingHours['data']); |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
$this->openingHours[$day] = OpeningHoursForDay::fromStrings($openingHours)->setData($data); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
protected function setExceptionsFromStrings(array $exceptions) |
450
|
|
|
{ |
451
|
|
|
if (empty($exceptions)) { |
452
|
|
|
return; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
if (!$this->dayLimit) { |
456
|
|
|
$this->dayLimit = 366; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
$this->exceptions = Arr::map($exceptions, function (array $openingHours, string $date) { |
460
|
|
|
$recurring = DateTime::createFromFormat('m-d', $date); |
461
|
|
|
|
462
|
|
|
if ($recurring === false || $recurring->format('m-d') !== $date) { |
463
|
|
|
$dateTime = DateTime::createFromFormat('Y-m-d', $date); |
464
|
|
|
|
465
|
|
|
if ($dateTime === false || $dateTime->format('Y-m-d') !== $date) { |
466
|
|
|
throw InvalidDate::invalidDate($date); |
467
|
|
|
} |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
return OpeningHoursForDay::fromStrings($openingHours); |
471
|
|
|
}); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
protected function normalizeDayName(string $day) |
475
|
|
|
{ |
476
|
|
|
$day = strtolower($day); |
477
|
|
|
|
478
|
|
|
if (! Day::isValid($day)) { |
479
|
|
|
throw InvalidDayName::invalidDayName($day); |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
return $day; |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
protected function applyTimezone(DateTimeInterface $date) |
486
|
|
|
{ |
487
|
|
|
if ($this->timezone) { |
488
|
|
|
$date = $date->setTimezone($this->timezone); |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
return $date; |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
public function filter(callable $callback): array |
495
|
|
|
{ |
496
|
|
|
return Arr::filter($this->openingHours, $callback); |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
public function map(callable $callback): array |
500
|
|
|
{ |
501
|
|
|
return Arr::map($this->openingHours, $callback); |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
public function flatMap(callable $callback): array |
505
|
|
|
{ |
506
|
|
|
return Arr::flatMap($this->openingHours, $callback); |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
public function filterExceptions(callable $callback): array |
510
|
|
|
{ |
511
|
|
|
return Arr::filter($this->exceptions, $callback); |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
public function mapExceptions(callable $callback): array |
515
|
|
|
{ |
516
|
|
|
return Arr::map($this->exceptions, $callback); |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
public function flatMapExceptions(callable $callback): array |
520
|
|
|
{ |
521
|
|
|
return Arr::flatMap($this->exceptions, $callback); |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
public function asStructuredData(): array |
525
|
|
|
{ |
526
|
|
View Code Duplication |
$regularHours = $this->flatMap(function (OpeningHoursForDay $openingHoursForDay, string $day) { |
|
|
|
|
527
|
|
|
return $openingHoursForDay->map(function (TimeRange $timeRange) use ($day) { |
528
|
|
|
return [ |
529
|
|
|
'@type' => 'OpeningHoursSpecification', |
530
|
|
|
'dayOfWeek' => ucfirst($day), |
531
|
|
|
'opens' => (string) $timeRange->start(), |
532
|
|
|
'closes' => (string) $timeRange->end(), |
533
|
|
|
]; |
534
|
|
|
}); |
535
|
|
|
}); |
536
|
|
|
|
537
|
|
|
$exceptions = $this->flatMapExceptions(function (OpeningHoursForDay $openingHoursForDay, string $date) { |
538
|
|
|
if ($openingHoursForDay->isEmpty()) { |
539
|
|
|
return [[ |
540
|
|
|
'@type' => 'OpeningHoursSpecification', |
541
|
|
|
'opens' => '00:00', |
542
|
|
|
'closes' => '00:00', |
543
|
|
|
'validFrom' => $date, |
544
|
|
|
'validThrough' => $date, |
545
|
|
|
]]; |
546
|
|
|
} |
547
|
|
|
|
548
|
|
View Code Duplication |
return $openingHoursForDay->map(function (TimeRange $timeRange) use ($date) { |
|
|
|
|
549
|
|
|
return [ |
550
|
|
|
'@type' => 'OpeningHoursSpecification', |
551
|
|
|
'opens' => (string) $timeRange->start(), |
552
|
|
|
'closes' => (string) $timeRange->end(), |
553
|
|
|
'validFrom' => $date, |
554
|
|
|
'validThrough' => $date, |
555
|
|
|
]; |
556
|
|
|
}); |
557
|
|
|
}); |
558
|
|
|
|
559
|
|
|
return array_merge($regularHours, $exceptions); |
560
|
|
|
} |
561
|
|
|
} |
562
|
|
|
|
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.