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
Push — master ( fbc101...a22100 )
by joseph
32s queued 16s
created

pingAndReconnectOnFailure()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 6.4624

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 4
nop 0
dl 0
loc 11
ccs 3
cts 11
cp 0.2727
crap 6.4624
rs 10
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
     * @var MysqliConnectionFactory
84
     */
85
    private $mysqliConnectionFactory;
86
87 1
    public function __construct(
88
        EntityManagerInterface $entityManager,
89
        MysqliConnectionFactory $mysqliConnectionFactory,
90
        UuidFunctionPolyfill $uuidFunctionPolyfill,
91
        UuidFactory $uuidFactory
92
    ) {
93 1
        parent::__construct($entityManager);
94 1
        $this->entityManager           = $entityManager;
95 1
        $this->mysqliConnectionFactory = $mysqliConnectionFactory;
96 1
        $this->uuidFunctionPolyfill    = $uuidFunctionPolyfill;
97 1
        $this->uuidFactory             = $uuidFactory;
98 1
        $this->connect();
99 1
    }
100
101 1
    public function endBulkProcess(): void
102
    {
103 1
        parent::endBulkProcess();
104
        // Reset the insert mode to default to prevent state bleeding across batch runs
105 1
        $this->setInsertMode(self::INSERT_MODE_DEFAULT);
106 1
    }
107
108
    /**
109
     * @param string $insertMode
110
     *
111
     * @return BulkSimpleEntityCreator
112
     */
113 1
    public function setInsertMode(string $insertMode): BulkSimpleEntityCreator
114
    {
115 1
        if (false === \in_array($insertMode, self::INSERT_MODES, true)) {
116
            throw new \InvalidArgumentException('Invalid insert mode');
117
        }
118 1
        $this->insertMode = $insertMode;
119 1
        if ($this->insertMode === self::INSERT_MODE_IGNORE) {
120
            $this->requireAffectedRatio = 0;
121
        }
122
123 1
        return $this;
124
    }
125
126
    public function addEntityToSave(EntityInterface $entity): void
127
    {
128
        throw new \RuntimeException('You should not try to save Entities with this saver');
129
    }
130
131 1
    public function addEntitiesToSave(array $entities): void
132
    {
133 1
        foreach ($entities as $entityData) {
134 1
            if (\is_array($entityData)) {
135 1
                $this->addEntityCreationData($entityData);
136 1
                continue;
137
            }
138
            throw new \InvalidArgumentException('You should only pass in simple arrays of scalar entity data');
139
        }
140 1
    }
141
142 1
    public function addEntityCreationData(array $entityData): void
143
    {
144 1
        $this->entitiesToSave[] = $entityData;
145 1
        $this->bulkSaveIfChunkBigEnough();
146 1
    }
147
148 1
    public function setHelper(BulkSimpleEntityCreatorHelper $helper): void
149
    {
150 1
        $this->helper        = $helper;
151 1
        $this->tableName     = $helper->getTableName();
152 1
        $this->entityFqn     = $helper->getEntityFqn();
153 1
        $this->meta          = $this->entityManager->getClassMetadata($this->entityFqn);
154 1
        $this->primaryKeyCol = $this->meta->getSingleIdentifierFieldName();
155 1
        $this->isBinaryUuid  = $this->isBinaryUuid();
156 1
        $this->runPolyfillIfRequired();
157 1
    }
158
159 1
    private function isBinaryUuid(): bool
160
    {
161 1
        $idMapping = $this->meta->getFieldMapping($this->meta->getSingleIdentifierFieldName());
162
163 1
        return $idMapping['type'] === UuidBinaryOrderedTimeType::NAME;
164
    }
165
166 1
    private function runPolyfillIfRequired(): void
167
    {
168 1
        if (false === $this->isBinaryUuid) {
169
            return;
170
        }
171 1
        $this->uuidFunctionPolyfill->run();
172 1
    }
173
174
    /**
175
     * As these are not actually entities, lets empty them out before
176
     * parent::freeResources tries to detach from the entity manager
177
     */
178 1
    protected function freeResources()
179
    {
180 1
        $this->entitiesToSave = [];
181 1
        parent::freeResources();
182 1
    }
183
184 1
    protected function doSave(): void
185
    {
186 1
        foreach ($this->entitiesToSave as $entityData) {
187 1
            $this->appendToQuery($this->buildSql($entityData));
188
        }
189 1
        $this->runQuery();
190 1
        $this->reset();
191 1
    }
192
193 1
    private function appendToQuery(string $sql)
194
    {
195 1
        $this->query .= "\n$sql";
196 1
    }
197
198 1
    private function buildSql(array $entityData): string
199
    {
200 1
        $sql  = $this->insertMode . " into {$this->tableName} set ";
201
        $sqls = [
202 1
            $this->primaryKeyCol . ' = ' . $this->generateId(),
203
        ];
204 1
        foreach ($entityData as $key => $value) {
205 1
            if ($key === $this->primaryKeyCol) {
206
                throw new \InvalidArgumentException(
207
                    'You should not pass in IDs, they will be auto generated'
208
                );
209
            }
210 1
            if ($value instanceof UuidInterface) {
211
                $sqls[] = "`$key` = " . $this->getUuidSql($value);
212
                continue;
213
            }
214 1
            $value  = $this->mysqli->escape_string((string)$value);
215 1
            $sqls[] = "`$key` = '$value'";
216
        }
217 1
        $sql .= implode(', ', $sqls) . ';';
218
219 1
        return $sql;
220
    }
221
222 1
    private function generateId()
223
    {
224 1
        if ($this->isBinaryUuid) {
225 1
            return $this->getUuidSql($this->uuidFactory->getOrderedTimeUuid());
226
        }
227
228
        return $this->getUuidSql($this->uuidFactory->getUuid());
229
    }
230
231 1
    private function getUuidSql(UuidInterface $uuid)
232
    {
233 1
        if ($this->isBinaryUuid) {
234 1
            $uuidString = (string)$uuid;
235
236 1
            return "UUID_TO_BIN('$uuidString', true)";
237
        }
238
239
        throw new \RuntimeException('This is not currently suppported - should be easy enough though');
240
    }
241
242 1
    private function runQuery(): void
243
    {
244 1
        if ('' === $this->query) {
245
            return;
246
        }
247 1
        $this->pingAndReconnectOnFailure();
248 1
        $this->query = "
249
           START TRANSACTION;
250
           SET FOREIGN_KEY_CHECKS = 0; 
251 1
           {$this->query}             
252
           SET FOREIGN_KEY_CHECKS = 1; 
253
           COMMIT;";
254 1
        $result      = $this->mysqli->multi_query($this->query);
255 1
        if (true !== $result) {
256
            throw new \RuntimeException(
257
                'Multi Query returned false which means the first statement failed: ' .
258
                $this->mysqli->error
259
            );
260
        }
261 1
        $affectedRows = 0;
262 1
        $queryCount   = 0;
263
        do {
264 1
            $queryCount++;
265 1
            $errorNo = (int)$this->mysqli->errno;
266 1
            if (0 !== $errorNo) {
267
                $errorMessage = 'Query #' . $queryCount .
268
                                ' got MySQL Error #' . $errorNo .
269
                                ': ' . $this->mysqli->error
270
                                . "\nQuery: " . $this->getQueryLine($queryCount) . "'\n";
271
                throw new \RuntimeException($errorMessage);
272
            }
273 1
            $affectedRows += max($this->mysqli->affected_rows, 0);
274 1
            if (false === $this->mysqli->more_results()) {
275 1
                break;
276
            }
277 1
            $this->mysqli->next_result();
278 1
        } while (true);
279 1
        if ($affectedRows < count($this->entitiesToSave) * $this->requireAffectedRatio) {
280
            throw new \RuntimeException(
281
                'Affected rows count of ' . $affectedRows .
282
                ' does match the expected count of entitiesToSave ' . count($this->entitiesToSave)
283
            );
284
        }
285 1
        $this->totalAffectedRows += $affectedRows;
286 1
        $this->mysqli->commit();
287 1
    }
288
289 1
    private function pingAndReconnectOnFailure(): void
290
    {
291 1
        if (null === $this->mysqli) {
292
            $this->connect();
293
        }
294
        try {
295 1
            $this->mysqli->query('select 1');
296
        } catch (\Throwable $exception) {
297
            $this->mysqli->close();
298
            $this->mysqli = null;
299
            $this->connect();
300
        }
301 1
    }
302
303 1
    private function connect(): void
304
    {
305 1
        $this->mysqli = $this->mysqliConnectionFactory->createFromEntityManager($this->entityManager);
306 1
    }
307
308
    private function getQueryLine(int $line): string
309
    {
310
        $lines = explode(";\n", $this->query);
311
312
        return $lines[$line + 1];
313
    }
314
315 1
    private function reset(): void
316
    {
317 1
        $this->entitiesToSave = [];
318 1
        $this->query          = '';
319 1
    }
320
}
321