Calculator::getMeasurementMapping()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.5069

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 8
cts 11
cp 0.7272
rs 9.2888
c 0
b 0
f 0
cc 5
nc 4
nop 1
crap 5.5069
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecocode\SyliusBasePricePlugin\Services;
6
7
use Ecocode\SyliusBasePricePlugin\Entity\Mapping;
8
use Ecocode\SyliusBasePricePlugin\Entity\Product\ProductVariantInterface;
9
use RuntimeException;
10
use Sylius\Bundle\MoneyBundle\Templating\Helper\ConvertMoneyHelper;
11
use Sylius\Bundle\MoneyBundle\Templating\Helper\ConvertMoneyHelperInterface;
12
use Sylius\Bundle\MoneyBundle\Templating\Helper\FormatMoneyHelper;
13
use Sylius\Component\Core\Model\ChannelInterface;
14
use Sylius\Component\Core\Model\ChannelPricingInterface;
15
use Sylius\Component\Core\Model\ProductVariantInterface as CoreModelProductVariantInterface;
16
use Symfony\Contracts\Translation\TranslatorInterface;
17
use Twig\Extension\AbstractExtension;
18
use UnitConverter\UnitConverter;
19
use function constant;
20
21
/**
22
 * Class Calculator
23
 * @package Ecocode\SyliusBasePricePlugin\Services
24
 */
25
class Calculator extends AbstractExtension
26
{
27
    /** @var array<string, array<string, array<string, string|float>>> */
28
    private $mappingConfig = [];
29
30
    /** @var bool */
31
    private $useShortUnitName = true;
32
33
    /** @var TranslatorInterface */
34
    private $translator;
35
36
    /** @var UnitConverter|null */
37
    private $unitConverter;
38
39
    /** @var FormatMoneyHelper */
40
    private $moneyFormatter;
41
42
    /** @var ConvertMoneyHelperInterface|null */
43
    private $moneyConverter;
44
45
    /**
46
     * Calculator constructor.
47
     *
48
     * @param TranslatorInterface $translator
49
     * @param UnitConverter       $unitConverter
50
     * @param FormatMoneyHelper   $formatMoneyHelper
51
     */
52 12
    public function __construct(
53
        TranslatorInterface $translator,
54
        UnitConverter $unitConverter,
55
        FormatMoneyHelper $formatMoneyHelper
56
    ) {
57 12
        $this->translator     = $translator;
58 12
        $this->moneyFormatter = $formatMoneyHelper;
59 12
        $this->setUnitConverter($unitConverter);
60 12
    }
61
62 12
    public function setMoneyConverter(ConvertMoneyHelper $convertMoneyHelper): self
63
    {
64 12
        $this->moneyConverter = $convertMoneyHelper;
65
66 12
        return $this;
67
    }
68
69 12
    public function setUnitConverter(UnitConverter $unitConverter): self
70
    {
71 12
        $this->unitConverter = $unitConverter;
72
73 12
        return $this;
74
    }
75
76 12
    private function getUnitConverter(): UnitConverter
77
    {
78 12
        if ($this->unitConverter === null) {
79
            throw new RuntimeException('Unit Converter class not set');
80
        }
81
82 12
        return $this->unitConverter;
83
    }
84
85
    /**
86
     * @param array<string, array<string, array<string, string|float>>> $mappingConfig
87
     *
88
     * @return $this
89
     */
90 12
    public function setMappingConfig(array $mappingConfig): self
91
    {
92 12
        $this->mappingConfig = $mappingConfig;
93
94 12
        return $this;
95
    }
96
97 12
    public function setUseShortUnitName(bool $useShortUnitName): self
98
    {
99 12
        $this->useShortUnitName = $useShortUnitName;
100
101 12
        return $this;
102
    }
103
104
    /**
105
     * @param ProductVariantInterface $productVariant
106
     * @param ChannelInterface        $channel
107
     * @param string                  $currencyCode current currency code
108
     *
109
     * @return string|null
110
     */
111 12
    public function calculate(
112
        ProductVariantInterface $productVariant,
113
        ChannelInterface $channel,
114
        string $currencyCode
115
    ): ?string {
116 12
        if (!$productVariant instanceof CoreModelProductVariantInterface) {
117
            return null;
118
        }
119
120 12
        $measurementMapping = $this->getMeasurementMapping($productVariant);
121
122 12
        foreach ($measurementMapping as $mappings) {
123
            $this
124 12
                ->getUnitConverter()
125 12
                ->from((string)$productVariant->getBasePriceUnit())
126 12
                ->convert((string)$productVariant->getBasePriceValue());
127
128 12
            foreach ($mappings as $mapping) {
129 12
                $symbol         = $mapping->getUnitClass()->getSymbol();
130 12
                $targetUnitSize = $this->getUnitConverter()->to((string)$symbol);
131
132 12
                if (!$this->isMappingUsable($mapping, $targetUnitSize)) {
133 4
                    continue;
134
                }
135
136 10
                $channelPricing = $productVariant->getChannelPricingForChannel($channel);
137 10
                if ($channelPricing === null) {
138
                    continue;
139
                }
140 10
                if ($channelPricing->getPrice() === null) {
141
                    continue;
142
                }
143
144 10
                $params = $this->getBasePriceFormatParams(
145 10
                    $channel,
146
                    $channelPricing,
147
                    $mapping,
148 10
                    (float)$targetUnitSize,
149
                    $currencyCode
150
                );
151
152 10
                return $this->translator->trans('ecocode_sylius_base_price_plugin.format', $params);
153
            }
154
        }
155
156 2
        return null;
157
    }
158
159
    /**
160
     * @param ProductVariantInterface $productVariant
161
     *
162
     * @return array<string, Mapping[]>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
163
     */
164 12
    public function getMeasurementMapping(ProductVariantInterface $productVariant): array
165
    {
166 12
        $basePriceUnitValue = $productVariant->getBasePriceUnit();
167 12
        if ($basePriceUnitValue === null || $productVariant->getBasePriceValue() === null) {
168
            return [];
169
        }
170
171 12
        $basePriceUnit = $this->getUnitConverter()->getRegistry()->loadUnit($basePriceUnitValue);
172
173 12
        if ($basePriceUnit === null) {
174
            return [];
175
        }
176
177 12
        $measurement = $basePriceUnit->getUnitOf();
178 12
        if ($measurement === null) {
179
            return [];
180
        }
181
182 12
        return $this->getMeasurementMappings($measurement);
183
    }
184
185 10
    private function getBasePriceFormatParams(
186
        ChannelInterface $channel,
187
        ChannelPricingInterface $channelPricing,
188
        Mapping $mapping,
189
        float $targetUnitSize,
190
        string $targetCurrency
191
    ): array {
192 10
        $symbol              = (string)$mapping->getUnitClass()->getSymbol();
193 10
        $format              = 'ecocode_sylius_base_price_plugin.%s.%s';
194 10
        $unitType            = sprintf($format, $this->useShortUnitName ? 'unit_short' : 'unit', $symbol);
195 10
        $unitTypeTranslation = $this->translator->trans($unitType);
196 10
        if ($unitTypeTranslation == $unitType) { // fallback if unit translation is missing
197 2
            $unitTypeTranslation = $this->useShortUnitName ? $symbol : $mapping->getUnitClass()->getName();
198
        }
199
200 10
        if ($this->moneyConverter === null) {
201
            throw new RuntimeException('Money converter not set');
202
        }
203
204 10
        $mod              = $mapping->getMod();
205 10
        $baseCurrency     = $channel->getBaseCurrency();
206 10
        $baseCurrencyCode = $baseCurrency !== null && $baseCurrency->getCode() !== null ? $baseCurrency->getCode() : $targetCurrency;
207 10
        $channelPrice     = (float)$channelPricing->getPrice();
208 10
        $price            = ($channelPrice / $targetUnitSize) * $mod;
209 10
        $convertedPrice   = $this->moneyConverter->convertAmount((int)$price, $baseCurrencyCode, $targetCurrency);
210 10
        $defaultLocale    = $channel->getDefaultLocale();
211 10
        $locale           = $defaultLocale !== null ? (string)$defaultLocale->getCode() : '';
212 10
        $formattedPrice   = $this->moneyFormatter->formatAmount((int)$convertedPrice, $targetCurrency, $locale);
213
214
        return [
215 10
            '%PRICE%' => $formattedPrice,
216 10
            '%VALUE%' => $mod > 1 ? $mod . ' ' : '',
217 10
            '%TYPE%'  => $unitTypeTranslation
218
        ];
219
    }
220
221
    /**
222
     * @param string|null $measurement
223
     *
224
     * @return array<string, Mapping[]>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
225
     */
226 12
    private function getMeasurementMappings(?string $measurement): array
227
    {
228 12
        $mappings = [];
229 12
        if ($measurement === null) {
230
            return $mappings;
231
        }
232
233 12
        foreach ($this->mappingConfig as $measurementsConst => $mappingsConfig) {
234 12
            $measurementConfig = (string)constant($measurementsConst);
235 12
            if ($measurement != $measurementConfig) {
236 5
                continue;
237
            }
238
239 12
            foreach ($mappingsConfig as $mappingData) {
240 12
                $mappings[$measurementConfig][] = new Mapping($mappingData);
241
            }
242
        }
243
244 12
        return $mappings;
245
    }
246
247
    /**
248
     * @param Mapping          $mapping
249
     * @param int|float|string $targetUnitValue
250
     *
251
     * @return bool
252
     */
253 12
    private function isMappingUsable(Mapping $mapping, $targetUnitValue): bool
254
    {
255 12
        if ($targetUnitValue <= 0) {
256
            return false;
257
        }
258
259 12
        if ($targetUnitValue > $mapping->getIfMoreThan()) {
260 10
            return true;
261
        }
262
263 4
        return false;
264
    }
265
}
266