JsonDonorRepository::updateDonorComment()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of byrokrat\giroapp.
5
 *
6
 * byrokrat\giroapp is free software: you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * byrokrat\giroapp is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with byrokrat\giroapp. If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 * Copyright 2016-21 Hannes Forsgård
20
 */
21
22
declare(strict_types=1);
23
24
namespace byrokrat\giroapp\Db\Json;
25
26
use byrokrat\giroapp\Db\DonorRepositoryInterface;
27
use byrokrat\giroapp\Exception\DonorDoesNotExistException;
28
use byrokrat\giroapp\Exception\DonorAlreadyExistsException;
29
use byrokrat\giroapp\Domain\Donor;
30
use byrokrat\giroapp\Domain\DonorCollection;
31
use byrokrat\giroapp\Domain\DonorFactory;
32
use byrokrat\giroapp\Domain\PostalAddress;
33
use byrokrat\giroapp\Utils\SystemClock;
34
use byrokrat\giroapp\Domain\State\StateInterface;
35
use hanneskod\yaysondb\CollectionInterface;
36
use hanneskod\yaysondb\Operators as y;
37
use Money\Money;
38
use Money\MoneyFormatter;
39
40
final class JsonDonorRepository implements DonorRepositoryInterface
41
{
42
    public const TYPE = 'giroapp/donor:1.0';
43
44
    /** @var CollectionInterface&iterable<array> */
45
    private $collection;
46
47
    /** @var DonorFactory */
48
    private $donorFactory;
49
50
    /** @var SystemClock */
51
    private $systemClock;
52
53
    /** @var MoneyFormatter */
54
    private $moneyFormatter;
55
56
    /**
57
     * @param CollectionInterface&iterable<array> $collection
58
     */
59
    public function __construct(
60
        CollectionInterface $collection,
61
        DonorFactory $donorFactory,
62
        SystemClock $systemClock,
63
        MoneyFormatter $moneyFormatter
64
    ) {
65
        $this->collection = $collection;
66
        $this->donorFactory = $donorFactory;
67
        $this->systemClock = $systemClock;
68
        $this->moneyFormatter = $moneyFormatter;
69
    }
70
71
    public function findAll(): DonorCollection
72
    {
73
        return new DonorCollection(function () {
0 ignored issues
show
Bug introduced by
function(...) { /* ... */ } of type callable is incompatible with the type iterable expected by parameter $donors of byrokrat\giroapp\Domain\...llection::__construct(). ( Ignorable by Annotation )

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

73
        return new DonorCollection(/** @scrutinizer ignore-type */ function () {
Loading history...
74
            foreach ($this->collection as $doc) {
75
                yield $this->createDonor($doc);
76
            }
77
        });
78
    }
79
80
    public function findByMandateKey(string $mandateKey): ?Donor
81
    {
82
        if ($this->collection->has($mandateKey)) {
83
            return $this->createDonor($this->collection->read($mandateKey));
84
        }
85
86
        return null;
87
    }
88
89
    public function requireByMandateKey(string $mandateKey): Donor
90
    {
91
        if ($donor = $this->findByMandateKey($mandateKey)) {
92
            return $donor;
93
        }
94
95
        throw new DonorDoesNotExistException("Unknown donor key $mandateKey");
96
    }
97
98
    public function findByPayerNumber(string $payerNumber): ?Donor
99
    {
100
        $doc = $this->collection->findOne(
101
            y::doc(['payer_number' => y::equals($payerNumber)])
102
        );
103
104
        if (empty($doc)) {
105
            return null;
106
        }
107
108
        return $this->createDonor($doc);
109
    }
110
111
    public function requireByPayerNumber(string $payerNumber): Donor
112
    {
113
        if ($donor = $this->findByPayerNumber($payerNumber)) {
114
            return $donor;
115
        }
116
117
        throw new DonorDoesNotExistException("Unknown payer number $payerNumber");
118
    }
119
120
    public function addNewDonor(Donor $newDonor): void
121
    {
122
        $expr = y::atLeastOne(
123
            y::doc(['mandate_key' => y::equals($newDonor->getMandateKey())]),
124
            y::doc(['payer_number' => y::equals($newDonor->getPayerNumber())]),
125
            y::doc(['donor_id' => y::equals($newDonor->getDonorId())])
126
        );
127
128
        if ($doc = $this->collection->findOne($expr)) {
129
            throw new DonorAlreadyExistsException(
130
                sprintf(
131
                    "Unable to save donor %s due to a conflict with donor %s",
132
                    $newDonor->getMandateKey(),
133
                    $doc['mandate_key'] ?? ''
134
                )
135
            );
136
        }
137
138
        $this->collection->insert($this->createDoc($newDonor), $newDonor->getMandateKey());
139
    }
140
141
    public function deleteDonor(Donor $donor): void
142
    {
143
        $this->requireByMandateKey($donor->getMandateKey());
144
145
        $this->collection->delete(
146
            y::doc(['mandate_key' => y::equals($donor->getMandateKey())])
147
        );
148
    }
149
150
    public function updateDonorName(Donor $donor, string $newName): void
151
    {
152
        $this->updateDonor($donor, ['name' => $newName]);
153
    }
154
155
    public function updateDonorState(Donor $donor, StateInterface $newState): void
156
    {
157
        $this->updateDonor($donor, ['state' => $newState->getStateId()]);
158
    }
159
160
    public function updateDonorPayerNumber(Donor $donor, string $newPayerNumber): void
161
    {
162
        $expr = y::doc([
163
            'payer_number' => y::equals($newPayerNumber),
164
            'mandate_key' => y::not(y::equals($donor->getMandateKey())),
165
        ]);
166
167
        if ($doc = $this->collection->findOne($expr)) {
168
            throw new DonorAlreadyExistsException(
169
                sprintf(
170
                    "Unable to set payer number %s due to a conflict with donor %s",
171
                    $donor->getMandateKey(),
172
                    $doc['mandate_key'] ?? ''
173
                )
174
            );
175
        }
176
177
        $this->updateDonor($donor, ['payer_number' => $newPayerNumber]);
178
    }
179
180
    public function updateDonorAmount(Donor $donor, Money $newAmount): void
181
    {
182
        $this->updateDonor($donor, ['donation_amount' => $this->moneyFormatter->format($newAmount)]);
183
    }
184
185
    public function updateDonorAddress(Donor $donor, PostalAddress $newAddress): void
186
    {
187
        $this->updateDonor($donor, [
188
            'address' => [
189
                'line1' => $newAddress->getLine1(),
190
                'line2' => $newAddress->getLine2(),
191
                'line3' => $newAddress->getLine3(),
192
                'postal_code' => $newAddress->getPostalCode(),
193
                'postal_city' => $newAddress->getPostalCity(),
194
            ]
195
        ]);
196
    }
197
198
    public function updateDonorEmail(Donor $donor, string $newEmail): void
199
    {
200
        $this->updateDonor($donor, ['email' => $newEmail]);
201
    }
202
203
    public function updateDonorPhone(Donor $donor, string $newPhone): void
204
    {
205
        $this->updateDonor($donor, ['phone' => $newPhone]);
206
    }
207
208
    public function updateDonorComment(Donor $donor, string $newComment): void
209
    {
210
        $this->updateDonor($donor, ['comment' => $newComment]);
211
    }
212
213
    public function setDonorAttribute(Donor $donor, string $key, string $value): void
214
    {
215
        $attributes = $this->createDoc($this->requireByMandateKey($donor->getMandateKey()))['attributes'];
216
        $attributes[$key] = $value;
217
        $this->updateDonor($donor, ['attributes' => $attributes]);
218
    }
219
220
    public function deleteDonorAttribute(Donor $donor, string $key): void
221
    {
222
        $attributes = $this->createDoc($this->requireByMandateKey($donor->getMandateKey()))['attributes'];
223
        unset($attributes[$key]);
224
        $this->updateDonor($donor, ['attributes' => $attributes]);
225
    }
226
227
    /**
228
     * @param array<string, mixed> $updatedValues
229
     */
230
    private function updateDonor(Donor $donor, array $updatedValues): void
231
    {
232
        $currentValues = $this->createDoc($this->requireByMandateKey($donor->getMandateKey()));
233
234
        $doc = array_merge($currentValues, $updatedValues);
235
236
        $doc['updated'] = $this->systemClock->getNow()->format(\DateTime::W3C);
237
238
        $this->collection->insert($doc, $donor->getMandateKey());
239
    }
240
241
    /**
242
     * @return array<string, mixed>
243
     */
244
    private function createDoc(Donor $donor): array
245
    {
246
        return [
247
            'type' => self::TYPE,
248
            'mandate_key' => $donor->getMandateKey(),
249
            'state' => $donor->getState()->getStateId(),
250
            'mandate_source' => $donor->getMandateSource(),
251
            'payer_number' => $donor->getPayerNumber(),
252
            'account' => $donor->getAccount()->getNumber(),
253
            'donor_id' => $donor->getDonorId()->format('S-sk'),
254
            'name' => $donor->getName(),
255
            'address' => [
256
                'line1' => $donor->getPostalAddress()->getLine1(),
257
                'line2' => $donor->getPostalAddress()->getLine2(),
258
                'line3' => $donor->getPostalAddress()->getLine3(),
259
                'postal_code' => $donor->getPostalAddress()->getPostalCode(),
260
                'postal_city' => $donor->getPostalAddress()->getPostalCity(),
261
            ],
262
            'email' => $donor->getEmail(),
263
            'phone' => $donor->getPhone(),
264
            'donation_amount' => $this->moneyFormatter->format($donor->getDonationAmount()),
265
            'comment' => $donor->getComment(),
266
            'created' => $donor->getCreated()->format(\DateTime::W3C),
267
            'updated' => $donor->getUpdated()->format(\DateTime::W3C),
268
            'attributes' => $donor->getAttributes(),
269
        ];
270
    }
271
272
    /**
273
     * @param mixed[] $doc
274
     */
275
    private function createDonor(array $doc): Donor
276
    {
277
        return $this->donorFactory->createDonor(
278
            $doc['mandate_key'],
279
            $doc['state'],
280
            $doc['mandate_source'],
281
            $doc['payer_number'],
282
            $doc['account'],
283
            $doc['donor_id'],
284
            $doc['name'],
285
            [
286
                $doc['address']['line1'],
287
                $doc['address']['line2'],
288
                $doc['address']['line3'],
289
                $doc['address']['postal_code'],
290
                $doc['address']['postal_city'],
291
            ],
292
            $doc['email'],
293
            $doc['phone'],
294
            $doc['donation_amount'],
295
            $doc['comment'],
296
            $doc['created'],
297
            $doc['updated'],
298
            $doc['attributes']
299
        );
300
    }
301
}
302