Является ли эта оболочка для «хорошего кода» PDO?Есть ли потенциальные проблемы?
-
03-07-2019 - |
Вопрос
Я создал этот класс для работы с PDO, чтобы сделать SQL-запросы «более простыми» и не о которых беспокоиться.
Вот мои мысли
- Должно ли это быть больше похоже на то, что класс DB расширяет PDO?
- Является ли метод запроса слишком большим?Следует ли разделить его на частные методы, которые называются..это то, что известно как Слабая связь?
- Является ли мой способ обнаружения запроса SELECT слишком уродливым?
- Какие еще проблемы очевидны?Поскольку я учусь по ходу дела, я уверен, что мог бы упустить из виду множество потенциальных проблем.
Спасибо
`
class Db
{
private static $_instance = NULL;
private function __construct() {
// can not call me
}
private function __clone() {
// no!
}
public static function getInstance() {
if (!self::$_instance)
{
try {
self::$_instance = new PDO('mysql:host=' . CONFIG_MYSQL_SERVER . ';dbname=' . CONFIG_MYSQL_DATABASE, CONFIG_MYSQL_USERNAME, CONFIG_MYSQL_PASSWORD);;
self::$_instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
trigger_error($e->getMessage());
}
}
return self::$_instance;
}
public static function query($query /*string*/, $bindings = NULL)
{
$queryPortion = substr($query,0, 6);
try {
if ($bindings) {
$prepared = self::getInstance()->prepare($query);
foreach($bindings as $binding=>$data) { // defaults to string
if (!is_array($data)) {
$prepared->bindParam($binding, $data);
} else {
switch(count($data)) {
case 1:
$prepared->bindParam($binding, $data['value']);
break;
case 2:
$prepared->bindParam($binding, $data['value'], $data['dataType']);
break;
case 3:
$prepared->bindParam($binding, $data['value'], $data['dataType'], (int)$data['length']);
break;
default:
trigger_error('An error has occured with the prepared statement bindings.');
return false;
break;
}
}
}
$prepared->execute();
return $prepared->fetchAll(PDO::FETCH_ASSOC);
} else if (String::match($queryPortion, 'select')) { // if this is a select query
$rows = self::getInstance()->query($query);
return $rows->fetchAll(PDO::FETCH_ASSOC);
} else {
return self::getInstance()->exec($query);
}
}
catch(PDOException $e)
{
trigger_error($e->getMessage());
}
}
public static function getLastInsertId()
{
try {
self::getInstance()->lastInsertId();
}
catch(PDOException $e)
{
trigger_error($e->getMessage());
}
}
public static function disconnect()
{
// kill PDO object
self::$_instance = NULL;
}
}
Решение
Это неплохо, и, как уже было сказано, это может помочь для небольших приложений, хотя в основном это очень тонкая абстракция на другой абстракции.Это не приносит много других функций.
Что-то, что вы, возможно, захотите рассмотреть, среди прочего:
- Поскольку это код PHP5, используйте исключения вместо
trigger_error
иset_exception_handler
при необходимости, пока исключения не станут более распространенными, но это определенно чище и более перспективно. - Вы используете синглтон, это не обязательно плохо, но в данном случае, например, одним недостатком будет то, что вы сможете обрабатывать только одно соединение с одной базой данных.
- Я не знаю, используете ли вы хранимые процедуры, но хранимая процедура может вернуть набор результатов сквозь
query()
тоже метод. - У вас есть две точки с запятой (
;;
) в конце вашегоnew PDO
линия.
При этом я не думаю, что ваш метод запроса слишком велик, и на данный момент мало что можно вспомнить из других источников.Хотя как только вы увидите две-три строки, которые можно вызвать из другой функции, разделите их.Это хороший способ СУХОЙ.
Другие советы
Да и нет.
Это хороший код для простого, быстрого и грязного приложения.
Проблема возникает, когда вы используете это в более сложном структурированном приложении.Обработка ошибок будет зависеть от того, какой sql вы выполняете.
Кроме того, любые серьезные ошибки будут отображаться как ошибки типа «проблема в строке 999», где 999 находится в вашей супер -дуперской рутине, и у вас будут трудности отслеживать его обратно к конкретному запросу SQL.
Сказав, что я сам постоянно делаю подобные вещи в небольших проектах.
Вот что я использовал (просто замените ссылки на Zzz_Config на $GLOBALS['db_conf'] или что-то в этом роде):
/**
* Extended PDO with databse connection (instance) storage by name.
*/
class Zzz_Db extends PDO
{
/**
* Named connection instances.
*
* @var array
*/
static private $_instances;
/**
* Retrieves (or instantiates) a connection by name.
*
* @param string $name Connection name (config item key).
* @return Zzz_Db Named connection.
*/
static public function getInstance($name = null)
{
$name = $name === null ? 'db' : "db.$name";
if (!isset(self::$_instances[$name])) {
if (!$config = Zzz_Config::get($name)) {
throw new RuntimeException("No such database config item: $name");
}
if (!isset($config['dsn'])) {
if (!isset($config['database'])) {
throw new RuntimeException('Invalid db config');
}
$config['dsn'] = sprintf('%s:host=%s;dbname=%s',
isset($config['adapter']) ? $config['adapter'] : 'mysql',
isset($config['host']) ? $config['host'] : 'localhost',
$config['database']);
}
$db = self::$_instances[$name] = new self(
$config['dsn'],
isset($config['username']) ? $config['username'] : null,
isset($config['password']) ? $config['password'] : null);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, 'Zzz_Db_Statement');
if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$db->exec('SET CHARACTER SET utf8');
}
}
return self::$_instances[$name];
}
}
Использование может быть:
$db = Zzz_Db::getInstance(); // or Zzz_Db::getInstance('some_named_db')
$stmt = $db->prepare('SELECT ...
Цель состоит в том, чтобы сохранить конфигурацию базы данных в файле *.ini (доступном для редактирования непрограммистами).
Я пошел другим путем и создал класс, расширяющий PDO с помощью множества функций-оболочек вокруг него. prepare()
/execute()
, и это намного лучше, чем встроенные функции (хотя это немного субъективно...).
Еще одна вещь:тебе следует установить PDO::ATTR_EMULATE_PREPARES
к false
если вы не используете очень старую версию MySQL (<= 4.0).По умолчанию это true
, что является огромной головной болью и заставляет вещи непонятным образом ломаться...Думаю, именно поэтому у тебя огромная обертка bindParam()
в первую очередь.
Чтобы ответить на ваш вопрос, хороший это код или нет, спросите себя:
Какова добавленная стоимость моего кода по сравнению с прямым использованием PDO?
Если вы найдете хороший ответ, используйте свой код.Если нет, я бы придерживался PDO.
Также попробуйте рассмотреть возможность реализации Zend-фреймворкКласс DB, который работает сам по себе и поддерживает PDO.