Passed
Branch 1.4 (47c6b6)
by G
04:43
created

VatNumberValidator::hasVatNumberForCompany()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 2
nop 2
dl 0
loc 15
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gewebe\SyliusVATPlugin\Validator\Constraints;
6
7
use Gewebe\SyliusVATPlugin\Entity\VatNumberAddressInterface;
8
use Gewebe\SyliusVATPlugin\Vat\Number\ClientException;
9
use Gewebe\SyliusVATPlugin\Vat\Number\VatNumberValidatorInterface;
10
use Gewebe\SyliusVATPlugin\Vat\Number\VatNumberValidatorProviderInterface;
11
use Symfony\Component\Validator\Constraint;
12
use Symfony\Component\Validator\ConstraintValidator;
13
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
14
use Symfony\Component\Validator\Exception\UnexpectedValueException;
15
16
/**
17
 * @psalm-suppress PropertyNotSetInConstructor
18
 */
19
class VatNumberValidator extends ConstraintValidator
20
{
21
    private ?VatNumberValidatorInterface $validator = null;
22
23
    public function __construct(
24
        private VatNumberValidatorProviderInterface $validatorProvider,
25
        private bool $isActive = true,
26
        private bool $validateCountry = true,
27
        private bool $validateExistence = true,
28
        private bool $isCompanyVatRequired = true,
29
        private array $requiredCountries = [],
30
    ) {
31
    }
32
33
    public function validate(mixed $value, Constraint $constraint): void
34
    {
35
        if (!$constraint instanceof VatNumber) {
36
            throw new UnexpectedTypeException($constraint, VatNumber::class);
37
        }
38
39
        if (!$value instanceof VatNumberAddressInterface) {
40
            throw new UnexpectedValueException($value, VatNumberAddressInterface::class);
41
        }
42
43
        if (!$this->hasVatNumberForCompany($value, $constraint)) {
44
            return;
45
        }
46
47
        if (!$this->hasVatNumberForCountry($value, $constraint)) {
48
            return;
49
        }
50
51
        if (!$value->hasVatNumber()) {
52
            return;
53
        }
54
55
        $this->validateVatNumberAddress($value, $constraint);
56
    }
57
58
    private function hasVatNumberForCompany(VatNumberAddressInterface $address, VatNumber $constraint): bool
59
    {
60
        if ($this->isCompanyVatRequired &&
61
            null !== $address->getCompany() &&
62
            '' !== $address->getCompany() &&
63
            !$address->hasVatNumber()) {
64
            $this->context->buildViolation($constraint->messageRequiredForCompany)
65
                ->atPath($constraint->vatNumberPath)
66
                ->addViolation()
67
            ;
68
69
            return false;
70
        }
71
72
        return true;
73
    }
74
75
    private function hasVatNumberForCountry(VatNumberAddressInterface $address, VatNumber $constraint): bool
76
    {
77
        if (count($this->requiredCountries) > 0 &&
78
            in_array($address->getCountryCode(), $this->requiredCountries, true) &&
79
            !$address->hasVatNumber()) {
80
            $this->context->buildViolation($constraint->messageRequired)
81
                ->atPath($constraint->vatNumberPath)
82
                ->addViolation()
83
            ;
84
85
            return false;
86
        }
87
88
        return true;
89
    }
90
91
    private function setValidator(VatNumberAddressInterface $address): bool
92
    {
93
        $countryCode = $address->getCountryCode();
94
        if (null === $countryCode || '' === $countryCode) {
95
            return false;
96
        }
97
98
        $this->validator = $this->validatorProvider->getValidator($countryCode);
99
        if ($this->validator instanceof VatNumberValidatorInterface) {
100
            return true;
101
        }
102
103
        return false;
104
    }
105
106
    private function validateVatNumberAddress(VatNumberAddressInterface $address, VatNumber $constraint): bool
107
    {
108
        if (!$this->isActive) {
109
            return false;
110
        }
111
112
        if (!$this->setValidator($address)) {
113
            return false;
114
        }
115
116
        if (!$this->validateFormat($address, $constraint)) {
117
            return false;
118
        }
119
120
        if ($this->validateCountry && !$this->validateCountry($address, $constraint)) {
121
            return false;
122
        }
123
124
        if ($this->validateExistence) {
125
            return $this->validateExistence($address, $constraint);
126
        }
127
128
        return false;
129
    }
130
131
    /**
132
     * check vat number format
133
     */
134
    private function validateFormat(VatNumberAddressInterface $address, VatNumber $constraint): bool
135
    {
136
        if ($this->validator === null) {
137
            return false;
138
        }
139
140
        $vatNumber = $address->getVatNumber();
141
142
        if (null === $vatNumber || !$this->validator->validateFormat($vatNumber)) {
143
            $this->context->buildViolation($constraint->messageFormat)
144
                ->atPath($constraint->vatNumberPath)
145
                ->addViolation()
146
            ;
147
148
            return false;
149
        }
150
151
        return true;
152
    }
153
154
    /**
155
     * check vat number country is same as address country
156
     */
157
    private function validateCountry(VatNumberAddressInterface $address, VatNumber $constraint): bool
158
    {
159
        if ($this->validator === null) {
160
            return false;
161
        }
162
163
        $vatNumber = $address->getVatNumber();
164
        $countryCode = $address->getCountryCode();
165
166
        if (null === $vatNumber ||
167
            null === $countryCode ||
168
            !$this->validator->validateCountry($vatNumber, $countryCode)) {
169
            $this->context->buildViolation($constraint->messageCountry)
170
                ->atPath($constraint->vatNumberPath)
171
                ->addViolation()
172
            ;
173
174
            return false;
175
        }
176
177
        return true;
178
    }
179
180
    /**
181
     * check vat number existence
182
     */
183
    private function validateExistence(VatNumberAddressInterface $address, VatNumber $constraint): bool
184
    {
185
        if ($this->validator === null) {
186
            return false;
187
        }
188
189
        try {
190
            $vatNumber = $address->getVatNumber();
191
192
            $valid = null !== $vatNumber && $this->validator->validate($vatNumber);
193
194
            $address->setVatValid($valid);
195
        } catch (ClientException $e) {
196
            // ignore VAT client exceptions (when the service is down)
197
            // this could mean that an unexisting VAT number passes validation,
198
            // but it's (probably) better than a hard-error
199
            return true;
200
        }
201
202
        if (false === $valid) {
203
            $this->context->buildViolation($constraint->messageVerified)
204
                ->atPath($constraint->vatNumberPath)
205
                ->addViolation()
206
            ;
207
208
            return false;
209
        }
210
211
        return true;
212
    }
213
}
214