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

BulkSimpleEntityCreator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 10
ccs 0
cts 10
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 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
/**
16
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
17
 */
18
class BulkSimpleEntityCreator extends AbstractBulkProcess
19
{
20
    public const INSERT_MODE_INSERT  = 'INSERT ';
21
    public const INSERT_MODE_IGNORE  = 'INSERT IGNORE ';
22
    public const INSERT_MODE_DEFAULT = self::INSERT_MODE_INSERT;
23
    public const INSERT_MODES        = [
24
        self::INSERT_MODE_INSERT,
25
        self::INSERT_MODE_IGNORE,
26
    ];
27
28
    /**
29
     * @var BulkSimpleEntityCreatorHelper
30
     */
31
    private $helper;
32
    /**
33
     * @var string
34
     */
35
    private $tableName;
36
    /**
37
     * @var string
38
     */
39
    private $entityFqn;
40
    /**
41
     * Is the UUID binary
42
     *
43
     * @var bool
44
     */
45
    private $isBinaryUuid = true;
46
    /**
47
     * @var ClassMetadataInfo
48
     */
49
    private $meta;
50
    /**
51
     * @var string
52
     */
53
    private $primaryKeyCol;
54
    /**
55
     * @var \mysqli
56
     */
57
    private $mysqli;
58
    /**
59
     * @var UuidFunctionPolyfill
60
     */
61
    private $uuidFunctionPolyfill;
62
    /**
63
     * @var UuidFactory
64
     */
65
    private $uuidFactory;
66
    /**
67
     * @var string
68
     */
69
    private $query;
70
    /**
71
     * For creation this should always be 100%, so 1
72
     *
73
     * @var int
74
     */
75
    private $requireAffectedRatio = 1;
76
    /**
77
     * @var int
78
     */
79
    private $totalAffectedRows = 0;
80
81
    private $insertMode = self::INSERT_MODE_DEFAULT;
82
83
    public function __construct(
84
        EntityManagerInterface $entityManager,
85
        MysqliConnectionFactory $mysqliConnectionFactory,
86
        UuidFunctionPolyfill $uuidFunctionPolyfill,
87
        UuidFactory $uuidFactory
88
    ) {
89
        parent::__construct($entityManager);
90
        $this->mysqli               = $mysqliConnectionFactory->createFromEntityManager($entityManager);
91
        $this->uuidFunctionPolyfill = $uuidFunctionPolyfill;
92
        $this->uuidFactory          = $uuidFactory;
93
    }
94
95
    public function endBulkProcess(): void
96
    {
97
        parent::endBulkProcess();
98
        // Reset the insert mode to default to prevent state bleeding across batch runs
99
        $this->setInsertMode(self::INSERT_MODE_DEFAULT);
100
101
    }
102
103
    /**
104
     * @param string $insertMode
105
     *
106
     * @return BulkSimpleEntityCreator
107
     */
108
    public function setInsertMode(string $insertMode): BulkSimpleEntityCreator
109
    {
110
        if (false === \in_array($insertMode, self::INSERT_MODES, true)) {
111
            throw new \InvalidArgumentException('Invalid insert mode');
112
        }
113
        $this->insertMode = $insertMode;
114
        if ($this->insertMode === self::INSERT_MODE_IGNORE) {
115
            $this->requireAffectedRatio = 0;
116
        }
117
118
        return $this;
119
    }
120
121
    public function addEntityToSave(EntityInterface $entity): void
122
    {
123
        throw new \RuntimeException('You should not try to save Entities with this saver');
124
    }
125
126
    public function addEntitiesToSave(array $entities): void
127
    {
128
        foreach ($entities as $entityData) {
129
            if (\is_array($entityData)) {
130
                $this->addEntityCreationData($entityData);
131
                continue;
132
            }
133
            throw new \InvalidArgumentException('You should only pass in simple arrays of scalar entity data');
134
        }
135
    }
136
137
    public function addEntityCreationData(array $entityData): void
138
    {
139
        $this->entitiesToSave[] = $entityData;
140
        $this->bulkSaveIfChunkBigEnough();
141
    }
142
143
    public function setHelper(BulkSimpleEntityCreatorHelper $helper): void
144
    {
145
        $this->helper        = $helper;
146
        $this->tableName     = $helper->getTableName();
147
        $this->entityFqn     = $helper->getEntityFqn();
148
        $this->meta          = $this->entityManager->getClassMetadata($this->entityFqn);
149
        $this->primaryKeyCol = $this->meta->getSingleIdentifierFieldName();
150
        $this->isBinaryUuid  = $this->isBinaryUuid();
151
        $this->runPolyfillIfRequired();
152
    }
153
154
    private function isBinaryUuid(): bool
155
    {
156
        $idMapping = $this->meta->getFieldMapping($this->meta->getSingleIdentifierFieldName());
157
158
        return $idMapping['type'] === UuidBinaryOrderedTimeType::NAME;
159
    }
160
161
    private function runPolyfillIfRequired(): void
162
    {
163
        if (false === $this->isBinaryUuid) {
164
            return;
165
        }
166
        $this->uuidFunctionPolyfill->run();
167
    }
168
169
    /**
170
     * As these are not actually entities, lets empty them out before
171
     * parent::freeResources tries to detach from the entity manager
172
     */
173
    protected function freeResources()
174
    {
175
        $this->entitiesToSave = [];
176
        parent::freeResources();
177
    }
178
179
    protected function doSave(): void
180
    {
181
        foreach ($this->entitiesToSave as $entityData) {
182
            $this->appendToQuery($this->buildSql($entityData));
183
        }
184
        $this->runQuery();
185
        $this->reset();
186
    }
187
188
    private function appendToQuery(string $sql)
189
    {
190
        $this->query .= "\n$sql";
191
    }
192
193
    private function buildSql(array $entityData): string
194
    {
195
        $sql  = $this->insertMode . " into {$this->tableName} set ";
196
        $sqls = [
197
            $this->primaryKeyCol . ' = ' . $this->generateId(),
198
        ];
199
        foreach ($entityData as $key => $value) {
200
            if ($key === $this->primaryKeyCol) {
201
                throw new \InvalidArgumentException(
202
                    'You should not pass in IDs, they will be auto generated'
203
                );
204
            }
205
            if ($value instanceof UuidInterface) {
206
                $sqls[] = "`$key` = " . $this->getUuidSql($value);
207
                continue;
208
            }
209
            $value  = $this->mysqli->escape_string((string)$value);
210
            $sqls[] = "`$key` = '$value'";
211
        }
212
        $sql .= implode(', ', $sqls) . ';';
213
214
        return $sql;
215
    }
216
217
    private function generateId()
218
    {
219
        if ($this->isBinaryUuid) {
220
            return $this->getUuidSql($this->uuidFactory->getOrderedTimeUuid());
221
        }
222
223
        return $this->getUuidSql($this->uuidFactory->getUuid());
224
    }
225
226
    private function getUuidSql(UuidInterface $uuid)
227
    {
228
        if ($this->isBinaryUuid) {
229
            $uuidString = (string)$uuid;
230
            return "UUID_TO_BIN('$uuidString', true)";
231
        }
232
233
        throw new \RuntimeException('This is not currently suppported - should be easy enough though');
234
    }
235
236
    private function runQuery(): void
237
    {
238
        if ('' === $this->query) {
239
            return;
240
        }
241
        $this->query = "
242
           START TRANSACTION;
243
           SET FOREIGN_KEY_CHECKS = 0; 
244
           {$this->query}             
245
           SET FOREIGN_KEY_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