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 (#174)
by joseph
25:27
created

BulkSimpleEntityCreator::addEntityToSave()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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