Porównujemy naszą klasę PDOConnection oraz klasę Database z projektu PHPiggy, którego autor w dość ciekawy sposób podszedł do tematu. Zaczynajmny!
Jednym z istotnych elementów naszej klasy jest parser pliku ini:
class ConfigINI {
public static $iniPath = __DIR__ . "/config.ini";
public static function getPDOCredentials(){
$ini_array = parse_ini_file(static::$iniPath, true);
return $ini_array['DB_CREDENTIALS'];
}
}
Nasza klasa zawiera connection, requiredKeys oraz metodę getConnection:
class PDOConnection {
protected $connection;
public static $requiredKeys = [
'driver',
'host',
'db_name',
'db_user',
'db_password',
'port'
];
//(...)
public function getConnection(): PDO|null
{
return $this->connection;
}
//(...)
}
Metoda hasRequiredKeys sprawdza, czy odpowiednie klucze zostały przekazane:
class PDOConnection {
protected $connection;
public static $requiredKeys = [
'driver',
'host',
'db_name',
'db_user',
'db_password',
'port'
];
//(...)
public static function hasRequiredKeys($credentials){
foreach(self::$requiredKeys as $key){
if(!array_key_exists($key, $credentials))
return false;
}
return true;
}
}
Metoda parseCredentials parsuje dane na dsn, użytkownika i hasło:
class PDOConnection {
protected $connection;
public static $requiredKeys = [
'driver',
'host',
'db_name',
'db_user',
'db_password',
'port'
];
//(...)
public static function parseCredentials($credentials){
$driver = $credentials['driver'];
$config = http_build_query(arg_separator:';', data:
[
'host' => $credentials['host'],
'port' => $credentials['port'],
'dbname' => $credentials['db_name']
]
);
$dsn = "{$driver}:{$config}";
return [$dsn, $credentials['db_user'], $credentials['db_password']];
}
}
Metoda connect pobiera dane z pliku ini, dodaje port 3306, jeżeli go nie określiliśmy, sprawdza klucze, parsuje dsn i inne głupoty i nawiązuje połączenie:
class PDOConnection {
protected $connection;
//(...)
public function connect(): PDOConnection{
try {
$this->credentials = ConfigINI::getPDOCredentials();
if(!array_key_exists('port', $this->credentials))
$this->credentials['port'] = 3306;
if(!static::hasRequiredKeys($this->credentials))
throw new Error("Credentials dont have valid keys");
$parsed = static::parseCredentials($this->credentials);
$this->connection = new PDO(...$parsed);
} catch (Exception $e) {
echo $e->getMessage();
}
return $this;
}
}
Autor PHPiggy proponuje coś innego – rozdzielenie na PDOConnection i PDOStatement:
use PDO, PDOException, PDOStatement;
class Database
{
private PDO $connection;
private PDOStatement $stmt;
//(...)
}
W podobny sposób nawiązuje połączenie:
use PDO, PDOException, PDOStatement;
class Database
{
private PDO $connection;
private PDOStatement $stmt;
public function __construct(
string $driver,
array $config,
string $username,
string $password
) {
$config = http_build_query(data: $config, arg_separator: ';');
$dsn = "{$driver}:{$config}";
try {
$this->connection = new PDO($dsn, $username, $password, [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (PDOException $e) {
die("Unable to connect to database");
}
}
}
Zapewnia bardzo elastyczne metody:
class Database
{
private PDO $connection;
private PDOStatement $stmt;
//(...)
public function query(string $query, array $params = []): Database
{
$this->stmt = $this->connection->prepare($query);
$this->stmt->execute($params);
return $this;
}
public function count()
{
return $this->stmt->fetchColumn();
}
//(...)
}
Takie postawienie sprawy pozwala mu na bardzo elastyczny kod:
class UserService
{
public function __construct(private Database $db)
{
}
public function isEmailTaken(string $email)
{
//DOBRZE NAPISANA METODA QUERY Z BINDINGIEM TABLICOWYM JAKO DRUGIM ARGUMENTEM
$emailCount = $this->db->query(
"SELECT COUNT(*) FROM users WHERE email = :email",
[
'email' => $email
]
)->count();
//METODA QUERY ZWRACA THIS A NA THIS MOŻNA CHAINOWAĆ INNE METODY, NP. COUNT
//DZIĘKI SEPARACJI NA CONNECTION I STATEMENT TO WSZYSTKO DZIAŁA,
//BO QUERY ZAPISAŁO STMT I ZWRÓCIŁO THIS, ZAŚ COUNT OPERUJE NA STMT
//(...)
}
Całość klasy wygląda tak i stanowi bardzo dobry wzór:
class Database
{
private PDO $connection;
private PDOStatement $stmt;
//(...)
public function query(string $query, array $params = []): Database
{
$this->stmt = $this->connection->prepare($query);
$this->stmt->execute($params);
return $this;
}
public function count()
{
return $this->stmt->fetchColumn();
}
public function find()
{
return $this->stmt->fetch();
}
public function id()
{
return $this->connection->lastInsertId();
}
public function findAll()
{
return $this->stmt->fetchAll();
}
}