NumberingGenerator::increaseCounterValue()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 18
ccs 0
cts 12
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 12
1
<?php
2
3
namespace LSB\NumberingBundle\Service;
4
5
use Doctrine\DBAL\LockMode;
6
use Doctrine\ORM\EntityManagerInterface;
7
use Doctrine\Persistence\ManagerRegistry;
8
use LSB\NumberingBundle\Entity\NumberingCounterData;
9
use LSB\NumberingBundle\Interfaces\NumberableInterface;
10
use LSB\NumberingBundle\Model\GeneratorOptions;
11
use LSB\NumberingBundle\Model\SimpleNumber;
12
use LSB\NumberingBundle\Model\TimeContext;
13
14
/**
15
 * Class NumberingGenerator
16
 * @package LSB\NumberingBundle\Service
17
 */
18
class NumberingGenerator
19
{
20
21
    /**
22
     * @var array
23
     */
24
    protected $configuration;
25
26
    /**
27
     * @var EntityManagerInterface
28
     */
29
    protected $em;
30
31
    /**
32
     * @var ManagerRegistry
33
     */
34
    protected $doctrine;
35
36
    /**
37
     * @var NumberingPatternTagVerifier
38
     */
39
    protected $tagVerifier;
40
41
    /**
42
     * @var NumberingPatternResolver
43
     */
44
    protected $patternResolver;
45
46
    /**
47
     * NumberingGenerator constructor.
48
     * @param ManagerRegistry $doctrine
49
     * @param NumberingPatternTagVerifier $tagVerifier
50
     * @param NumberingPatternResolver $patternResolver
51
     */
52
    public function __construct(ManagerRegistry $doctrine, NumberingPatternTagVerifier $tagVerifier, NumberingPatternResolver $patternResolver)
53
    {
54
        $this->doctrine = $doctrine;
55
        $this->em = $this->doctrine->getManager();
56
        $this->tagVerifier = $tagVerifier;
57
        $this->patternResolver = $patternResolver;
58
    }
59
60
    /**
61
     * @param array $config
62
     */
63
    public function setConfiguration(array $config): void
64
    {
65
        $this->configuration = $config;
66
    }
67
68
    /**
69
     * @return array|null
70
     */
71
    public function getConfiguration(): ?array
72
    {
73
        return $this->configuration;
74
    }
75
76
77
    /**
78
     * @param NumberableInterface $subject
79
     * @param GeneratorOptions $options
80
     * @return SimpleNumber
81
     * @throws \Doctrine\DBAL\ConnectionException
82
     */
83
    public function generateNumber(NumberableInterface $subject, GeneratorOptions $options): SimpleNumber
84
    {
85
        return $this->process($subject, $options);
86
    }
87
88
    /**
89
     * @param NumberableInterface $subject
90
     * @param GeneratorOptions $options
91
     * @return SimpleNumber
92
     * @throws \Doctrine\DBAL\ConnectionException
93
     * @throws \LSB\NumberingBundle\Exception\NumberingGeneratorException
94
     */
95
    protected function process(NumberableInterface $subject, GeneratorOptions $options): SimpleNumber
96
    {
97
        $increase = true;
98
        $subjectClassName = get_class($subject);
99
        $counterConfig = $this->getCounterConfig($options->getConfigName());
100
        $patternConfig = $this->getPatternForConfig($options->getConfigName());
101
102
        // verify pattern tags
103
        $this->tagVerifier->verify($patternConfig, $counterConfig);
104
105
        // get existing NumberingCounterData
106
        $counterData = $this->em->getRepository(NumberingCounterData::class)->getByConfigAndSubjectClass($counterConfig, $subjectClassName, $options->getContextObjectValue());
107
108
        // create new NumberingCounterData
109
        if (!$counterData instanceof NumberingCounterData) {
110
            $counterData = $this->createNewCounterData($subjectClassName, $counterConfig, $options->getContextObjectValue());
111
            $this->em->persist($counterData);
112
            $this->em->flush();
113
            $increase = false;
114
        }
115
116
        try {
117
            $this->em->getConnection()->setAutoCommit(false);
118
            $this->em->getConnection()->beginTransaction();
119
            $this->em->lock($counterData, LockMode::PESSIMISTIC_WRITE);
120
121
            // increase current number value
122
            if ($increase) {
123
                $this->increaseCounterValue($counterData, $options->getDate());
124
            }
125
126
            // resolve number from pattern
127
            $resolvedNumber = $this->patternResolver->resolve($patternConfig['pattern'], $counterData, $options->getDate());
128
129
            $this->em->flush();
130
            $this->em->getConnection()->commit();
131
            $this->em->getConnection()->setAutoCommit(true);
132
133
            $number = new SimpleNumber($resolvedNumber, $counterData->getCurrent());
134
135
        } catch (\Exception $e) {
136
            $this->resetEntityManager();
137
            throw $e;
138
        }
139
140
        return $number;
141
    }
142
143
    /**
144
     * @throws \Doctrine\DBAL\ConnectionException
145
     */
146
    protected function resetEntityManager(): void
147
    {
148
        $this->em->getConnection()->rollback();
149
        $this->em->getConnection()->setAutoCommit(true);
150
151
        if (!$this->em->isOpen()) {
152
            $this->em = $this->doctrine->resetManager();
153
        }
154
    }
155
156
    /**
157
     * @param NumberingCounterData $counterData
158
     * @param \DateTime|null $date
159
     * @throws \Exception
160
     */
161
    protected function increaseCounterValue(NumberingCounterData $counterData, \DateTime $date = null): void
162
    {
163
        $currentValue = $counterData->getCurrent() ?? $counterData->getStart();
164
        $newValue = $currentValue + $counterData->getStep();
165
166
        if (!empty($counterData->getTimeContext())) {
167
168
            $timeContextValue = TimeContext::getValueForTag($counterData->getTimeContext(), $date);
169
170
            if ($timeContextValue !== $counterData->getTimeContextValue()) {
171
                // set start value and new time context value
172
                $newValue = $counterData->getStart();
173
                $counterData->setTimeContextValue($timeContextValue);
174
            }
175
176
        }
177
178
        $counterData->setCurrent($newValue);
179
    }
180
181
    /**
182
     * @param string $subjectClassName
183
     * @param array $config
184
     * @param string|null $contextObjectValue
185
     * @return NumberingCounterData
186
     * @throws \Exception
187
     */
188
    protected function createNewCounterData(string $subjectClassName, array $config, string $contextObjectValue = null): NumberingCounterData
189
    {
190
        $numberingCounterData = (new NumberingCounterData)
191
            ->setSubjectFQCN($subjectClassName)
192
            ->setConfigName($config['name'])
193
            ->setStart($config['start'])
194
            ->setCurrent($config['start'])
195
            ->setStep($config['step']);
196
197
        if (isset($config['time_context']) && !empty($config['time_context'])) {
198
            $numberingCounterData
199
                ->setTimeContext($config['time_context'])
200
                ->setTimeContextValue(TimeContext::getValueForTag($config['time_context']));
201
        }
202
203
        if (isset($config['context_object_fqcn']) && !empty($config['context_object_fqcn'])) {
204
            $numberingCounterData
205
                ->setContextObjectFQCN($config['context_object_fqcn'])
206
                ->setContextObjectValue($contextObjectValue);
207
        }
208
209
        return $numberingCounterData;
210
    }
211
212
    /**
213
     * @param string $configName
214
     * @return array
215
     * @throws \Exception
216
     */
217
    public function getPatternForConfig(string $configName): array
218
    {
219
        $patterns = $this->getConfiguration()['patterns'];
220
221
        $config = $this->getCounterConfig($configName);
222
        $patternName = $config['patternName'];
223
224
        $filteredPattern = array_filter($patterns, function ($elem) use ($patternName) {
225
            return $elem['name'] === $patternName;
226
        });
227
228
        if (empty($filteredPattern)) {
229
            throw new \Exception('No pattern name found, check bundle configuration');
230
        }
231
232
        return reset($filteredPattern);
233
    }
234
235
    /**
236
     * @param string $configName
237
     * @return array
238
     * @throws \Exception
239
     */
240
    public function getCounterConfig(string $configName): array
241
    {
242
        $configs = $this->getConfiguration()['counter_configs'];
243
244
        $filteredConfig = array_filter($configs, function ($elem) use ($configName) {
245
            return $elem['name'] === $configName;
246
        });
247
248
        if (empty($filteredConfig)) {
249
            throw new \Exception('No counter config found, check bundle configuration');
250
        }
251
252
        if (count($filteredConfig) > 1) {
253
            throw new \Exception('More than one counter config found, check bundle configuration');
254
        }
255
256
        return reset($filteredConfig);
257
    }
258
}
259