Passed
Push — master ( 78fe79...12c952 )
by Florian
01:53
created

ImportService::parseReservations()   C

Complexity

Conditions 13
Paths 9

Size

Total Lines 56
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 32
c 1
b 0
f 0
dl 0
loc 56
rs 6.6166
cc 13
nc 9
nop 5

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the vseth-semesterly-reports project.
5
 *
6
 * (c) Florian Moser <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace App\Service;
13
14
use App\Entity\Reservation;
15
use App\Entity\User;
16
use App\Helper\DateTimeHelper;
17
use App\Model\ImportStatistics;
18
use App\Service\Interfaces\BillServiceInterface;
19
use App\Service\Interfaces\ImportServiceInterface;
20
use Doctrine\Persistence\ManagerRegistry;
21
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
22
use Symfony\Component\HttpFoundation\File\UploadedFile;
23
24
class ImportService implements ImportServiceInterface
25
{
26
    /**
27
     * @var string
28
     */
29
    private $basePath;
30
31
    /** @var ManagerRegistry */
32
    private $doctrine;
33
34
    /** @var BillServiceInterface */
35
    private $billService;
36
37
    const DELIMITER = ',';
38
39
    /**
40
     * ImportService constructor.
41
     *
42
     * @param BillServiceInterface $billService
43
     */
44
    public function __construct(ParameterBagInterface $parameterBag, ManagerRegistry $doctrine, Interfaces\BillServiceInterface $billService)
45
    {
46
        $this->basePath = $parameterBag->get('UPLOAD_DIR');
47
        $this->doctrine = $doctrine;
48
        $this->billService = $billService;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     *
54
     * @throws \Exception
55
     */
56
    public function import(UploadedFile $users, UploadedFile $reservations, \DateTime $periodStart, \DateTime $periodEnd)
57
    {
58
        $reservationsPath = $this->uploadFile($reservations, 'reservations.csv');
59
        $usersPath = $this->uploadFile($users, 'users.csv');
60
61
        $periodStartString = $periodStart->format('Y-m-d');
62
        $periodEndString = $periodEnd->format('Y-m-d');
63
64
        /** @var \DateTime[] $lastSubscriptionEndByUser */
65
        $lastSubscriptionEndByUser = [];
66
        /** @var Reservation[][] $reservationsByUser */
67
        $reservationsByUser = [];
68
        $this->parseReservations($reservationsPath, $periodStartString, $periodEndString, $lastSubscriptionEndByUser, $reservationsByUser);
69
70
        /** @var User[] $users */
71
        $users = [];
72
        $this->parseUsers($usersPath, $reservationsByUser, $lastSubscriptionEndByUser, $users);
73
74
        $this->calculateAmountOwed($users);
75
76
        $this->saveAll($users, $reservationsByUser);
77
78
        $totalReservations = 0;
79
        foreach ($reservationsByUser as $item) {
80
            $totalReservations += \count($item);
81
        }
82
83
        $totalAmountOwed = 0;
84
        foreach ($users as $user) {
85
            $totalAmountOwed += $user->getAmountOwed();
86
        }
87
88
        return new ImportStatistics(\count($users), $totalReservations, $totalAmountOwed);
89
    }
90
91
    /**
92
     * @param User[] $users
93
     */
94
    private function calculateAmountOwed(array $users)
95
    {
96
        foreach ($users as $user) {
97
            $amountOwed = $this->billService->getAmountOwed($user);
98
            $user->setAmountOwed($amountOwed);
99
            $user->setAmountOwedWithFees($amountOwed);
100
        }
101
    }
102
103
    /**
104
     * @throws \Exception
105
     */
106
    private function parseReservations(string $reservationsPath, string $periodStart, string $periodEnd, array &$lastSubscriptionEndByUser, array &$reservationsByUser)
107
    {
108
        $row = 1;
109
        if (($handle = fopen($reservationsPath, 'r')) !== false) {
110
            while (($data = fgetcsv($handle, 1000, self::DELIMITER)) !== false) {
111
                if ($row++ === 1) {
112
                    $this->ensureReservationHeader($data);
113
                }
114
115
                $num = \count($data);
116
                if ($num < 9) {
117
                    continue;
118
                }
119
120
                $user = $data[5];
121
                $start = $data[7];
122
                if ($start < $periodStart) {
123
                    $periodDate = mb_substr($data[7], 0, 10); // 2019-01-01
124
                    if (isset($lastSubscriptionEndByUser[$user]) && $lastSubscriptionEndByUser[$user] > $periodDate) {
125
                        continue;
126
                    }
127
128
                    // remember new subscription
129
                    $newSubscriptiondEnd = DateTimeHelper::getSubscriptionEnd(new \DateTime($periodDate));
130
                    $lastSubscriptionEndByUser[$user] = $newSubscriptiondEnd->format('Y-m-d');
131
132
                    continue;
133
                }
134
135
                if ($start >= $periodStart && $start <= $periodEnd) {
136
                    $reservation = new Reservation();
137
                    $reservation->setStart(new \DateTime($start));
138
                    $reservation->setEnd(new \DateTime($data[8]));
139
140
                    if ($reservation->getEnd()->format('i') === 59) { // add one minute if datetime ends in :59
141
                        $reservation->getEnd()->add(new \DateInterval('PT1M'));
142
                    }
143
144
                    // abort if other weird date failures
145
                    if ($reservation->getEnd()->format('i') !== 0 || $reservation->getStart()->format('i') !== 0) {
146
                        throw new \Exception('can not handle non-full hour start/end times');
147
                    }
148
149
                    $reservation->setRoom($data[6]);
150
151
                    $reservation->setModifiedAt(new \DateTime($data[2]));
152
                    $reservation->setCreatedAt(new \DateTime($data[3]));
153
154
                    $reservationsByUser[$user][] = $reservation;
155
156
                    return;
157
158
                    continue;
0 ignored issues
show
Unused Code introduced by
ContinueNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
159
                }
160
            }
161
            fclose($handle);
162
        }
163
    }
164
165
    /**
166
     * @param Reservation[][] $reservationsByUser
167
     * @param \DateTime[] $lastSubscriptionEndByUser
168
     * @param User[] $users
169
     *
170
     * @throws \Exception
171
     */
172
    private function parseUsers(string $usersPath, array &$reservationsByUser, array $lastSubscriptionEndByUser, array &$users)
173
    {
174
        $row = 1;
175
        if (($handle = fopen($usersPath, 'r')) !== false) {
176
            while (($data = fgetcsv($handle, 1000, self::DELIMITER)) !== false) {
177
                if ($row++ === 1) {
178
                    $this->ensureUsersHeader($data);
179
                }
180
181
                if (\count($data) < 14) {
182
                    continue;
183
                }
184
185
                $userId = $data[0];
186
                if (!isset($reservationsByUser[$userId]) || \count($reservationsByUser[$userId]) === 0) {
187
                    continue;
188
                }
189
190
                $user = new User();
191
                $user->setGivenName($data[8]);
192
                $user->setFamilyName($data[9]);
193
                $user->setAddress($data[10]);
194
                $user->setEmail($data[11]);
195
                $user->setPhone($data[12]);
196
                $user->setCategory($data[14]);
197
198
                $subscriptionEnd = isset($lastSubscriptionEndByUser[$userId]) ? new \DateTime($lastSubscriptionEndByUser[$userId]) : null;
0 ignored issues
show
Bug introduced by
$lastSubscriptionEndByUser[$userId] of type DateTime is incompatible with the type string expected by parameter $time of DateTime::__construct(). ( Ignorable by Annotation )

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

198
                $subscriptionEnd = isset($lastSubscriptionEndByUser[$userId]) ? new \DateTime(/** @scrutinizer ignore-type */ $lastSubscriptionEndByUser[$userId]) : null;
Loading history...
199
                $user->setLastPayedPeriodicFeeEnd($subscriptionEnd);
200
201
                foreach ($reservationsByUser[$userId] as $reservation) {
202
                    $reservation->setUser($user);
203
                    $user->getReservations()->add($reservation);
204
                }
205
206
                $users[] = $user;
207
            }
208
        }
209
    }
210
211
    private function ensureReservationHeader(array $data)
212
    {
213
        $expectedSize = 10;
214
        if (\count($data) !== $expectedSize) {
215
            throw new \Exception('reservation header wrong count');
216
        }
217
218
        $expectedContent = ['id', 'archived', 'moddate', 'createdate', 'position', 'user', 'raum', 'start', 'ende', 'apiid'];
219
        for ($i = 0; $i < $expectedSize; ++$i) {
220
            if ($data[$i] !== $expectedContent[$i]) {
221
                throw new \Exception('the header fields must be ' . implode('', $expectedContent) . ' in that order.');
222
            }
223
        }
224
    }
225
226
    private function ensureUsersHeader(array $data)
227
    {
228
        $expectedSize = 20;
229
        if (\count($data) !== $expectedSize) {
230
            throw new \Exception('reservation header wrong count');
231
        }
232
233
        $expectedContent = ['id', 'archived', 'moddate', 'createdate', 'passwort', 'seitenpagemounts', 'seitenentrytypes', 'anrede', 'vorname', 'nachname', 'adresse', 'email', 'tel', 'admin', 'category', 'passwordhash', 'badge', 'birthday', 'benutzername', 'pwchanged'];
234
        for ($i = 0; $i < $expectedSize; ++$i) {
235
            if ($data[$i] !== $expectedContent[$i]) {
236
                throw new \Exception('the header fields must be ' . implode('', $expectedContent) . ' in that order.');
237
            }
238
        }
239
    }
240
241
    private function uploadFile(UploadedFile $file, string $fileName)
242
    {
243
        $file->move($this->basePath, $fileName);
244
245
        return $this->basePath . \DIRECTORY_SEPARATOR . $fileName;
246
    }
247
248
    /**
249
     * @param User[] $users
250
     * @param Reservation[][] $reservationsByUser
251
     */
252
    private function saveAll(array $users, array $reservationsByUser)
253
    {
254
        $manager = $this->doctrine->getManager();
255
256
        foreach ($users as $user) {
257
            $user->generateAuthenticationCode();
258
            $manager->persist($user);
259
        }
260
261
        foreach ($reservationsByUser as $reservationsBySingleUser) {
262
            foreach ($reservationsBySingleUser as $reservation) {
263
                $manager->persist($reservation);
264
            }
265
        }
266
267
        $manager->flush();
268
    }
269
}
270