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.

BulkEntityUpdater::isBinaryUuid()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1.008

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
ccs 4
cts 5
cp 0.8
crap 1.008
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Savers;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
9
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\BulkEntityUpdater\BulkEntityUpdateHelper;
10
use EdmondsCommerce\DoctrineStaticMeta\Schema\MysqliConnectionFactory;
11
use EdmondsCommerce\DoctrineStaticMeta\Schema\UuidFunctionPolyfill;
12
use mysqli;
13
use Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType;
14
use Ramsey\Uuid\UuidInterface;
15
use RuntimeException;
16
17
use function get_class;
18
19
class BulkEntityUpdater extends AbstractBulkProcess
20
{
21
    /**
22
     * @var BulkEntityUpdateHelper
23
     */
24
    private $extractor;
25
    /**
26
     * @var string
27
     */
28
    private $tableName;
29
    /**
30
     * @var string
31
     */
32
    private $entityFqn;
33
    /**
34
     * @var mysqli
35
     */
36
    private $mysqli;
37
    /**
38
     * This holds the bulk SQL query
39
     *
40
     * @var string
41
     */
42
    private $query;
43
44
    /**
45
     * @var float
46
     */
47
    private $requireAffectedRatio = 1.0;
48
49
    /**
50
     * @var int
51
     */
52
    private $totalAffectedRows = 0;
53
    /**
54
     * @var UuidFunctionPolyfill
55
     */
56
    private $uuidFunctionPolyfill;
57
58
    /**
59
     * Is the UUID binary
60
     *
61
     * @var bool
62
     */
63
    private $isBinaryUuid = true;
64
65 6
    public function __construct(
66
        EntityManagerInterface $entityManager,
67
        UuidFunctionPolyfill $uuidFunctionPolyfill,
68
        MysqliConnectionFactory $mysqliConnectionFactory
69
    ) {
70 6
        parent::__construct($entityManager);
71 6
        $this->uuidFunctionPolyfill = $uuidFunctionPolyfill;
72 6
        $this->mysqli               = $mysqliConnectionFactory->createFromEntityManager($entityManager);
73 6
    }
74
75
    /**
76
     * @param float $requireAffectedRatio
77
     *
78
     * @return BulkEntityUpdater
79
     */
80 2
    public function setRequireAffectedRatio(float $requireAffectedRatio): BulkEntityUpdater
81
    {
82 2
        $this->requireAffectedRatio = $requireAffectedRatio;
83
84 2
        return $this;
85
    }
86
87
    public function addEntityToSave(EntityInterface $entity)
88
    {
89
        if (false === $entity instanceof $this->entityFqn) {
90
            throw new RuntimeException('You can only bulk save a single entity type, currently saving ' .
91
                                        $this->entityFqn .
92
                                        ' but you are trying to save ' .
93
                                        get_class($entity));
94
        }
95
        parent::addEntityToSave($entity);
96
    }
97
98 4
    public function setExtractor(BulkEntityUpdateHelper $extractor): void
99
    {
100 4
        $this->extractor    = $extractor;
101 4
        $this->tableName    = $extractor->getTableName();
102 4
        $this->entityFqn    = $extractor->getEntityFqn();
103 4
        $this->isBinaryUuid = $this->isBinaryUuid();
104 4
        $this->runPolyfillIfRequired();
105 4
    }
106
107 4
    private function isBinaryUuid(): bool
108
    {
109 4
        $meta      = $this->entityManager->getClassMetadata($this->entityFqn);
110 4
        $idMapping = $meta->getFieldMapping($meta->getSingleIdentifierFieldName());
111
112 4
        return $idMapping['type'] === UuidBinaryOrderedTimeType::NAME;
113
    }
114
115 4
    private function runPolyfillIfRequired(): void
116
    {
117 4
        if (false === $this->isBinaryUuid) {
118
            return;
119
        }
120 4
        $this->uuidFunctionPolyfill->run();
121 4
    }
122
123 2
    public function startBulkProcess(): AbstractBulkProcess
124
    {
125 2
        if (!$this->extractor instanceof BulkEntityUpdateHelper) {
0 ignored issues
show
introduced by
$this->extractor is always a sub-type of EdmondsCommerce\Doctrine...\BulkEntityUpdateHelper.
Loading history...
126
            throw new RuntimeException(
127
                'You must call setExtractor with your extractor logic before starting the process. '
128
                . 'Note - a small anonymous class would be ideal'
129
            );
130
        }
131 2
        $this->resetQuery();
132
133 2
        return parent::startBulkProcess();
134
    }
135
136 3
    private function resetQuery(): void
137
    {
138 3
        $this->query = '';
139 3
    }
140
141
    /**
142
     * @return int
143
     */
144 1
    public function getTotalAffectedRows(): int
145
    {
146 1
        return $this->totalAffectedRows;
147
    }
148
149 4
    protected function doSave(): void
150
    {
151 4
        foreach ($this->entitiesToSave as $entity) {
152 4
            if (!$entity instanceof $this->entityFqn || !$entity instanceof EntityInterface) {
153
                throw new RuntimeException(
154
                    'You can only bulk save a single entity type, currently saving ' . $this->entityFqn .
155
                    ' but you are trying to save ' . get_class($entity)
156
                );
157
            }
158 4
            $this->appendToQuery(
159 4
                $this->convertExtractedToSqlRow(
160 4
                    $this->extractor->extract($entity)
161
                )
162
            );
163
        }
164 4
        $this->runQuery();
165 2
        $this->resetQuery();
166 2
    }
167
168 4
    private function appendToQuery(string $sql): void
169
    {
170 4
        $this->query .= "\n$sql";
171 4
    }
172
173
    /**
174
     * Take the extracted array and build an update query
175
     *
176
     * @param array $extracted
177
     *
178
     * @return string
179
     */
180 4
    private function convertExtractedToSqlRow(array $extracted): string
181
    {
182 4
        if ([] === $extracted) {
183
            throw new RuntimeException('Extracted array is empty in ' . __METHOD__);
184
        }
185 4
        $primaryKeyCol = null;
186 4
        $primaryKey    = null;
187 4
        $sql           = "update `{$this->tableName}` set ";
188 4
        $sqls          = [];
189 4
        foreach ($extracted as $key => $value) {
190 4
            if (null === $primaryKeyCol) {
191 4
                $primaryKeyCol = $key;
192 4
                $primaryKey    = $this->convertUuidToSqlString($value);
193 4
                continue;
194
            }
195 4
            $value  = $this->mysqli->escape_string((string)$value);
196 4
            $sqls[] = "`$key` = '$value'";
197
        }
198 4
        $sql .= implode(",\n", $sqls);
199 4
        $sql .= " where `$primaryKeyCol` = $primaryKey; ";
200
201 4
        return $sql;
202
    }
203
204 4
    private function convertUuidToSqlString(UuidInterface $uuid): string
205
    {
206 4
        $uuidString = (string)$uuid;
207 4
        if (false === $this->isBinaryUuid) {
208
            return "'$uuidString'";
209
        }
210
211 4
        return UuidFunctionPolyfill::UUID_TO_BIN . "('$uuidString', true)";
212
    }
213
214 4
    private function runQuery(): void
215
    {
216 4
        if ('' === $this->query) {
217
            return;
218
        }
219 4
        $this->query = "
220
           START TRANSACTION;
221
           SET FOREIGN_KEY_CHECKS = 0; 
222
           SET UNIQUE_CHECKS = 0;
223 4
           {$this->query}             
224
           SET FOREIGN_KEY_CHECKS = 1; 
225
           SET UNIQUE_CHECKS = 1;
226
           COMMIT;";
227 4
        $result      = $this->mysqli->multi_query($this->query);
228 4
        if (true !== $result) {
229
            throw new RuntimeException(
230
                'Multi Query returned false which means the first statement failed: ' .
231
                $this->mysqli->error
232
            );
233
        }
234 4
        $affectedRows = 0;
235 4
        $queryCount   = 0;
236
        do {
237 4
            $queryCount++;
238 4
            if (0 !== $this->mysqli->errno) {
239 1
                throw new RuntimeException(
240 1
                    'Query #' . $queryCount .
241 1
                    ' got MySQL Error #' . $this->mysqli->errno .
242 1
                    ': ' . $this->mysqli->error
243 1
                    . "\nQuery: " . $this->getQueryLine($queryCount) . "'\n"
244
                );
245
            }
246 4
            $affectedRows += max($this->mysqli->affected_rows, 0);
247 4
            if (false === $this->mysqli->more_results()) {
248 3
                break;
249
            }
250 4
            $this->mysqli->next_result();
251 4
        } while (true);
252 3
        if ($affectedRows < count($this->entitiesToSave) * $this->requireAffectedRatio) {
253 1
            throw new RuntimeException(
254 1
                'Affected rows count of ' . $affectedRows .
255 1
                ' does match the expected count of entitiesToSave ' . count($this->entitiesToSave)
256
            );
257
        }
258 2
        $this->totalAffectedRows += $affectedRows;
259 2
        $this->mysqli->commit();
260 2
    }
261
262 1
    private function getQueryLine(int $line): string
263
    {
264 1
        $lines = explode(';', $this->query);
265
266 1
        return $lines[$line + 1];
267
    }
268
}
269