Skip to content

Commit 82f791e

Browse files
committed
Initial commit
0 parents  commit 82f791e

File tree

8 files changed

+790
-0
lines changed

8 files changed

+790
-0
lines changed

composer.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "simply/database",
3+
"type": "library",
4+
"description": "A minimalistic database abstraction that provides convenience and structure",
5+
"keywords": [
6+
"database",
7+
"orm",
8+
"framework"
9+
],
10+
"license": "MIT",
11+
"authors": [
12+
{
13+
"name": "Riikka Kalliomäki",
14+
"email": "[email protected]",
15+
"homepage": "http://riimu.net"
16+
}
17+
],
18+
"require": {
19+
"php": "^7.2",
20+
"psr/container": "^1.0"
21+
},
22+
"require-dev": {
23+
"simply/container": "^0.2.1",
24+
"phpunit/phpunit": "^7.2",
25+
"riimu/php-cs-fixer-config": "^0.1.0",
26+
"riimu/sami-config": "^0.1.0"
27+
},
28+
"autoload": {
29+
"psr-4": {
30+
"Simply\\Database\\": "src/"
31+
}
32+
},
33+
"autoload-dev": {
34+
"psr-4": {
35+
"Simply\\Database\\": "tests/tests/"
36+
}
37+
}
38+
}

src/Connection/Connection.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Simply\Database\Connection;
4+
5+
/**
6+
* Connection.
7+
* @author Riikka Kalliomäki <[email protected]>
8+
* @copyright Copyright (c) 2018 Riikka Kalliomäki
9+
* @license http://opensource.org/licenses/mit-license.php MIT License
10+
*/
11+
interface Connection
12+
{
13+
public const ORDER_ASCENDING = 1;
14+
public const ORDER_DESCENDING = 2;
15+
16+
public function getConnection(): \PDO;
17+
public function getLastInsertId();
18+
public function insert(string $table, array $values, string & $primaryKey = null): \PDOStatement;
19+
public function select(array $fields, string $table, array $where, array $orderBy = [], int $limit = null): \PDOStatement;
20+
public function update(string $table, array $values, array $where): \PDOStatement;
21+
public function delete(string $table, array $where): \PDOStatement;
22+
public function query(string $sql, array $parameters = []): \PDOStatement;
23+
}

src/Connection/MySqlConnection.php

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
<?php
2+
3+
namespace Simply\Database\Connection;
4+
5+
/**
6+
* MySqlConnection.
7+
* @author Riikka Kalliomäki <[email protected]>
8+
* @copyright Copyright (c) 2018 Riikka Kalliomäki
9+
* @license http://opensource.org/licenses/mit-license.php MIT License
10+
*/
11+
class MySqlConnection implements Connection
12+
{
13+
private $initializer;
14+
private $pdo;
15+
16+
public function __construct($hostname, $database, $username, $password)
17+
{
18+
$this->lastId = false;
19+
$this->initializer = function () use ($hostname, $database, $username, $password) {
20+
return new \PDO($this->getDataSource($hostname, $database), $username, $password, [
21+
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
22+
\PDO::ATTR_EMULATE_PREPARES => false,
23+
\PDO::MYSQL_ATTR_INIT_COMMAND => sprintf("SET timezone = '%s'", date('P')),
24+
]);
25+
};
26+
}
27+
28+
private function getDataSource($hostname, $database)
29+
{
30+
if (strncmp($hostname, '/', 1) === 0) {
31+
return sprintf('mysql:unix_socket=%s;dbname=%s;charset=utf8mb4', $hostname, $database);
32+
}
33+
34+
$parts = explode(':', $hostname, 2);
35+
36+
if (\count($parts) === 1) {
37+
return sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', $hostname, $database);
38+
}
39+
40+
return sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $parts[0], $parts[1], $database);
41+
}
42+
43+
public function getConnection(): \PDO
44+
{
45+
if (!$this->pdo) {
46+
$this->pdo = ($this->initializer)();
47+
}
48+
49+
return $this->pdo;
50+
}
51+
52+
public function insert(string $table, array $values, string & $primaryKey = null): \PDOStatement
53+
{
54+
$parameters = [];
55+
$result = $this->query($this->formatQuery([
56+
'INSERT INTO' => sprintf('%s (%s)', $this->formatTable($table), $this->formatFields(array_keys($values))),
57+
'VALUES' => $this->formatParameters($values, $parameters),
58+
]), $parameters);
59+
60+
if ($primaryKey !== null) {
61+
$primaryKey = $this->getConnection()->lastInsertId();
62+
}
63+
64+
return $result;
65+
}
66+
67+
public function select(array $fields, string $table, array $where, array $orderBy = [], int $limit = null): \PDOStatement
68+
{
69+
$parameters = [];
70+
71+
return $this->query($this->formatQuery([
72+
'SELECT' => $this->formatFields($fields),
73+
'FROM' => $this->formatTable($table),
74+
'WHERE' => $this->formatConditions($where, $parameters),
75+
'ORDER BY' => $this->formatOrder($orderBy),
76+
'LIMIT' => $orderBy ? $this->formatLimit($limit, $parameters) : '',
77+
]), $parameters);
78+
}
79+
80+
public function update(string $table, array $values, array $where): \PDOStatement
81+
{
82+
$parameters = [];
83+
84+
return $this->query($this->formatQuery([
85+
'UPDATE' => $this->formatTable($table),
86+
'SET' => $this->formatAssignments($values, $parameters),
87+
'WHERE' => $this->formatConditions($where, $parameters)
88+
]), $parameters);
89+
}
90+
91+
public function delete(string $table, array $where): \PDOStatement
92+
{
93+
$parameters = [];
94+
95+
return $this->query($this->formatQuery([
96+
'DELETE FROM' => $this->formatTable($table),
97+
'WHERE' => $this->formatConditions($where, $parameters),
98+
]), $parameters);
99+
}
100+
101+
private function formatFields(array $fields): string
102+
{
103+
if (!$fields) {
104+
throw new \InvalidArgumentException('No fields provided for the query');
105+
}
106+
107+
return implode(', ', array_map(function (string $field) {
108+
return $this->escapeIdentifier($field);
109+
}, $fields));
110+
}
111+
112+
private function formatTable(string $table): string
113+
{
114+
if ($table === '') {
115+
throw new \InvalidArgumentException('No table provided for the query');
116+
}
117+
118+
return $this->escapeIdentifier($table);
119+
}
120+
121+
private function formatConditions(array $conditions, array & $parameters): string
122+
{
123+
if (!$conditions) {
124+
throw new \InvalidArgumentException('No conditions provided for the query');
125+
}
126+
127+
$clauses = [];
128+
129+
foreach ($conditions as $field => $value) {
130+
$clauses[] = $this->formatClause($field, $value, $parameters);
131+
}
132+
133+
return implode(' AND ', $clauses);
134+
}
135+
136+
private function formatClause(string $field, $value, array & $parameters): string
137+
{
138+
$escaped = $this->escapeIdentifier($field);
139+
140+
if (\is_array($value)) {
141+
if (\in_array(null, $value, true)) {
142+
$value = array_filter($value, function ($value): bool {
143+
return $value !== null;
144+
});
145+
146+
if ($value) {
147+
$placeholders = $this->formatParameters($value, $parameters);
148+
return "($escaped IN $placeholders OR $escaped IS NULL)";
149+
}
150+
151+
return "$escaped IS NULL";
152+
}
153+
154+
$placeholders = $this->formatParameters($value, $parameters);
155+
return "$escaped IN $placeholders";
156+
}
157+
158+
if ($value === null) {
159+
return "$escaped IS NULL";
160+
}
161+
162+
$parameters[] = $value;
163+
return "$escaped = ?";
164+
}
165+
166+
private function formatParameters(array $values, array & $parameters): string
167+
{
168+
array_push($parameters, ... array_values($parameters));
169+
return sprintf('(%s)', implode(', ', array_fill(0, \count($values), '?')));
170+
}
171+
172+
private function formatOrder(array $order): string
173+
{
174+
$clauses = [];
175+
176+
foreach ($order as $field => $direction) {
177+
$clauses[] = sprintf('%s %s', $this->escapeIdentifier($field), $this->formatDirection($direction));
178+
}
179+
180+
return implode(', ', $clauses);
181+
}
182+
183+
private function formatDirection(int $order): string
184+
{
185+
if ($order === self::ORDER_ASCENDING) {
186+
return 'ASC';
187+
}
188+
189+
if ($order === self::ORDER_DESCENDING) {
190+
return 'DESC';
191+
}
192+
193+
throw new \InvalidArgumentException('Invalid sorting direction');
194+
}
195+
196+
private function formatLimit(?int $limit, array & $parameters): string
197+
{
198+
if ($limit === null) {
199+
return '';
200+
}
201+
202+
$parameters[] = $limit;
203+
return '?';
204+
}
205+
206+
private function formatAssignments(array $values, array & $parameters): string
207+
{
208+
if (!$values) {
209+
throw new \InvalidArgumentException('No values provided for the query');
210+
}
211+
212+
$assignments = [];
213+
214+
foreach ($values as $field => $value) {
215+
$assignments[] = sprintf('%s = ?', $this->escapeIdentifier($field));
216+
$parameters[] = $value;
217+
}
218+
219+
return implode(', ', $assignments);
220+
}
221+
222+
private function escapeIdentifier(string $identifier): string
223+
{
224+
return "`$identifier`";
225+
}
226+
227+
private function formatQuery(array $clauses): string
228+
{
229+
$parts = [];
230+
231+
foreach ($clauses as $clause => $value) {
232+
if ($value === '') {
233+
continue;
234+
}
235+
236+
$parts[] = sprintf('%s %s', $clause, $value);
237+
}
238+
239+
return implode(' ', $parts);
240+
}
241+
242+
public function query(string $sql, array $parameters = []): \PDOStatement
243+
{
244+
$query = $this->getConnection()->prepare($sql);
245+
246+
foreach ($parameters as $name => $value) {
247+
$this->bindQueryParameter($query, \is_int($name) ? $name + 1 : $name, $value);
248+
}
249+
250+
$query->execute();
251+
252+
return $query;
253+
}
254+
255+
private function bindQueryParameter(\PDOStatement $query, $name, $value): bool
256+
{
257+
switch (true) {
258+
case \is_string($value):
259+
return $query->bindValue($name, $value, \PDO::PARAM_STR);
260+
case \is_float($value):
261+
return $query->bindValue($name, var_export($value, true), \PDO::PARAM_STR);
262+
case \is_int($value):
263+
return $query->bindValue($name, $value, \PDO::PARAM_INT);
264+
case \is_bool($value):
265+
return $query->bindValue($name, $value ? 1 : 0, \PDO::PARAM_INT);
266+
case $value === null:
267+
return $query->bindValue($name, null, \PDO::PARAM_NULL);
268+
default:
269+
throw new \InvalidArgumentException('Invalid parameter value type');
270+
}
271+
}
272+
}

src/Model.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Simply\Database;
4+
5+
/**
6+
* Model.
7+
* @author Riikka Kalliomäki <[email protected]>
8+
* @copyright Copyright (c) 2018 Riikka Kalliomäki
9+
* @license http://opensource.org/licenses/mit-license.php MIT License
10+
*/
11+
class Model
12+
{
13+
protected $record;
14+
15+
protected function __construct(Record $record)
16+
{
17+
$this->record = $record;
18+
}
19+
20+
public static function createFromDatabaseRecord(Record $record): Model
21+
{
22+
/** @var Model $model */
23+
$model = (new \ReflectionClass(static::class))->newInstanceWithoutConstructor();
24+
$model->record = $record;
25+
26+
return $model;
27+
}
28+
29+
public function getDatabaseRecord(): Record
30+
{
31+
return $this->record;
32+
}
33+
}

0 commit comments

Comments
 (0)