Completed
Push — master ( 70b0e0...944a1d )
by Oleg
01:42
created

Write::getAutoIncrementColumn()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 2
nop 0
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 4
rs 9.2
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace SlayerBirden\DataFlow\Writer\Dbal;
5
6
use Doctrine\DBAL\Connection;
7
use Doctrine\DBAL\DBALException;
8
use SlayerBirden\DataFlow\DataBagInterface;
9
use SlayerBirden\DataFlow\EmitterInterface;
10
use SlayerBirden\DataFlow\PipeInterface;
11
use SlayerBirden\DataFlow\IdentificationTrait;
12
13
class Write implements PipeInterface
14
{
15
    use IdentificationTrait;
16
    /**
17
     * @var string
18
     */
19
    private $identifier;
20
    /**
21
     * @var Connection
22
     */
23
    private $connection;
24
    /**
25
     * @var string
26
     */
27
    private $table;
28
    /**
29
     * @var AutoIncrementCallbackInterface|null
30
     */
31
    private $callback;
32
    /**
33
     * @var WriterUtilityInterface
34
     */
35
    private $utility;
36
    /**
37
     * @var UpdateStrategyInterface
38
     */
39
    private $updateStrategy;
40
    /**
41
     * @var EmitterInterface
42
     */
43
    private $emitter;
44
    /**
45
     * @var string|null
46
     */
47
    private $autoIncrementColumn;
48
49 6
    public function __construct(
50
        string $identifier,
51
        Connection $connection,
52
        string $table,
53
        WriterUtilityInterface $utility,
54
        UpdateStrategyInterface $updateStrategy,
55
        EmitterInterface $emitter,
56
        ?AutoIncrementCallbackInterface $callback = null
57
    ) {
58 6
        $this->identifier = $identifier;
59 6
        $this->connection = $connection;
60 6
        $this->table = $table;
61 6
        $this->callback = $callback;
62 6
        $this->utility = $utility;
63 6
        $this->updateStrategy = $updateStrategy;
64 6
        $this->emitter = $emitter;
65 6
    }
66
67
    /**
68
     * DBAL Insert statement.
69
     * Inserts data into a table using DBAL.
70
     *
71
     * {@inheritdoc}
72
     */
73 5
    public function pass(DataBagInterface $dataBag): DataBagInterface
74
    {
75 5
        $dataToInsert = $this->getDataToInsert($dataBag);
76 5
        if ($this->recordExists($dataBag)) {
77 2
            $this->updateRecord($dataToInsert, $dataBag);
78
        } else {
79 4
            $this->insertRecord($dataToInsert, $dataBag);
80
        }
81
82 4
        return $dataBag;
83
    }
84
85 4
    private function insertRecord(array $dataToInsert, DataBagInterface $dataBag)
86
    {
87 4
        $autoIncrementColumn = $this->getAutoIncrementColumn();
88 4
        $this->connection->insert($this->table, $dataToInsert);
89 4
        $this->emitter->emit('record_insert', $this->table, $dataBag);
90 4
        if ($autoIncrementColumn && $this->callback) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $autoIncrementColumn of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
91 1
            $id = (int)$this->connection->lastInsertId();
92 1
            ($this->callback)($id, $dataBag);
93
        }
94 4
    }
95
96
    /**
97
     * @param array $dataToInsert
98
     * @param DataBagInterface $dataBag
99
     * @throws DBALException
100
     */
101 2
    private function updateRecord(array $dataToInsert, DataBagInterface $dataBag)
102
    {
103 2
        $identifier = $this->updateStrategy->getRecordIdentifier($dataBag);
104 2
        $autoIncrementColumn = $this->getAutoIncrementColumn();
105 2
        $this->connection->update(
106 2
            $this->table,
107 2
            $dataToInsert,
108 2
            $identifier
109
        );
110 2
        $this->emitter->emit('record_update', $this->table, $dataBag);
111 2
        if ($autoIncrementColumn && $this->callback) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $autoIncrementColumn of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
112 1
            $id = $this->getRecordId($identifier, $autoIncrementColumn);
113
114 1
            ($this->callback)($id, $dataBag);
115
        }
116 2
    }
117
118 5
    private function getDataToInsert(DataBagInterface $dataBag): array
119
    {
120 5
        $columns = $this->utility->getColumns($this->table);
121 5
        $dataToInsert = [];
122 5
        foreach ($columns as $column) {
123 3
            if (isset($dataBag[$column->getName()])) {
124 3
                $dataToInsert[$column->getName()] = $column->getType()->convertToDatabaseValue(
125 3
                    $dataBag[$column->getName()],
126 3
                    $this->connection->getDatabasePlatform()
127
                );
128
            }
129
        }
130
131 5
        return $dataToInsert;
132
    }
133
134
    /**
135
     * @return null|string
136
     */
137 4
    private function getAutoIncrementColumn(): ?string
138
    {
139 4
        if ($this->autoIncrementColumn === null) {
140 4
            $columns = $this->utility->getColumns($this->table);
141 4
            foreach ($columns as $column) {
142 3
                if ($column->getAutoincrement()) {
143 3
                    $this->autoIncrementColumn = $column->getName();
144
                }
145
            }
146
        }
147
148 4
        return $this->autoIncrementColumn;
149
    }
150
151
    /**
152
     * Get auto-increment field value of the record using given id.
153
     *
154
     * @param array $identifier
155
     * @param string $autoIncrementColumn
156
     * @return int
157
     * @throws DBALException
158
     */
159 1
    public function getRecordId(array $identifier, string $autoIncrementColumn): int
160
    {
161 1
        if (isset($identifier[$autoIncrementColumn])) {
162
            return (int)$identifier[$autoIncrementColumn];
163
        }
164 1
        $queryBuilder = $this->connection->createQueryBuilder();
165 1
        $queryBuilder->select($autoIncrementColumn)
166 1
            ->from($this->table)
167 1
            ->setParameters($identifier);
168 1
        foreach (array_keys($identifier) as $key) {
169 1
            $queryBuilder->andWhere("$key = :$key");
170
        }
171 1
        $stmt = $this->connection->prepare($queryBuilder->getSQL());
172 1
        $stmt->execute($identifier);
173 1
        return (int)$stmt->fetchColumn();
174
    }
175
176
    /**
177
     * Checks if record already exists in the DB.
178
     *
179
     * @param DataBagInterface $dataBag
180
     * @return bool
181
     * @throws DBALException
182
     */
183 5
    private function recordExists(DataBagInterface $dataBag): bool
184
    {
185 5
        $id = $this->updateStrategy->getRecordIdentifier($dataBag);
186
187 5
        if (!empty($id)) {
188 4
            $queryBuilder = $this->connection->createQueryBuilder();
189 4
            $queryBuilder->select('count(*)')
190 4
                ->from($this->table)
191 4
                ->setParameters($id);
192 4
            foreach (array_keys($id) as $key) {
193 4
                $queryBuilder->andWhere("$key = :$key");
194
            }
195 4
            $stmt = $this->connection->prepare($queryBuilder->getSQL());
196 4
            $stmt->execute($id);
197 4
            $count = (int)$stmt->fetchColumn();
198 4
            if ($count > 1) {
199 1
                throw new InvalidIdentificationException(
200 1
                    sprintf('Could not narrow results to 1 entry using given predicate: %s', json_encode($id))
201
                );
202
            }
203 3
            return $count === 1;
204
        }
205 1
        return false;
206
    }
207
}
208