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

BulkEntityUpdater::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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