In order to reduce the boilerplate code, I decided to write a lightweight database helper, based on the Table Data Gateway pattern (or at least the way I understand it) with the aim of using it for a simple CRUD application.
Writing INSERT and UPDATE queries is always a nuisance, repeating all those ranks of column names, parameter names, binding parameters and such. So I decided to write a simple class that does all the job under the hood, and accepts/returns simple arrays, without going down the rabbit hole of creating a full featured ORM.
My aim was also to make this class as secure as possible, hence the table and field names are always hardcoded.
It consists of one prototype class,
abstract class BasicTableGateway
{
protected $db;
protected $table;
protected $fields;
protected $primary = 'id';
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function listBySQL(string $sql, array $params): array
{
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
public function list(int $page = 1, int $limit = 50): array
{
$page--;
$offset = $page * $limit;
$stmt = $this->db->prepare("SELECT * FROM `$this->table` LIMIT ?,?");
$stmt->execute([$offset,$limit]);
return $stmt->fetchAll();
}
public function read($id): ?array
{
$ret = $this->listBySQL("SELECT * FROM `$this->table` WHERE `$this->primary`=?", [$id]);
return $ret ? reset($ret) : null;
}
public function update(array $data, int $id): void
{
$this->validate($data);
$params = [];
$set = "";
foreach($data as $key => $value)
{
$set .= "`$key` = ?,";
$params[] = $value;
}
$set = rtrim($set, ",");
$params[] = $id;
$sql = "UPDATE `$this->table` SET $set WHERE `$this->primary`=?";
$this->db->prepare($sql)->execute($params);
}
public function insert($data): int
{
$this->validate($data);
$fields = '`'.implode("`,`", array_keys($data)).'`';
$placeholders = str_repeat('?,', count($data) - 1) . '?';
$sql = "INSERT INTO `$this->table` ($fields) VALUES ($placeholders)";
$this->db->prepare($sql)->execute(array_values($data));
return $this->db->lastInsertId();
}
public function delete($id)
{
$sql = "DELETE FROM `$this->table` WHERE `$this->primary`=?";
$this->db->prepare($sql)->execute([$id]);
}
protected function validate($data)
{
$diff = array_diff(array_keys($data), $this->fields);
if ($diff) {
throw new \InvalidArgumentException("Unknown field(s): ". implode($diff));
}
}
}
and children classes where related properties are set to operate certain tables:
class UserGateway extends BasicTableGateway {
protected $table = 'users';
protected $fields = ['email', 'password', 'name', 'birthday'];
}
which is then used like this
$data = [
'email' => '[email protected]',
'password' => 'hash',
'name' => 'Fooster',
];
$id = $userGateway->insert($data);
$user = $userGateway->read($id);
echo json_encode($user),PHP_EOL;
$userGateway->update(['name' => 'Wooster'], $id);
$user = $userGateway->read($id);
echo json_encode($user),PHP_EOL;
$users = $userGateway->list();
echo json_encode($users),PHP_EOL;
$userGateway->delete($id);
The code works all right for a simple CRUD but I'd like to know whether it can be improved or may be there are some critical faults I don't realize.
list
can internally calllistBySql
\$\endgroup\$