GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#152)
by joseph
17:58
created

BulkEntityUpdater   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 103
dl 0
loc 249
ccs 0
cts 167
cp 0
rs 9.92
c 0
b 0
f 0
wmc 31

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getQueryLine() 0 5 1
A startBulkProcess() 0 11 2
A runPolyfillIfRequired() 0 6 2
A isBinaryUuid() 0 6 1
A __construct() 0 8 1
A setRequireAffectedRatio() 0 5 1
A appendToQuery() 0 3 1
B runQuery() 0 46 7
A convertUuidToSqlString() 0 8 2
A getTotalAffectedRows() 0 3 1
A setExtractor() 0 7 1
A resetQuery() 0 3 1
A addEntityToSave() 0 9 2
A doSave() 0 17 4
A convertExtractedToSqlRow() 0 22 4
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Savers;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
7
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\BulkEntityUpdater\BulkEntityUpdateHelper;
8
use EdmondsCommerce\DoctrineStaticMeta\Schema\MysqliConnectionFactory;
9
use EdmondsCommerce\DoctrineStaticMeta\Schema\UuidFunctionPolyfill;
10
use Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType;
11
use Ramsey\Uuid\UuidInterface;
12
13
class BulkEntityUpdater extends AbstractBulkProcess
14
{
15
    /**
16
     * @var BulkEntityUpdateHelper
17
     */
18
    private $extractor;
19
    /**
20
     * @var string
21
     */
22
    private $tableName;
23
    /**
24
     * @var string
25
     */
26
    private $entityFqn;
27
    /**
28
     * @var \mysqli
29
     */
30
    private $mysqli;
31
    /**
32
     * This holds the bulk SQL query
33
     *
34
     * @var string
35
     */
36
    private $query;
37
38
    /**
39
     * @var float
40
     */
41
    private $requireAffectedRatio = 1.0;
42
43
    /**
44
     * @var int
45
     */
46
    private $totalAffectedRows = 0;
47
    /**
48
     * @var UuidFunctionPolyfill
49
     */
50
    private $uuidFunctionPolyfill;
51
52
    /**
53
     * Is the UUID binary
54
     *
55
     * @var bool
56
     */
57
    private $isBinaryUuid = true;
58
59
    public function __construct(
60
        EntityManagerInterface $entityManager,
61
        UuidFunctionPolyfill $uuidFunctionPolyfill,
62
        MysqliConnectionFactory $mysqliConnectionFactory
63
    ) {
64
        parent::__construct($entityManager);
65
        $this->uuidFunctionPolyfill = $uuidFunctionPolyfill;
66
        $this->mysqli               = $mysqliConnectionFactory->createFromEntityManager($entityManager);
67
    }
68
69
    /**
70
     * @param float $requireAffectedRatio
71
     *
72
     * @return BulkEntityUpdater
73
     */
74
    public function setRequireAffectedRatio(float $requireAffectedRatio): BulkEntityUpdater
75
    {
76
        $this->requireAffectedRatio = $requireAffectedRatio;
77
78
        return $this;
79
    }
80
81
    public function addEntityToSave(EntityInterface $entity)
82
    {
83
        if (false === $entity instanceof $this->entityFqn) {
84
            throw new \RuntimeException('You can only bulk save a single entity type, currently saving ' .
85
                                        $this->entityFqn .
86
                                        ' but you are trying to save ' .
87
                                        \get_class($entity));
88
        }
89
        parent::addEntityToSave($entity);
90
    }
91
92
    public function setExtractor(BulkEntityUpdateHelper $extractor): void
93
    {
94
        $this->extractor    = $extractor;
95
        $this->tableName    = $extractor->getTableName();
96
        $this->entityFqn    = $extractor->getEntityFqn();
97
        $this->isBinaryUuid = $this->isBinaryUuid();
98
        $this->runPolyfillIfRequired();
99
    }
100
101
    private function isBinaryUuid(): bool
102
    {
103
        $meta      = $this->entityManager->getClassMetadata($this->entityFqn);
104
        $idMapping = $meta->getFieldMapping($meta->getSingleIdentifierFieldName());
105
106
        return $idMapping['type'] === UuidBinaryOrderedTimeType::NAME;
107
    }
108
109
    private function runPolyfillIfRequired(): void
110
    {
111
        if (false === $this->isBinaryUuid) {
112
            return;
113
        }
114
        $this->uuidFunctionPolyfill->run();
115
    }
116
117
    public function startBulkProcess(): AbstractBulkProcess
118
    {
119
        if (!$this->extractor instanceof BulkEntityUpdateHelper) {
0 ignored issues
show
introduced by
$this->extractor is always a sub-type of EdmondsCommerce\Doctrine...\BulkEntityUpdateHelper.
Loading history...
120
            throw new \RuntimeException(
121
                'You must call setExtractor with your extractor logic before starting the process. '
122
                . 'Note - a small anonymous class would be ideal'
123
            );
124
        }
125
        $this->resetQuery();
126
127
        return parent::startBulkProcess();
128
    }
129
130
    private function resetQuery()
131
    {
132
        $this->query = '';
133
    }
134
135
    /**
136
     * @return int
137
     */
138
    public function getTotalAffectedRows(): int
139
    {
140
        return $this->totalAffectedRows;
141
    }
142
143
    protected function doSave(): void
144
    {
145
        foreach ($this->entitiesToSave as $entity) {
146
            if (!$entity instanceof $this->entityFqn || !$entity instanceof EntityInterface) {
147
                throw new \RuntimeException(
148
                    'You can only bulk save a single entity type, currently saving ' . $this->entityFqn .
149
                    ' but you are trying to save ' . \get_class($entity)
150
                );
151
            }
152
            $this->appendToQuery(
153
                $this->convertExtractedToSqlRow(
154
                    $this->extractor->extract($entity)
155
                )
156
            );
157
        }
158
        $this->runQuery();
159
        $this->resetQuery();
160
    }
161
162
    private function appendToQuery(string $sql)
163
    {
164
        $this->query .= "\n$sql";
165
    }
166
167
    /**
168
     * Take the extracted array and build an update query
169
     *
170
     * @param array $extracted
171
     *
172
     * @return string
173
     */
174
    private function convertExtractedToSqlRow(array $extracted): string
175
    {
176
        if ([] === $extracted) {
177
            throw new \RuntimeException('Extracted array is empty in ' . __METHOD__);
178
        }
179
        $primaryKeyCol = null;
180
        $primaryKey    = null;
181
        $sql           = "update `{$this->tableName}` set ";
182
        $sqls          = [];
183
        foreach ($extracted as $key => $value) {
184
            if (null === $primaryKeyCol) {
185
                $primaryKeyCol = $key;
186
                $primaryKey    = $this->convertUuidToSqlString($value);
187
                continue;
188
            }
189
            $value  = $this->mysqli->escape_string((string)$value);
190
            $sqls[] = "`$key` = '$value'";
191
        }
192
        $sql .= implode(",\n", $sqls);
193
        $sql .= " where `$primaryKeyCol` = $primaryKey; ";
194
195
        return $sql;
196
    }
197
198
    private function convertUuidToSqlString(UuidInterface $uuid): string
199
    {
200
        $uuidString = (string)$uuid;
201
        if (false === $this->isBinaryUuid) {
202
            return "'$uuidString'";
203
        }
204
205
        return UuidFunctionPolyfill::UUID_TO_BIN . "('$uuidString')";
206
207
    }
208
209
    private function runQuery(): void
210
    {
211
        if ('' === $this->query) {
212
            return;
213
        }
214
        $this->query = "
215
           START TRANSACTION;
216
           SET FOREIGN_KEY_CHECKS = 0; 
217
           SET UNIQUE_CHECKS = 0;
218
           {$this->query}             
219
           SET FOREIGN_KEY_CHECKS = 1; 
220
           SET UNIQUE_CHECKS = 1;
221
           COMMIT;";
222
        $result      = $this->mysqli->multi_query($this->query);
223
        if (true !== $result) {
224
            throw new \RuntimeException(
225
                'Multi Query returned false which means the first statement failed: ' .
226
                $this->mysqli->error
227
            );
228
        }
229
        $affectedRows = 0;
230
        $queryCount   = 0;
231
        do {
232
            $queryCount++;
233
            if (0 !== $this->mysqli->errno) {
234
                throw new \RuntimeException(
235
                    'Query #' . $queryCount .
236
                    ' got MySQL Error #' . $this->mysqli->errno .
237
                    ': ' . $this->mysqli->error
238
                    . "\nQuery: " . $this->getQueryLine($queryCount) . "'\n"
239
                );
240
            }
241
            $affectedRows += max($this->mysqli->affected_rows, 0);
242
            if (false === $this->mysqli->more_results()) {
243
                break;
244
            }
245
            $this->mysqli->next_result();
246
        } while (true);
247
        if ($affectedRows < count($this->entitiesToSave) * $this->requireAffectedRatio) {
248
            throw new \RuntimeException(
249
                'Affected rows count of ' . $affectedRows .
250
                ' does match the expected count of entitiesToSave ' . count($this->entitiesToSave)
251
            );
252
        }
253
        $this->totalAffectedRows += $affectedRows;
254
        $this->mysqli->commit();
255
    }
256
257
    private function getQueryLine(int $line): string
258
    {
259
        $lines = explode(';', $this->query);
260
261
        return $lines[$line + 1];
262
    }
263
}
264