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 (#190)
by joseph
21:51
created

BulkEntityUpdater::appendToQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
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
    private function runQuery(): void
209
    {
210
        if ('' === $this->query) {
211
            return;
212
        }
213
        $this->query = "
214
           START TRANSACTION;
215
           SET FOREIGN_KEY_CHECKS = 0; 
216
           SET UNIQUE_CHECKS = 0;
217
           {$this->query}             
218
           SET FOREIGN_KEY_CHECKS = 1; 
219
           SET UNIQUE_CHECKS = 1;
220
           COMMIT;";
221
        $result      = $this->mysqli->multi_query($this->query);
222
        if (true !== $result) {
223
            throw new \RuntimeException(
224
                'Multi Query returned false which means the first statement failed: ' .
225
                $this->mysqli->error
226
            );
227
        }
228
        $affectedRows = 0;
229
        $queryCount   = 0;
230
        do {
231
            $queryCount++;
232
            if (0 !== $this->mysqli->errno) {
233
                throw new \RuntimeException(
234
                    'Query #' . $queryCount .
235
                    ' got MySQL Error #' . $this->mysqli->errno .
236
                    ': ' . $this->mysqli->error
237
                    . "\nQuery: " . $this->getQueryLine($queryCount) . "'\n"
238
                );
239
            }
240
            $affectedRows += max($this->mysqli->affected_rows, 0);
241
            if (false === $this->mysqli->more_results()) {
242
                break;
243
            }
244
            $this->mysqli->next_result();
245
        } while (true);
246
        if ($affectedRows < count($this->entitiesToSave) * $this->requireAffectedRatio) {
247
            throw new \RuntimeException(
248
                'Affected rows count of ' . $affectedRows .
249
                ' does match the expected count of entitiesToSave ' . count($this->entitiesToSave)
250
            );
251
        }
252
        $this->totalAffectedRows += $affectedRows;
253
        $this->mysqli->commit();
254
    }
255
256
    private function getQueryLine(int $line): string
257
    {
258
        $lines = explode(';', $this->query);
259
260
        return $lines[$line + 1];
261
    }
262
}
263