Completed
Pull Request — master (#256)
by Kristof
04:51
created

CalendarFactory   C

Complexity

Total Complexity 40

Size/Duplication

Total Lines 283
Duplicated Lines 5.3 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 17
dl 15
loc 283
rs 6.6773
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
F createFromCdbCalendar() 15 133 23
A createFromWeekScheme() 0 18 2
B createOpeningHoursFromWeekScheme() 0 31 6
A openingHoursToArray() 0 22 2
B createMergedOpeningHours() 0 22 5
B createTimestamp() 0 25 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 CalendarFactory 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 CalendarFactory, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace CultuurNet\UDB3;
4
5
use CultuurNet\UDB3\Cdb\DateTimeFactory;
6
use ValueObjects\DateTime\Time;
7
use ValueObjects\DateTime\WeekDay;
8
9
class CalendarFactory implements CalendarFactoryInterface
10
{
11
    /**
12
     * @inheritdoc
13
     */
14
    public function createFromCdbCalendar(\CultureFeed_Cdb_Data_Calendar $cdbCalendar)
15
    {
16
        //
17
        // Get the calendar type.
18
        //
19
        $calendarType = '';
20
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_Permanent) {
21
            $calendarType = 'permanent';
22
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
23
            $calendarType = 'periodic';
24
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
25
            $calendarType = 'single';
26
            if (iterator_count($cdbCalendar) > 1) {
27
                $calendarType = 'multiple';
28
            }
29
        }
30
31
        //
32
        // Get the start day.
33
        //
34
        $cdbCalendar->rewind();
35
        $startDateString = '';
36
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
37
            /** @var \CultureFeed_Cdb_Data_Calendar_Period $period */
38
            $period = $cdbCalendar->current();
39
            $startDateString = $period->getDateFrom() . 'T00:00:00';
40 View Code Duplication
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
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...
41
            /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
42
            $timestamp = $cdbCalendar->current();
43
            if ($timestamp->getStartTime()) {
44
                $startDateString = $timestamp->getDate() . 'T' . $timestamp->getStartTime();
45
            } else {
46
                $startDateString = $timestamp->getDate() . 'T00:00:00';
47
            }
48
        }
49
        $startDate = !empty($startDateString) ? DateTimeFactory::dateTimeFromDateString($startDateString) : null;
50
51
        //
52
        // Get the end day.
53
        //
54
        $cdbCalendar->rewind();
55
        $endDateString = '';
56
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
57
            /** @var \CultureFeed_Cdb_Data_Calendar_Period $period */
58
            $period = $cdbCalendar->current();
59
            $endDateString = $period->getDateTo() . 'T00:00:00';
60
        } else if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
61
            $firstTimestamp = $cdbCalendar->current();
62
            /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
63
            $cdbCalendarAsArray = iterator_to_array($cdbCalendar);
64
            $timestamp = iterator_count($cdbCalendar) > 1 ? end($cdbCalendarAsArray) : $firstTimestamp;
65 View Code Duplication
            if ($timestamp->getEndTime()) {
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...
66
                $endDateString = $timestamp->getDate() . 'T' . $timestamp->getEndTime();
67
            } else {
68
                $endTime = $timestamp->getStartTime() ? $timestamp->getStartTime() : '00:00:00';
69
                $endDateString = $timestamp->getDate() . 'T' . $endTime;
70
            }
71
        }
72
        $endDate = !empty($endDateString) ? DateTimeFactory::dateTimeFromDateString($endDateString) : null;
73
74
        //
75
        // Get the time stamps.
76
        //
77
        $cdbCalendar->rewind();
78
        $timestamps = [];
79
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
80
            while ($cdbCalendar->valid()) {
81
                /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $timestamp */
82
                $timestamp = $cdbCalendar->current();
83
                $cdbCalendar->next();
84
85
                $startTime = $timestamp->getStartTime() ? $timestamp->getStartTime() : '00:00:00';
86
                $startDateString = $timestamp->getDate() . 'T' . $startTime;
87
88
                if ($timestamp->getEndTime()) {
89
                    $endDateString = $timestamp->getDate() . 'T' . $timestamp->getEndTime();
90
                } else {
91
                    $endDateString = $timestamp->getDate() . 'T' . $startTime;
92
                }
93
94
                $timestamps[] = $this->createTimestamp(
95
                    $startDateString,
96
                    $endDateString
97
                );
98
            }
99
        }
100
101
        //
102
        // Get the opening hours.
103
        //
104
        $cdbCalendar->rewind();
105
        $openingHoursAsArray = [];
106
107
        $weekSchema = null;
108
        if ($cdbCalendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
109
            $period = $cdbCalendar->current();
110
            $weekSchema = $period->getWeekScheme();
111
        } else if ($cdbCalendar instanceof  \CultureFeed_Cdb_Data_Calendar_Permanent) {
112
            $weekSchema = $cdbCalendar->getWeekScheme();
113
        }
114
115
        if ($weekSchema) {
116
            $openingHours = $this->createOpeningHoursFromWeekScheme($weekSchema);
117
            $openingHoursAsArray = $this->openingHoursToArray($openingHours);
118
        }
119
120
        // End date might be before start date in cdbxml when event takes place
121
        // between e.g. 9 PM and 3 AM (the next day). UDB3 does not support this
122
        // and gracefully ignores the end time.
123
        //
124
        // Example cdbxml:
125
        //
126
        // <timestamp>
127
        //   <date>2016-12-16</date>
128
        //   <timestart>21:00:00</timestart>
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
129
        //   <timeend>05:00:00</timeend>
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
130
        // </timestamp>
131
        //
132
        if ($endDate < $startDate) {
133
            $endDate = $startDate;
134
        }
135
136
        //
137
        // Create the calendar value object.
138
        //
139
        return new Calendar(
140
            CalendarType::fromNative($calendarType),
141
            $startDate,
142
            $endDate,
143
            $timestamps,
144
            $openingHoursAsArray
145
        );
146
    }
147
148
    /**
149
     * @param \CultureFeed_Cdb_Data_Calendar_Weekscheme|null $weekScheme
150
     * @return Calendar
151
     */
152
    public function createFromWeekScheme(
153
        \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme = null
154
    ) {
155
        $openingHoursAsArray = [];
156
157
        if ($weekScheme) {
158
            $openingHours = $this->createOpeningHoursFromWeekScheme($weekScheme);
159
            $openingHoursAsArray = $this->openingHoursToArray($openingHours);
160
        }
161
162
        return new Calendar(
163
            CalendarType::PERMANENT(),
164
            null,
165
            null,
166
            [],
167
            $openingHoursAsArray
168
        );
169
    }
170
171
    /**
172
     * @param \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme
173
     * @return OpeningHours
174
     */
175
    private function createOpeningHoursFromWeekScheme(
176
        \CultureFeed_Cdb_Data_Calendar_Weekscheme $weekScheme
177
    ) {
178
        $openingHours = new OpeningHours();
179
180
        foreach ($weekScheme->getDays() as $day) {
181
            if ($day->isOpen()) {
182
                /** @var \CultureFeed_Cdb_Data_Calendar_OpeningTime[] $openingTimes */
183
                $openingTimes = $day->getOpeningTimes();
184
185
                $opens = \DateTime::createFromFormat(
186
                    'H:i:s',
187
                    $openingTimes ? $openingTimes[0]->getOpenFrom() : '00:00:00'
188
                );
189
                $closes = \DateTime::createFromFormat(
190
                    'H:i:s',
191
                    $openingTimes ? $openingTimes[0]->getOpenTill() : '00:00:00'
192
                );
193
194
                $openingHour = new OpeningHour(
195
                    WeekDay::fromNative(ucfirst($day->getDayName())),
196
                    Time::fromNativeDateTime($opens),
0 ignored issues
show
Security Bug introduced by
It seems like $opens defined by \DateTime::createFromFor...penFrom() : '00:00:00') on line 185 can also be of type false; however, ValueObjects\DateTime\Time::fromNativeDateTime() does only seem to accept object<DateTime>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
197
                    $closes ? Time::fromNativeDateTime($closes) : Time::fromNativeDateTime($opens)
0 ignored issues
show
Security Bug introduced by
It seems like $opens defined by \DateTime::createFromFor...penFrom() : '00:00:00') on line 185 can also be of type false; however, ValueObjects\DateTime\Time::fromNativeDateTime() does only seem to accept object<DateTime>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
198
                );
199
200
                $openingHours->addOpeningHour($openingHour);
201
            }
202
        }
203
204
        return $openingHours;
205
    }
206
207
    /**
208
     * @param OpeningHours $openingHours
209
     * @return array
210
     */
211
    private function openingHoursToArray(OpeningHours $openingHours)
212
    {
213
        $openingHoursAsArray = [];
214
215
        $mergedOpeningHours = $this->createMergedOpeningHours($openingHours);
216
217
        foreach ($mergedOpeningHours as $mergedOpeningHour) {
218
            $openingHour = $mergedOpeningHour->getOpeningHours()[0];
219
            $openingHoursAsArray[] = [
220
                'dayOfWeek' => array_map(
221
                    function (WeekDay $weekDay) {
222
                        return strtolower($weekDay->toNative());
223
                    },
224
                    $mergedOpeningHour->getWeekDays()
225
                ),
226
                'opens' => $openingHour->getOpens()->toNativeDateTime()->format('H:i'),
227
                'closes' => $openingHour->getCloses()->toNativeDateTime()->format('H:i'),
228
            ];
229
        }
230
231
        return $openingHoursAsArray;
232
    }
233
234
    /**
235
     * @param OpeningHours $openingHours
236
     * @return OpeningHours[]
237
     */
238
    private function createMergedOpeningHours(OpeningHours $openingHours)
239
    {
240
        /** @var OpeningHours[] $mergedOpeningHours */
241
        $mergedOpeningHours = [];
242
243
        foreach ($openingHours->getOpeningHours() as $openingHour) {
244
            $merged = false;
245
            foreach ($mergedOpeningHours as $mergedOpeningHour) {
246
                if ($mergedOpeningHour->equalOpeningHour($openingHour)) {
247
                    $mergedOpeningHour->addOpeningHour($openingHour);
248
                    $merged = true;
249
                    break;
250
                }
251
            }
252
253
            if (!$merged) {
254
                $mergedOpeningHours[] = new OpeningHours([$openingHour]);
255
            }
256
        }
257
258
        return $mergedOpeningHours;
259
    }
260
261
    /**
262
     * @param string $startDateString
263
     * @param string $endDateString
264
     * @return Timestamp
265
     */
266
    private function createTimestamp(
267
        $startDateString,
268
        $endDateString
269
    ) {
270
        $startDate = DateTimeFactory::dateTimeFromDateString($startDateString);
271
        $endDate = DateTimeFactory::dateTimeFromDateString($endDateString);
272
273
        // End date might be before start date in cdbxml when event takes place
274
        // between e.g. 9 PM and 3 AM (the next day). UDB3 does not support this
275
        // and gracefully ignores the end time.
276
        //
277
        // Example cdbxml:
278
        //
279
        // <timestamp>
280
        //   <date>2016-12-16</date>
281
        //   <timestart>21:00:00</timestart>
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
282
        //   <timeend>05:00:00</timeend>
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
283
        // </timestamp>
284
        //
285
        if ($endDate < $startDate) {
286
            $endDate = $startDate;
287
        }
288
289
        return new Timestamp($startDate, $endDate);
290
    }
291
}
292