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.
Passed
Pull Request — master (#154)
by joseph
25:46
created

BulkSimpleEntityCreator::runQuery()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 46
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 16.9107

Importance

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