Passed
Pull Request — master (#121)
by Manuel
04:10
created

QrBill::setAlternativeSchemes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php declare(strict_types=1);
2
3
namespace Sprain\SwissQrBill;
4
5
use Endroid\QrCode\ErrorCorrectionLevel;
6
use Sprain\SwissQrBill\Constraint\ValidCreditorInformationPaymentReferenceCombination;
7
use Sprain\SwissQrBill\DataGroup\AddressInterface;
8
use Sprain\SwissQrBill\DataGroup\Element\AdditionalInformation;
9
use Sprain\SwissQrBill\DataGroup\Element\AlternativeScheme;
10
use Sprain\SwissQrBill\DataGroup\Element\CreditorInformation;
11
use Sprain\SwissQrBill\DataGroup\Element\Header;
12
use Sprain\SwissQrBill\DataGroup\Element\PaymentAmountInformation;
13
use Sprain\SwissQrBill\DataGroup\Element\PaymentReference;
14
use Sprain\SwissQrBill\DataGroup\EmptyElement\EmptyAdditionalInformation;
15
use Sprain\SwissQrBill\DataGroup\EmptyElement\EmptyAddress;
16
use Sprain\SwissQrBill\DataGroup\QrCodeableInterface;
17
use Sprain\SwissQrBill\Exception\InvalidQrBillDataException;
18
use Sprain\SwissQrBill\QrCode\QrCode;
19
use Sprain\SwissQrBill\String\StringModifier;
20
use Sprain\SwissQrBill\Validator\SelfValidatableInterface;
21
use Sprain\SwissQrBill\Validator\SelfValidatableTrait;
22
use Symfony\Component\Validator\Constraints as Assert;
23
use Symfony\Component\Validator\Mapping\ClassMetadata;
24
25
final class QrBill implements SelfValidatableInterface
26
{
27
    use SelfValidatableTrait;
28
29
    private const SWISS_CROSS_LOGO_FILE = __DIR__ . '/../assets/swiss-cross.optimized.png';
30
    private const PX_QR_CODE = 543;    // recommended 46x46 mm in px @ 300dpi – in pixel based outputs, the final image size may be slightly different, depending on the qr code contents
31
    private const PX_SWISS_CROSS = 83; // recommended 7x7 mm in px @ 300dpi
32
33
    private Header $header;
34
    private ?CreditorInformation $creditorInformation = null;
35
    private ?AddressInterface $creditor = null;
36
    private ?PaymentAmountInformation $paymentAmountInformation = null;
37
    private ?AddressInterface $ultimateDebtor = null;
38
    private ?PaymentReference $paymentReference = null;
39
    private ?AdditionalInformation $additionalInformation = null;
40
    /** @var AlternativeScheme[] */
41
    private array $alternativeSchemes = [];
42
43
    private function __construct(Header $header)
44
    {
45
        $this->header = $header;
46
    }
47
48
    public static function create(): self
49
    {
50
        $header = Header::create(
51
            Header::QRTYPE_SPC,
52
            Header::VERSION_0200,
53
            Header::CODING_LATIN
54
        );
55
56
        return new self($header);
57
    }
58
59
    public function getHeader(): Header
60
    {
61
        return $this->header;
62
    }
63
64
    public function setHeader(Header $header): self
65
    {
66
        $this->header = $header;
67
68
        return $this;
69
    }
70
71
    public function getCreditorInformation(): ?CreditorInformation
72
    {
73
        return $this->creditorInformation;
74
    }
75
76
    public function setCreditorInformation(CreditorInformation $creditorInformation): self
77
    {
78
        $this->creditorInformation = $creditorInformation;
79
80
        return $this;
81
    }
82
83
    public function getCreditor(): ?AddressInterface
84
    {
85
        return $this->creditor;
86
    }
87
88
    public function setCreditor(AddressInterface $creditor): self
89
    {
90
        $this->creditor = $creditor;
91
        
92
        return $this;
93
    }
94
95
    public function getPaymentAmountInformation(): ?PaymentAmountInformation
96
    {
97
        return $this->paymentAmountInformation;
98
    }
99
100
    public function setPaymentAmountInformation(PaymentAmountInformation $paymentAmountInformation): self
101
    {
102
        $this->paymentAmountInformation = $paymentAmountInformation;
103
        
104
        return $this;
105
    }
106
107
    public function getUltimateDebtor(): ?AddressInterface
108
    {
109
        return $this->ultimateDebtor;
110
    }
111
112
    public function setUltimateDebtor(AddressInterface $ultimateDebtor): self
113
    {
114
        $this->ultimateDebtor = $ultimateDebtor;
115
        
116
        return $this;
117
    }
118
119
    public function getPaymentReference(): ?PaymentReference
120
    {
121
        return $this->paymentReference;
122
    }
123
124
    public function setPaymentReference(PaymentReference $paymentReference): self
125
    {
126
        $this->paymentReference = $paymentReference;
127
        
128
        return $this;
129
    }
130
131
    public function getAdditionalInformation(): ?AdditionalInformation
132
    {
133
        return $this->additionalInformation;
134
    }
135
136
    public function setAdditionalInformation(AdditionalInformation $additionalInformation): self
137
    {
138
        $this->additionalInformation = $additionalInformation;
139
140
        return $this;
141
    }
142
143
    public function getAlternativeSchemes(): array
144
    {
145
        return $this->alternativeSchemes;
146
    }
147
148
    public function setAlternativeSchemes(array $alternativeSchemes): self
149
    {
150
        $this->alternativeSchemes = $alternativeSchemes;
151
152
        return $this;
153
    }
154
155
    public function addAlternativeScheme(AlternativeScheme $alternativeScheme): self
156
    {
157
        $this->alternativeSchemes[] = $alternativeScheme;
158
159
        return $this;
160
    }
161
162
    /**
163
     * @throws InvalidQrBillDataException
164
     */
165
    public function getQrCode(): QrCode
166
    {
167
        if (!$this->isValid()) {
168
            throw new InvalidQrBillDataException(
169
                'The provided data is not valid to generate a qr code. Use getViolations() to find details.'
170
            );
171
        }
172
173
        $qrCode = new QrCode();
174
        $qrCode->setText($this->getQrCodeContent());
175
        $qrCode->setSize(self::PX_QR_CODE);
176
        $qrCode->setLogoHeight(self::PX_SWISS_CROSS);
177
        $qrCode->setLogoWidth(self::PX_SWISS_CROSS);
178
        $qrCode->setLogoPath(self::SWISS_CROSS_LOGO_FILE);
179
        $qrCode->setRoundBlockSize(true, \Endroid\QrCode\QrCode::ROUND_BLOCK_SIZE_MODE_ENLARGE);
180
        $qrCode->setMargin(0);
181
        $qrCode->setErrorCorrectionLevel(new ErrorCorrectionLevel(ErrorCorrectionLevel::MEDIUM));
182
183
        return $qrCode;
184
    }
185
186
    private function getQrCodeContent(): string
187
    {
188
        $elements = [
189
            $this->getHeader(),
190
            $this->getCreditorInformation(),
191
            $this->getCreditor(),
192
            new EmptyAddress(), # Placeholder for ultimateCreditor, which is currently not yet allowed to be used by the implementation guidelines
193
            $this->getPaymentAmountInformation(),
194
            $this->getUltimateDebtor() ?: new EmptyAddress(),
195
            $this->getPaymentReference(),
196
            $this->getAdditionalInformation() ?: new EmptyAdditionalInformation(),
197
            $this->getAlternativeSchemes()
198
        ];
199
200
        $qrCodeStringElements = $this->extractQrCodeDataFromElements($elements);
201
202
        return implode("\n", $qrCodeStringElements);
203
    }
204
205
    private function extractQrCodeDataFromElements(array $elements): array
206
    {
207
        $qrCodeElements = [];
208
209
        foreach ($elements as $element) {
210
            if ($element instanceof QrCodeableInterface) {
211
                $qrCodeElements = array_merge($qrCodeElements, $element->getQrCodeData());
212
            } elseif (is_array($element)) {
213
                $qrCodeElements = array_merge($qrCodeElements, $this->extractQrCodeDataFromElements($element));
214
            }
215
        }
216
217
        array_walk($qrCodeElements, function (&$string) {
218
            if (is_string($string)) {
219
                $string = StringModifier::replaceLineBreaksWithString($string);
220
                $string = StringModifier::replaceMultipleSpacesWithOne($string);
221
                $string = trim($string);
222
            }
223
        });
224
225
        return $qrCodeElements;
226
    }
227
228
    public static function loadValidatorMetadata(ClassMetadata $metadata): void
229
    {
230
        $metadata->addConstraint(
231
            new ValidCreditorInformationPaymentReferenceCombination()
232
        );
233
234
        $metadata->addPropertyConstraints('header', [
235
            new Assert\NotNull(),
236
            new Assert\Valid()
237
        ]);
238
239
        $metadata->addPropertyConstraints('creditorInformation', [
240
            new Assert\NotNull(),
241
            new Assert\Valid()
242
        ]);
243
244
        $metadata->addPropertyConstraints('creditor', [
245
            new Assert\NotNull(),
246
            new Assert\Valid()
247
        ]);
248
249
        $metadata->addPropertyConstraints('paymentAmountInformation', [
250
            new Assert\NotNull(),
251
            new Assert\Valid()
252
        ]);
253
254
        $metadata->addPropertyConstraints('ultimateDebtor', [
255
            new Assert\Valid()
256
        ]);
257
258
        $metadata->addPropertyConstraints('paymentReference', [
259
            new Assert\NotNull(),
260
            new Assert\Valid()
261
        ]);
262
263
        $metadata->addPropertyConstraints('alternativeSchemes', [
264
            new Assert\Count([
265
                'max' => 2
266
            ]),
267
            new Assert\Valid([
268
                'traverse' => true
269
            ])
270
        ]);
271
    }
272
}
273