GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#57)
by Deniss
01:43
created

OpeningHours   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 317
Duplicated Lines 5.99 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 6
dl 19
loc 317
rs 8.4864
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A create() 0 4 1
A isValid() 0 10 2
A fill() 0 12 2
A forWeek() 0 4 1
B forWeekCombined() 0 26 5
A forDay() 0 6 1
A forDate() 0 6 1
A exceptions() 0 4 1
A isOpenOn() 0 4 1
A isClosedOn() 0 4 1
A isOpenAt() 0 8 1
A isClosedAt() 0 4 1
A isOpen() 0 4 1
A isClosed() 0 4 1
A nextOpen() 0 20 2
A regularClosingDays() 0 6 1
A regularClosingDaysISO() 0 4 1
A exceptionalClosingDates() 0 10 1
A setTimezone() 0 4 1
A parseOpeningHoursAndExceptions() 0 11 2
A setOpeningHoursFromStrings() 0 6 1
B setExceptionsFromStrings() 0 16 5
A normalizeDayName() 0 10 2
A applyTimezone() 0 8 2
A filter() 0 4 1
A map() 0 4 1
A flatMap() 0 4 1
A filterExceptions() 0 4 1
A mapExceptions() 0 4 1
A flatMapExceptions() 0 4 1
B asStructuredData() 19 37 2

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 OpeningHours 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 OpeningHours, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Spatie\OpeningHours;
4
5
use DateTime;
6
use DateTimeZone;
7
use DateTimeInterface;
8
use Spatie\OpeningHours\Helpers\Arr;
9
use Spatie\OpeningHours\Exceptions\Exception;
10
use Spatie\OpeningHours\Exceptions\InvalidDate;
11
use Spatie\OpeningHours\Exceptions\InvalidDayName;
12
13
class OpeningHours
14
{
15
    /** @var \Spatie\OpeningHours\Day[] */
16
    protected $openingHours = [];
17
18
    /** @var array */
19
    protected $exceptions = [];
20
21
    /** @var DateTimeZone|null */
22
    protected $timezone = null;
23
24
    public function __construct($timezone = null)
25
    {
26
        $this->timezone = $timezone ? new DateTimeZone($timezone) : null;
27
28
        $this->openingHours = Day::mapDays(function () {
29
            return new OpeningHoursForDay();
30
        });
31
    }
32
33
    /**
34
     * @param array $data
35
     *
36
     * @return static
37
     */
38
    public static function create(array $data)
39
    {
40
        return (new static())->fill($data);
41
    }
42
43
    /**
44
     * @param array $data
45
     *
46
     * @return bool
47
     */
48
    public static function isValid(array $data): bool
49
    {
50
        try {
51
            static::create($data);
52
53
            return true;
54
        } catch (Exception $exception) {
55
            return false;
56
        }
57
    }
58
59
    public function fill(array $data)
60
    {
61
        list($openingHours, $exceptions) = $this->parseOpeningHoursAndExceptions($data);
62
63
        foreach ($openingHours as $day => $openingHoursForThisDay) {
64
            $this->setOpeningHoursFromStrings($day, $openingHoursForThisDay);
65
        }
66
67
        $this->setExceptionsFromStrings($exceptions);
68
69
        return $this;
70
    }
71
72
    public function forWeek(): array
73
    {
74
        return $this->openingHours;
75
    }
76
77
    public function forWeekCombined(): array
78
    {
79
        $equalDays = [];
80
        $allOpeningHours = $this->openingHours;
81
        $uniqueOpeningHours = array_unique($allOpeningHours);
82
        $nonUniqueOpeningHours = $allOpeningHours;
83
84
        // Initialize $equalDays array based on unique days
85
        foreach ($uniqueOpeningHours as $day => $value) {
86
            $equalDays[$day] = ['days' => [$day], 'opening_hours' => $value];
87
            unset($nonUniqueOpeningHours[$day]);
88
        }
89
90
        // Compare each uniqueDay data, with data from nonUnique days.
91
        // Populate equalDays array if data matches
92
        foreach ($uniqueOpeningHours as $uniqueDay => $uniqueValue) {
93
            /** @var OpeningHoursForDay $nonUniqueValue */
94
            foreach ($nonUniqueOpeningHours as $nonUniqueDay => $nonUniqueValue) {
95
                if ((string) $uniqueValue === (string) $nonUniqueValue) {
96
                    $equalDays[$uniqueDay]['days'][] = $nonUniqueDay;
97
                }
98
            }
99
        }
100
101
        return $equalDays;
102
    }
103
104
    public function forDay(string $day): OpeningHoursForDay
105
    {
106
        $day = $this->normalizeDayName($day);
107
108
        return $this->openingHours[$day];
109
    }
110
111
    public function forDate(DateTimeInterface $date): OpeningHoursForDay
112
    {
113
        $date = $this->applyTimezone($date);
114
115
        return $this->exceptions[$date->format('Y-m-d')] ?? ($this->exceptions[$date->format('m-d')] ?? $this->forDay(Day::onDateTime($date)));
116
    }
117
118
    public function exceptions(): array
119
    {
120
        return $this->exceptions;
121
    }
122
123
    public function isOpenOn(string $day): bool
124
    {
125
        return count($this->forDay($day)) > 0;
126
    }
127
128
    public function isClosedOn(string $day): bool
129
    {
130
        return ! $this->isOpenOn($day);
131
    }
132
133
    public function isOpenAt(DateTimeInterface $dateTime): bool
134
    {
135
        $dateTime = $this->applyTimezone($dateTime);
136
137
        $openingHoursForDay = $this->forDate($dateTime);
138
139
        return $openingHoursForDay->isOpenAt(Time::fromDateTime($dateTime));
140
    }
141
142
    public function isClosedAt(DateTimeInterface $dateTime): bool
143
    {
144
        return ! $this->isOpenAt($dateTime);
145
    }
146
147
    public function isOpen(): bool
148
    {
149
        return $this->isOpenAt(new DateTime());
150
    }
151
152
    public function isClosed(): bool
153
    {
154
        return $this->isClosedAt(new DateTime());
155
    }
156
157
    public function nextOpen(DateTimeInterface $dateTime): DateTime
158
    {
159
        $openingHoursForDay = $this->forDate($dateTime);
160
        $nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
161
162
        while ($nextOpen == false) {
163
            $dateTime
164
                ->modify('+1 day')
165
                ->setTime(0, 0, 0);
166
167
            $openingHoursForDay = $this->forDate($dateTime);
168
169
            $nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
170
        }
171
172
        $nextDateTime = $nextOpen->toDateTime();
173
        $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0);
174
175
        return $dateTime;
176
    }
177
178
    public function regularClosingDays(): array
179
    {
180
        return array_keys($this->filter(function (OpeningHoursForDay $openingHoursForDay) {
181
            return $openingHoursForDay->isEmpty();
182
        }));
183
    }
184
185
    public function regularClosingDaysISO(): array
186
    {
187
        return Arr::map($this->regularClosingDays(), [Day::class, 'toISO']);
188
    }
189
190
    public function exceptionalClosingDates(): array
191
    {
192
        $dates = array_keys($this->filterExceptions(function (OpeningHoursForDay $openingHoursForDay, $date) {
0 ignored issues
show
Unused Code introduced by
The parameter $date is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
193
            return $openingHoursForDay->isEmpty();
194
        }));
195
196
        return Arr::map($dates, function ($date) {
197
            return DateTime::createFromFormat('Y-m-d', $date);
198
        });
199
    }
200
201
    public function setTimezone($timezone)
202
    {
203
        $this->timezone = new DateTimeZone($timezone);
204
    }
205
206
    protected function parseOpeningHoursAndExceptions(array $data): array
207
    {
208
        $exceptions = Arr::pull($data, 'exceptions', []);
209
        $openingHours = [];
210
211
        foreach ($data as $day => $openingHoursData) {
212
            $openingHours[$this->normalizeDayName($day)] = $openingHoursData;
213
        }
214
215
        return [$openingHours, $exceptions];
216
    }
217
218
    protected function setOpeningHoursFromStrings(string $day, array $openingHours)
219
    {
220
        $day = $this->normalizeDayName($day);
221
222
        $this->openingHours[$day] = OpeningHoursForDay::fromStrings($openingHours);
223
    }
224
225
    protected function setExceptionsFromStrings(array $exceptions)
226
    {
227
        $this->exceptions = Arr::map($exceptions, function (array $openingHours, string $date) {
228
            $recurring = DateTime::createFromFormat('m-d', $date);
229
230
            if ($recurring === false || $recurring->format('m-d') !== $date) {
231
                $dateTime = DateTime::createFromFormat('Y-m-d', $date);
232
233
                if ($dateTime === false || $dateTime->format('Y-m-d') !== $date) {
234
                    throw InvalidDate::invalidDate($date);
235
                }
236
            }
237
238
            return OpeningHoursForDay::fromStrings($openingHours);
239
        });
240
    }
241
242
    protected function normalizeDayName(string $day)
243
    {
244
        $day = strtolower($day);
245
246
        if (! Day::isValid($day)) {
247
            throw new InvalidDayName();
248
        }
249
250
        return $day;
251
    }
252
253
    protected function applyTimezone(DateTimeInterface $date)
254
    {
255
        if ($this->timezone) {
256
            $date = $date->setTimezone($this->timezone);
257
        }
258
259
        return $date;
260
    }
261
262
    public function filter(callable $callback): array
263
    {
264
        return Arr::filter($this->openingHours, $callback);
265
    }
266
267
    public function map(callable $callback): array
268
    {
269
        return Arr::map($this->openingHours, $callback);
270
    }
271
272
    public function flatMap(callable $callback): array
273
    {
274
        return Arr::flatMap($this->openingHours, $callback);
275
    }
276
277
    public function filterExceptions(callable $callback): array
278
    {
279
        return Arr::filter($this->exceptions, $callback);
280
    }
281
282
    public function mapExceptions(callable $callback): array
283
    {
284
        return Arr::map($this->exceptions, $callback);
285
    }
286
287
    public function flatMapExceptions(callable $callback): array
288
    {
289
        return Arr::flatMap($this->exceptions, $callback);
290
    }
291
292
    public function asStructuredData(): array
293
    {
294 View Code Duplication
        $regularHours = $this->flatMap(function (OpeningHoursForDay $openingHoursForDay, string $day) {
0 ignored issues
show
Duplication introduced by
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...
295
            return $openingHoursForDay->map(function (TimeRange $timeRange) use ($day) {
296
                return [
297
                    '@type' => 'OpeningHoursSpecification',
298
                    'dayOfWeek' => ucfirst($day),
299
                    'opens' => (string) $timeRange->start(),
300
                    'closes' => (string) $timeRange->end(),
301
                ];
302
            });
303
        });
304
305
        $exceptions = $this->flatMapExceptions(function (OpeningHoursForDay $openingHoursForDay, string $date) {
306
            if ($openingHoursForDay->isEmpty()) {
307
                return [[
308
                    '@type' => 'OpeningHoursSpecification',
309
                    'opens' => '00:00',
310
                    'closes' => '00:00',
311
                    'validFrom' => $date,
312
                    'validThrough' => $date,
313
                ]];
314
            }
315
316 View Code Duplication
            return $openingHoursForDay->map(function (TimeRange $timeRange) use ($date) {
0 ignored issues
show
Duplication introduced by
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...
317
                return [
318
                    '@type' => 'OpeningHoursSpecification',
319
                    'opens' => $timeRange->start(),
320
                    'closes' => $timeRange->end(),
321
                    'validFrom' => $date,
322
                    'validThrough' => $date,
323
                ];
324
            });
325
        });
326
327
        return array_merge($regularHours, $exceptions);
328
    }
329
}
330