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 Ross
22:04
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 9.4161

Importance

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