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 (#154)
by joseph
30:28
created

BulkSimpleEntityCreator::runQuery()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 46
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 31
nc 7
nop 0
dl 0
loc 46
ccs 0
cts 30
cp 0
crap 56
rs 8.4906
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 Doctrine\ORM\Mapping\ClassMetadataInfo;
7
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Factories\UuidFactory;
8
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
9
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\BulkEntityUpdater\BulkSimpleEntityCreatorHelper;
10
use EdmondsCommerce\DoctrineStaticMeta\Schema\MysqliConnectionFactory;
11
use EdmondsCommerce\DoctrineStaticMeta\Schema\UuidFunctionPolyfill;
12
use Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType;
13
use Ramsey\Uuid\UuidInterface;
14
15
class BulkSimpleEntityCreator extends AbstractBulkProcess
16
{
17
    public const INSERT_MODE_INSERT  = 'INSERT ';
18
    public const INSERT_MODE_IGNORE  = 'INSERT IGNORE ';
19
    public const INSERT_MODE_DEFAULT = self::INSERT_MODE_INSERT;
20
    public const INSERT_MODES        = [
21
        self::INSERT_MODE_INSERT,
22
        self::INSERT_MODE_IGNORE,
23
    ];
24
25
    /**
26
     * @var BulkSimpleEntityCreatorHelper
27
     */
28
    private $helper;
29
    /**
30
     * @var string
31
     */
32
    private $tableName;
33
    /**
34
     * @var string
35
     */
36
    private $entityFqn;
37
    /**
38
     * Is the UUID binary
39
     *
40
     * @var bool
41
     */
42
    private $isBinaryUuid = true;
43
    /**
44
     * @var ClassMetadataInfo
45
     */
46
    private $meta;
47
    /**
48
     * @var string
49
     */
50
    private $primaryKeyCol;
51
    /**
52
     * @var \mysqli
53
     */
54
    private $mysqli;
55
    /**
56
     * @var UuidFunctionPolyfill
57
     */
58
    private $uuidFunctionPolyfill;
59
    /**
60
     * @var UuidFactory
61
     */
62
    private $uuidFactory;
63
    /**
64
     * @var string
65
     */
66
    private $query;
67
    /**
68
     * For creation this should always be 100%, so 1
69
     *
70
     * @var int
71
     */
72
    private $requireAffectedRatio = 1;
73
    /**
74
     * @var int
75
     */
76
    private $totalAffectedRows = 0;
77
78
    private $insertMode = self::INSERT_MODE_DEFAULT;
79
80
    public function __construct(
81
        EntityManagerInterface $entityManager,
82
        MysqliConnectionFactory $mysqliConnectionFactory,
83
        UuidFunctionPolyfill $uuidFunctionPolyfill,
84
        UuidFactory $uuidFactory
85
    ) {
86
        parent::__construct($entityManager);
87
        $this->mysqli               = $mysqliConnectionFactory->createFromEntityManager($entityManager);
88
        $this->uuidFunctionPolyfill = $uuidFunctionPolyfill;
89
        $this->uuidFactory          = $uuidFactory;
90
    }
91
92
    public function endBulkProcess(): void
93
    {
94
        // Reset the insert mode to default to prevent state bleeding across batch runs
95
        $this->setInsertMode(self::INSERT_MODE_DEFAULT);
96
97
        parent::endBulkProcess();
98
    }
99
100
101
    public function addEntityToSave(EntityInterface $entity): void
102
    {
103
        throw new \RuntimeException('You should not try to save Entities with this saver');
104
    }
105
106
    public function addEntitiesToSave(array $entities): void
107
    {
108
        foreach ($entities as $entityData) {
109
            if (\is_array($entityData)) {
110
                $this->addEntityCreationData($entityData);
111
                continue;
112
            }
113
            throw new \InvalidArgumentException('You should only pass in simple arrays of scalar entity data');
114
        }
115
    }
116
117
    public function addEntityCreationData(array $entityData): void
118
    {
119
        $this->entitiesToSave[] = $entityData;
120
        $this->bulkSaveIfChunkBigEnough();
121
    }
122
123
    public function setHelper(BulkSimpleEntityCreatorHelper $helper): void
124
    {
125
        $this->helper        = $helper;
126
        $this->tableName     = $helper->getTableName();
127
        $this->entityFqn     = $helper->getEntityFqn();
128
        $this->meta          = $this->entityManager->getClassMetadata($this->entityFqn);
129
        $this->primaryKeyCol = $this->meta->getSingleIdentifierFieldName();
130
        $this->isBinaryUuid  = $this->isBinaryUuid();
131
        $this->runPolyfillIfRequired();
132
    }
133
134
    private function isBinaryUuid(): bool
135
    {
136
        $idMapping = $this->meta->getFieldMapping($this->meta->getSingleIdentifierFieldName());
137
138
        return $idMapping['type'] === UuidBinaryOrderedTimeType::NAME;
139
    }
140
141
    private function runPolyfillIfRequired(): void
142
    {
143
        if (false === $this->isBinaryUuid) {
144
            return;
145
        }
146
        $this->uuidFunctionPolyfill->run();
147
    }
148
149
    /**
150
     * @param string $insertMode
151
     *
152
     * @return BulkSimpleEntityCreator
153
     */
154
    public function setInsertMode(string $insertMode): BulkSimpleEntityCreator
155
    {
156
        if (false === \in_array($insertMode, self::INSERT_MODES, true)) {
157
            throw new \InvalidArgumentException('Invalid insert mode');
158
        }
159
        $this->insertMode = $insertMode;
160
        if ($this->insertMode === self::INSERT_MODE_IGNORE) {
161
            $this->requireAffectedRatio = 0;
162
        }
163
164
        return $this;
165
    }
166
167
    protected function freeResources()
168
    {
169
        /**
170
         * AS these are not actually entities, lets empty them out before free resources tries to detach from the entity manager
171
         */
172
        $this->entitiesToSave = [];
173
        parent::freeResources();
174
    }
175
176
    protected function doSave(): void
177
    {
178
        foreach ($this->entitiesToSave as $entityData) {
179
            $this->appendToQuery($this->buildSql($entityData));
180
        }
181
        $this->runQuery();
182
        $this->reset();
183
    }
184
185
    private function appendToQuery(string $sql)
186
    {
187
        $this->query .= "\n$sql";
188
    }
189
190
    private function buildSql(array $entityData): string
191
    {
192
        $sql  = $this->insertMode . " into {$this->tableName} set ";
193
        $sqls = [
194
            $this->primaryKeyCol . ' = ' . $this->generateId(),
195
        ];
196
        foreach ($entityData as $key => $value) {
197
            if ($key === $this->primaryKeyCol) {
198
                throw new \InvalidArgumentException(
199
                    'You should not pass in IDs, they will be auto generated'
200
                );
201
            }
202
            if ($value instanceof UuidInterface) {
203
                $sqls[] = "`$key` = " . $this->getUuidSql($value);
204
                continue;
205
            }
206
            $value  = $this->mysqli->escape_string((string)$value);
207
            $sqls[] = "`$key` = '$value'";
208
        }
209
        $sql .= implode(', ', $sqls) . ';';
210
211
        return $sql;
212
    }
213
214
    private function generateId()
215
    {
216
        if ($this->isBinaryUuid) {
217
            return $this->getUuidSql($this->uuidFactory->getOrderedTimeUuid());
218
        }
219
220
        return $this->getUuidSql($this->uuidFactory->getUuid());
221
    }
222
223
    private function getUuidSql(UuidInterface $uuid)
224
    {
225
        if ($this->isBinaryUuid) {
226
            $uuidString = (string)$uuid;
227
228
            return "UUID_TO_BIN('$uuidString')";
229
        }
230
231
        throw new \RuntimeException('This is not currently suppported - should be easy enough though');
232
    }
233
234
    private function runQuery(): void
235
    {
236
        if ('' === $this->query) {
237
            return;
238
        }
239
        $this->query = "
240
           START TRANSACTION;
241
           SET FOREIGN_KEY_CHECKS = 0; 
242
           SET UNIQUE_CHECKS = 0;
243
           {$this->query}             
244
           SET FOREIGN_KEY_CHECKS = 1; 
245
           SET UNIQUE_CHECKS = 1;
246
           COMMIT;";
247
        $result      = $this->mysqli->multi_query($this->query);
248
        if (true !== $result) {
249
            throw new \RuntimeException(
250
                'Multi Query returned false which means the first statement failed: ' .
251
                $this->mysqli->error
252
            );
253
        }
254
        $affectedRows = 0;
255
        $queryCount   = 0;
256
        do {
257
            $queryCount++;
258
            $errorNo = (int)$this->mysqli->errno;
259
            if (0 !== $errorNo) {
260
                $errorMessage = 'Query #' . $queryCount .
261
                                ' got MySQL Error #' . $errorNo .
262
                                ': ' . $this->mysqli->error
263
                                . "\nQuery: " . $this->getQueryLine($queryCount) . "'\n";
264
                throw new \RuntimeException($errorMessage);
265
            }
266
            $affectedRows += max($this->mysqli->affected_rows, 0);
267
            if (false === $this->mysqli->more_results()) {
268
                break;
269
            }
270
            $this->mysqli->next_result();
271
        } while (true);
272
        if ($affectedRows < count($this->entitiesToSave) * $this->requireAffectedRatio) {
273
            throw new \RuntimeException(
274
                'Affected rows count of ' . $affectedRows .
275
                ' does match the expected count of entitiesToSave ' . count($this->entitiesToSave)
276
            );
277
        }
278
        $this->totalAffectedRows += $affectedRows;
279
        $this->mysqli->commit();
280
    }
281
282
    private function getQueryLine(int $line): string
283
    {
284
        $lines = explode(";\n", $this->query);
285
286
        return $lines[$line + 1];
287
    }
288
289
    private function reset(): void
290
    {
291
        $this->entitiesToSave = [];
292
        $this->query          = '';
293
    }
294
295
}