1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the BenGorFile package. |
5
|
|
|
* |
6
|
|
|
* (c) Beñat Espiña <[email protected]> |
7
|
|
|
* (c) Gorka Laucirica <[email protected]> |
8
|
|
|
* |
9
|
|
|
* For the full copyright and license information, please view the LICENSE |
10
|
|
|
* file that was distributed with this source code. |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
namespace BenGorFile\File\Infrastructure\Persistence\Sql; |
14
|
|
|
|
15
|
|
|
use BenGorFile\File\Domain\Model\File; |
16
|
|
|
use BenGorFile\File\Domain\Model\FileId; |
17
|
|
|
use BenGorFile\File\Domain\Model\FileMimeType; |
18
|
|
|
use BenGorFile\File\Domain\Model\FileName; |
19
|
|
|
use BenGorFile\File\Domain\Model\FileRepository; |
20
|
|
|
use BenGorFile\File\Infrastructure\Domain\Model\FileEventBus; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Sql file repository class. |
24
|
|
|
* |
25
|
|
|
* @author Beñat Espiña <[email protected]> |
26
|
|
|
* @author Gorka Laucirica <[email protected]> |
27
|
|
|
*/ |
28
|
|
|
final class SqlFileRepository implements FileRepository |
29
|
|
|
{ |
30
|
|
|
const DATE_FORMAT = 'Y-m-d H:i:s'; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* The pdo instance. |
34
|
|
|
* |
35
|
|
|
* @var \PDO |
36
|
|
|
*/ |
37
|
|
|
private $pdo; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* The file event bus, it can be null. |
41
|
|
|
* |
42
|
|
|
* @var FileEventBus|null |
43
|
|
|
*/ |
44
|
|
|
private $eventBus; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Constructor. |
48
|
|
|
* |
49
|
|
|
* @param \PDO $aPdo The pdo instance |
50
|
|
|
* @param FileEventBus|null $anEventBus The file event bus, it can be null |
51
|
|
|
*/ |
52
|
|
|
public function __construct(\PDO $aPdo, FileEventBus $anEventBus = null) |
53
|
|
|
{ |
54
|
|
|
$this->pdo = $aPdo; |
55
|
|
|
$this->eventBus = $anEventBus; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* {@inheritdoc} |
60
|
|
|
*/ |
61
|
|
View Code Duplication |
public function fileOfId(FileId $anId) |
|
|
|
|
62
|
|
|
{ |
63
|
|
|
$statement = $this->execute('SELECT * FROM file WHERE id = :id', ['id' => $anId->id()]); |
64
|
|
|
if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) { |
65
|
|
|
return $this->buildFile($row); |
66
|
|
|
} |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* {@inheritdoc} |
71
|
|
|
*/ |
72
|
|
View Code Duplication |
public function fileOfName(FileName $aName) |
|
|
|
|
73
|
|
|
{ |
74
|
|
|
$statement = $this->execute('SELECT * FROM file WHERE name = :name AND extension = :extension', [ |
75
|
|
|
'name' => $aName->name(), |
76
|
|
|
'extension' => $aName->extension(), |
77
|
|
|
]); |
78
|
|
|
if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) { |
79
|
|
|
return $this->buildFile($row); |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* {@inheritdoc} |
85
|
|
|
*/ |
86
|
|
View Code Duplication |
public function all() |
|
|
|
|
87
|
|
|
{ |
88
|
|
|
$statement = $this->execute('SELECT * FROM file'); |
|
|
|
|
89
|
|
|
if ($rows = $statement->fetch(\PDO::FETCH_ASSOC)) { |
90
|
|
|
return array_map(function ($row) { |
91
|
|
|
return $this->buildFile($row); |
92
|
|
|
}, $rows); |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* {@inheritdoc} |
98
|
|
|
*/ |
99
|
|
|
public function persist(File $aFile) |
100
|
|
|
{ |
101
|
|
|
($this->exist($aFile)) ? $this->update($aFile) : $this->insert($aFile); |
102
|
|
|
|
103
|
|
|
if ($this->eventBus instanceof FileEventBus) { |
104
|
|
|
$this->handle($aFile->events()); |
105
|
|
|
} |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* {@inheritdoc} |
110
|
|
|
*/ |
111
|
|
|
public function remove(File $aFile) |
112
|
|
|
{ |
113
|
|
|
$this->execute('DELETE FROM file WHERE id = :id', ['id' => $aFile->id()->id()]); |
114
|
|
|
|
115
|
|
|
if ($this->eventBus instanceof FileEventBus) { |
116
|
|
|
$this->handle($aFile->events()); |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* {@inheritdoc} |
122
|
|
|
*/ |
123
|
|
|
public function size() |
124
|
|
|
{ |
125
|
|
|
return $this->pdo->query('SELECT COUNT(*) FROM file')->fetchColumn(); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Loads the file schema into database create the table |
130
|
|
|
* with file attribute properties as columns. |
131
|
|
|
*/ |
132
|
|
|
public function initSchema() |
133
|
|
|
{ |
134
|
|
|
$this->pdo->exec(<<<SQL |
135
|
|
|
DROP TABLE IF EXISTS file; |
136
|
|
|
CREATE TABLE file ( |
137
|
|
|
id CHAR(36) PRIMARY KEY, |
138
|
|
|
name VARCHAR(255) NOT NULL, |
139
|
|
|
extension VARCHAR(100) NOT NULL, |
140
|
|
|
mime_type VARCHAR(255) NOT NULL, |
141
|
|
|
created_on DATETIME NOT NULL, |
142
|
|
|
updated_on DATETIME NOT NULL |
143
|
|
|
) |
144
|
|
|
SQL |
145
|
|
|
); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Checks if the file given exists in the database. |
150
|
|
|
* |
151
|
|
|
* @param File $aFile The file |
152
|
|
|
* |
153
|
|
|
* @return bool |
154
|
|
|
*/ |
155
|
|
|
private function exist(File $aFile) |
156
|
|
|
{ |
157
|
|
|
$count = $this->execute( |
158
|
|
|
'SELECT COUNT(*) FROM file WHERE id = :id', [':id' => $aFile->id()->id()] |
159
|
|
|
)->fetchColumn(); |
160
|
|
|
|
161
|
|
|
return $count === 1; |
|
|
|
|
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Prepares the insert SQL with the file given. |
166
|
|
|
* |
167
|
|
|
* @param File $aFile The file |
168
|
|
|
*/ |
169
|
|
|
private function insert(File $aFile) |
170
|
|
|
{ |
171
|
|
|
$sql = 'INSERT INTO file (id, name, extension, mime_type, created_on, updated_on) VALUES (:id, :name, :extension, :mimeType, :createdOn, :updatedOn)'; |
172
|
|
|
$this->execute($sql, [ |
173
|
|
|
'id' => $aFile->id()->id(), |
174
|
|
|
'name' => $aFile->name()->name(), |
175
|
|
|
'extension' => $aFile->name()->extension(), |
176
|
|
|
'mimeType' => $aFile->mimeType(), |
177
|
|
|
'createdOn' => $aFile->createdOn()->format(self::DATE_FORMAT), |
178
|
|
|
'updatedOn' => $aFile->updatedOn()->format(self::DATE_FORMAT), |
179
|
|
|
]); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Prepares the update SQL with the file given. |
184
|
|
|
* |
185
|
|
|
* @param File $aFile The file |
186
|
|
|
*/ |
187
|
|
|
private function update(File $aFile) |
188
|
|
|
{ |
189
|
|
|
$this->execute('UPDATE file SET name = :name, extension = :extension, mime_type = :mimeType, updated_on = :updatedOn WHERE id = :id', [ |
190
|
|
|
'name' => $aFile->name()->name(), |
191
|
|
|
'extension' => $aFile->name()->extension(), |
192
|
|
|
'mimeType' => $aFile->mimeType(), |
193
|
|
|
'updatedOn' => $aFile->updatedOn(), |
194
|
|
|
'id' => $aFile->id()->id(), |
195
|
|
|
]); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Wrapper that encapsulates the same logic about execute the query in PDO. |
200
|
|
|
* |
201
|
|
|
* @param string $aSql The SQL |
202
|
|
|
* @param array $parameters Array which contains the parameters of SQL |
203
|
|
|
* |
204
|
|
|
* @return \PDOStatement |
205
|
|
|
*/ |
206
|
|
|
private function execute($aSql, array $parameters) |
207
|
|
|
{ |
208
|
|
|
$statement = $this->pdo->prepare($aSql); |
209
|
|
|
$statement->execute($parameters); |
210
|
|
|
|
211
|
|
|
return $statement; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Builds the file with the given sql row attributes. |
216
|
|
|
* |
217
|
|
|
* @param array $row Array which contains attributes of file |
218
|
|
|
* |
219
|
|
|
* @return File |
220
|
|
|
*/ |
221
|
|
|
private function buildFile($row) |
222
|
|
|
{ |
223
|
|
|
$file = new File( |
224
|
|
|
new FileId($row['id']), |
225
|
|
|
new FileName($row['name'] . '.' . $row['extension']), |
226
|
|
|
new FileMimeType($row['mime_type']) |
227
|
|
|
); |
228
|
|
|
|
229
|
|
|
$createdOn = new \DateTimeImmutable($row['created_on']); |
230
|
|
|
$updatedOn = new \DateTimeImmutable($row['updated_on']); |
231
|
|
|
|
232
|
|
|
$file = $this->set($file, 'createdOn', $createdOn); |
233
|
|
|
$file = $this->set($file, 'updatedOn', $updatedOn); |
234
|
|
|
|
235
|
|
|
return $file; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Populates by Reflection the domain object with the given SQL plain values. |
240
|
|
|
* |
241
|
|
|
* @param File $file The file domain object |
242
|
|
|
* @param string $propertyName The property name |
243
|
|
|
* @param mixed $propertyValue The property value |
244
|
|
|
* |
245
|
|
|
* @return File |
246
|
|
|
*/ |
247
|
|
|
private function set(File $file, $propertyName, $propertyValue) |
248
|
|
|
{ |
249
|
|
|
$reflectionFile = new \ReflectionClass($file); |
250
|
|
|
$reflectionCreatedOn = $reflectionFile->getProperty($propertyName); |
251
|
|
|
$reflectionCreatedOn->setAccessible(true); |
252
|
|
|
$reflectionCreatedOn->setValue($file, $propertyValue); |
253
|
|
|
|
254
|
|
|
return $file; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Handles the given events with event bus. |
259
|
|
|
* |
260
|
|
|
* @param array $events A collection of file domain events |
261
|
|
|
*/ |
262
|
|
|
private function handle($events) |
263
|
|
|
{ |
264
|
|
|
foreach ($events as $event) { |
265
|
|
|
$this->eventBus->handle($event); |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.