Ещё 1 класс для работы с БД

che

Новичок
По мотивам темы "Оцените класс работы с БД на говнокод."

Для тех кто хочет предложить мне почитать ".. psr, phpdoc, неймспейсы, phpunit .." я уже читаю.
Кто хочет мне предложить "взять нормальную ORM, например {%super_orm_name%}" - нет. Просто нет.
Если вам больше нечего написать - проходите, пожалуйста, мимо.

Класс
PHP:
/**
* Обертка над SQL запросами
*
* Class DBWrapper
*
*/
class DBWrapper {
    /**
    * Производит SQL-зарос на выбор 1 элемента
    * Возвращает false если запись не найдена и ассоциативный массив записи, если она была найдена
    *
    * @param string $sql
    * @param array $params
    * @return bool|array
    */
    public static function find($sql, $params=array()){
        $response = false;

        $query  = self::execute($sql, $params);
        if(mysql_num_rows($query)){
            $response = mysql_fetch_assoc($query);
        }

        return $response;
    }

    /**
    * Производит SQL-зарос на выбор 1 элемента
    * Возвращает false если запись не найдена и значение указанного столбца в случае успеха
    *
    * @param string $sql SQl-выражение
    * @param int|string $column номер или имя столбца
    * @param array $params
    * @return mixed
    */
    public static function findColumn($sql, $column, $params=array()){
        $response = false;

        $query  = self::execute($sql, $params);
        if(mysql_num_rows($query)){
            if(is_integer($column)){
                $response = mysql_fetch_row($query);
            } else {
                $response = mysql_fetch_assoc($query);
            }

            $response  = $response[$column];
        }

        return $response;
    }

    /**
    * Производит SQL-запрос на выборку списка
    *
    * @param string $sql
    * @param array $params
    * @return array
    */
    public static function findAll($sql, $params=array()){
        $response = array();

        $query  = self::execute($sql, $params);
        if(mysql_num_rows($query)){
            while(($row = mysql_fetch_assoc($query)) !== false){
                $response[] = $row;
            }
        }

        return $response;
    }

    /**
    * Выполняет SQL-запрос предварительно подготавливая переданные данные
    *
    * @param string $sql строка запроса
    * @param array $params параметры запроса
    * @return resource
    */
    public static function execute($sql, $params=array()){
        if($params){
            $params = self::prepareParams($params);

            foreach($params as $key=>$value){
                $sql = preg_replace("/$key(,|\s|\)|$){1}/", "{$value}$1", $sql);
            }
        }

       
       return self::executeSql($sql);
    }

    /**
    * Производит подготовку параметров для использваония в SQL-запросе
    *
    * @param array $params
    * @return array
    */
    protected static function prepareParams($params){
        if(!$params){
            return $params;
        }

        $prepareFunction = self::getPrepareFunction();

        foreach($params as $key=>&$value){
            if(is_array($value)){
                $value = self::prepareParams($value);
                $value = implode(',', $value);
            } else {
                $value = "'". self::$prepareFunction($value) ."'";
            }
        }

        return $params;
    }

    /**
    * Возвращает имя функции-обработчика значения для SQL-запроса в зависимости от значения magic_quotes_gpc
    *
    * @return string
    */
    protected static function getPrepareFunction(){
        if(get_magic_quotes_gpc()){
            return "prepareMagicQuotes";
        }

        return "prepareNoMagicQuotes";
    }

    /**
    * Подготавливает значения для SQL-запроса, при условии что magic_quotes_gpc=on
    *
    * @param mixed $value
    * @return string
    */
    protected static function prepareMagicQuotes($value){
        return self::prepareNoMagicQuotes(stripslashes($value));
    }
    /**
    * Подготавливает значения для SQL-запроса, при условии что magic_quotes_gpc=off
    *
    * @param mixed $value
    * @return string
    */
    protected static function prepareNoMagicQuotes($value){
        // TODO: выделить преобразование html-кода в отдельное место (класс или еще что)
        return mysql_real_escape_string(htmlspecialchars_decode($value));
    }

    /**
    * Выполняет sql-запрос и возвращает ресурс запроса
    *
    * @param $sql string - sql-запрос
    * @return resource
    * @throws Exception
    */
    protected static function executeSql($sql){
        $query = mysql_query($sql);

        if(!$query){
            throw new Exception("Ошибка выполнения SQL-запроса: \"{$sql}\" \n ". mysql_errno() ." : ". mysql_error());
        }

        return $query;
    }
}
Что есть
Сейчас у меня только обёртка для экранирования переменных параметров запроса и реализация часто востребованных функций: получить строку, получить набор строк, получить значение столбца.
Используется это вот так:
PHP:
$persons = DBWrapper::findAll("SELEC * FROM person WHERE name=:name AND second_name IN (:second_name)", array(':name'=>$personName, ':second_name'=>array('Пупкин', 'Васечкин', 'Васильев')));
Чего хотелось бы
  1. Заключить в класс параметры подключения к БД и чтобы можно было быстро и без напряга менять одно подключение к БД на другое (иногда возникает такая потербность). Вроде такого:

    PHP:
    $a = $a = DBWrapper::find("SELECT * FROM table1");
    DBWrapper::setDb('db2');
    $b = $a = DBWrapper("SELECT * FROM table1");
  2. Изменить реализацию find() и findAll() таким образом что бы можно было сделать так:
    PHP:
    $a = DBWrapper::find("SELCT * FROM tabnle 1")->asRow();
    $b = DBWrapper::find("SELCT * FROM tabnle 1")->asArray();
    
    $c = DBWrapper::findAll("SELCT * FROM tabnle 1");
    foreach($c as $ite){
        $data[] = $item->asRow();
    }
  3. И меня ещё смущает реализация метода findAll() - хотелось бы не наполнять массив без необходимости, а возвращать какой-нибудь объект реализующий интерфейс Iterator или ещё что.
  4. куда-нибудь вынести восстановление html-сущностей:
    PHP:
    ..
    protected static function prepareNoMagicQuotes($value){
            // TODO: выделить преобразование html-кода в отдельное место (класс или еще что)
            return mysql_real_escape_string(htmlspecialchars_decode($value));
        }
    ..

PS. Приветствуются примеры кода, ссылки на реализации в том или ином проекте/фреймворке, конструктивная критика.
 

c0dex

web.dev 2002-...
Команда форума
Партнер клуба
Это не класс, а набор говнофункций
Ну не так все плохо, для молодого парня) Но lolwut конечно доставил.

che, ответь на вопрос, какова область применения этого враппера?

Вцелом же я бы писал что-то вроде такого, чтобы бы можно было сделать пул соединений
PHP:
$a = DBWrapper::connection('name1')->find("SELECT * FROM table1");
$b = DBWrapper::connection('name2')->find("SELECT * FROM table1");
Но необходимость такого функционала для такого рода либ - стремится к нулю.

Ты бы написал почему, а? Всем понятно, что многое плохо, но замечания надо писать, а не говном кидаться)
 

Вурдалак

Продвинутый новичок
c0dex, зачем? Я просто троллю. Я начинаю троллить, если мой кодоговнометр начинает зашкаливать. Есть 2 пути: либо указать на все ошибки, а это требует времени, либо просто и лаконично описать всё в целом. На первое у меня нет никакой мотивации. Человеку уже посоветовали (автор треда же тот же самый, что и прошлого или это просто новый рандомный чувак?) посмотреть реализации других DBAL, но
Кто хочет мне предложить "взять нормальную ORM, например {%super_orm_name%}" - нет. Просто нет.
Увы, это говно. Просто говно.
 

c0dex

web.dev 2002-...
Команда форума
Партнер клуба
Вурдалак, если автор тот же, ладно. Если нет, то осознанный фидбэк был бы неплох, особенно если учесть, что смотреть реализации ORM - дело муторное, я до сих пор не хочу влезать в нутро Eloquent, чую там все очень неприятно.
 

Вурдалак

Продвинутый новичок
Окей, пусть покажет
  • запрос с LIMIT :limit
  • запрос типа «UPDATE foo = :foo, bar = :bar» с параметрами [':foo' => 'I am :bar, biAtch', ':bar' => ', bar = 0x594f55205355434b-- ']
 

С.

Продвинутый новичок
И меня ещё смущает реализация метода findAll() - хотелось бы не наполнять массив без необходимости, а возвращать какой-нибудь объект реализующий интерфейс Iterator или ещё что.
То есть ты запрашиваешь данные, получаешь их, но тебе они могут и не понадобиться?
 

fixxxer

К.О.
Партнер клуба
ORM - дело муторное, я до сих пор не хочу влезать в нутро Eloquent, чую там все очень неприятно.
С учетом постановки задачи (RoR-style activerecord, тут сложно написать без дерьма), более-менее вменяемо.
 

hell0w0rd

Продвинутый новичок
Для тех кто хочет предложить мне почитать ".. psr, phpdoc, неймспейсы, phpunit .." я уже читаю.
Кто хочет мне предложить "взять нормальную ORM, например {%super_orm_name%}" - нет. Просто нет.
Если вам больше нечего написать - проходите, пожалуйста, мимо.
А чего ты хочешь-то выложив сюда код?
 

che

Новичок
fixxxer, Вурдалак - спасибо за конструктивную критику, замечания высказанные вами постараюсь устарнить.

Окей, пусть покажет
  • запрос с LIMIT :limit
  • запрос типа «UPDATE foo = :foo, bar = :bar» с параметрами [':foo' => 'I am :bar, biAtch', ':bar' => ', bar = 0x594f55205355434b-- ']
Подстановка используется только для значений записываемых в БД или используемых в параметрах выборки. Обертка писалась что бы не делать экранирование всех параметров в ручную. Ну и что бы каждый раз не писать так:
PHP:
$sql = "SELECT * FROM people";
$query = mysql_query($sql) or die('Ошибка в sql-запросе: ' . $sql);
if($query && mysql_num_rows($query)){
    while(($row = mysql_fet_assoc($query)) !== false){
        $data[] = $row;
    }
}
В случае UPDATE будет получено не корректное sql-выражение, но на такие случаи я и не рассчитывал:
Код:
UPDATE foo = 'I am ', bar = 0x594f55205355434b--', biAtch', bar = ', bar = 0x594f55205355434b--'
hell0w0rd - я написал чего я хочу, от сообщества же я жду посильной помощи

c0dex - спасибо, неплохой вариант, единственное я бы добавил еще вызов для БД по-умолчанию - без указания имени соединения:
PHP:
$a = DBWrapper::connection()->find("SELECT * FROM table1");
С. - может быть и так, но чаще всего получаемый массив данных потом проходит еще 1 цикл, уже для коррекции данных, получается что один цикл делает findAll() а второй, над теми же данными уже получатель запроса, мне немного не нравиться это.

==========================================

вообще я уже пытался реализовать паттерн одиночка, но я никак не могу обойти необходимость в начале выполнения программы явно передавать в класс параметры подключения, т.е.:
PHP:
// где-то в начале выполнения скиприта
DBWrapper::instance()->connectionParam(arrray('host','user', 'passsw', 'db_name'));

...

// и только потом уже я могу спокойно использовать:
$a = DBWrapper::instance()->find("SELECT * FROM people");
а хотелось бы:
PHP:
// конфигурационный файл
array(
  'db'=>array(
    'host'=>'host_name',
    'user'=>'root',
    'passwd'=>'qwerty',
    'db_name'=>'db1',
  ),
);

...

// где-то  в коде я просто делаю так:
$a = DBWrapper::instance()->find("SELECT * FROM person");
// и подключение устанавливается с нужными параметрами само
был бы признателен, если кто-нибудь подскажет где можно посмотреть подобную реализацию
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Почти корректный. В моём примере есть пробел после «--», у тебя он куда-то пропал. Ну, и table, понятное дело.

Плейсхолдеры нельзя заменять в несколько проходов.
 

fixxxer

К.О.
Партнер клуба
вообще я уже пытался реализовать паттерн одиночка, но я никак не могу обойти необходимость в начале выполнения программы явно передавать в класс параметры подключения, т.е.:
Потому что не надо делать не набор говнофункций, а надо делать полноценный класс. :)

PHP:
$db = new Database($connectionParameters)
А дальше, если так вперся дурацкий синглтон, и хочется продолжать писать без использования хоть какого-то вменяемого механизма управления зависимостями, который бы этот вопрос взял на себя, можно сделать банальный синглтон-обертку:

PHP:
DB::setInstance($db)
 

che

Новичок
Потому что не надо делать не набор говнофункций, а надо делать полноценный класс. :)
Я уже писал что начальная цель была избежать ручного экранирования параметров и сократить кол-во строк запрятав все операции по выбору данных в 1 контейнер. Цель достигнута. Теперь мне хочется что бы там еще хранился ресурс подключения к БД, в конечном итоге такой код будет:
PHP:
$db = new Database($connectionParameters);
DB::setInstance($db);
Но мне бы не хотелось это писать руками, хочется прослойку которая бы сама все это делала и брала параметры подключения из конфигурационного файла (я уже писал выше), и еще не создавала бы экземпляр без необходимости. Так вот, может ли мне кто подсказать делают ли так вообще или это плохой тон, если это норма, то хотелось бы увидеть пример кода делающий это, хотя бы в общих чертах, либо словесное описание либо ссылку на конкретный абзац стороннего ресурса (на русском) с описанием механизма.

А дальше, если так вперся дурацкий синглтон..
Как я понял его обычно для подключения к БД и применяют, во всяком случае это самый распространённый пример. И если без него, то как протаскивать переменную хранящую в себе класс в функции которые работают с БД? Я не считаю что переменную с экземпляром класса работающего с БД объявлять глобальной или передавать её в качестве параметра в каждую функцию работающую с БД - это хороший вариант. Как минимум - много переделывать из того что уже есть.
Если у вас есть пример без синглтона я с радостью его просмотрю.
 
Последнее редактирование:

c0dex

web.dev 2002-...
Команда форума
Партнер клуба
в database.php конфиге делается секция с именем вида:
PHP:
...
'connections' => array(
        'sqlite' => array(
            'driver'   => 'sqlite',
            'database' => __DIR__.'/../database/production.sqlite',
            'prefix'   => '',
        ),
        'mysql' => array(
            'driver'    => 'mysql',
            'host'      => 'localhost',
            'database'  => 'DB',
            'username'  => 'root',
            'password'  => 'root',
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
        ),
    ),
...
а драйвер сам решает по именованному параметру $users = DB::connection('foo')->select(...); что ему высадить из массива и использовать.
 

che

Новичок
c0dex - спасибо, это вариант. Возникает правда необходимость в разборе конфига в коде класса DB либо в написании отдельного класса который будет разбирать конфиг и выдавать параметры по требованию, а потом еще как-то надо передавать экземпляр класса-конфигуратора в класс DB, хотя тут можно как раз и использовать singletone для класса-конфигуратора, а в DB будет как-то так:
PHP:
// ассоциативный, хранит ресурсы подключения к БД
protected $dbConnections = array();
// ключ текущего соединения в массиве $dbConnections
protected $currConnectionName = '';

public static connection($dbName){
  if(self::$_itself === null){
    self::$_itself = new self();
  }
  $db = self::$_itself;

  if(!array_key_exists($dbName, $db->dbConnections)){
    $db->dbConnections[$dbName] = mysql_connect(Configurator::instance()->getDBParam($dbName));
    mysql_select_db($dbParam['db_name'], $dbName);
  }

  $db->currConnectionName = $dbName;

  return $db;
}

public function find($sql, $params=array){
  ...
  $result = mysql_query($sql, $this->dbConnections[$this->currConnectionName]);
  ...
}
...
Где-то в начале выполнения скрипта:
PHP:
$configDB = array(
'sqlite' => array(
            'driver'  => 'sqlite',
            'database' => __DIR__.'/../database/production.sqlite',
            'prefix'  => '',
        ),
        'mysql' => array(
            'driver'    => 'mysql',
            'host'      => 'localhost',
            'database'  => 'DB',
            'username'  => 'root',
            'password'  => 'root',
            'charset'  => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
        ),
);

Configurator::instance($configDB);
Теперь можно не задумываться о том установлено подключение или нет:
PHP:
$peopleList = DB::connection('db1')->findAll('SELECT * FROM people');
Меня смущает то что класс DB прямо зависит от класса Configurator, можно ли этого избежать или это нормальная практика?

PS. c0dex, спасибо за хороший пример конфига, он во многом мне помог
 

c0dex

web.dev 2002-...
Команда форума
Партнер клуба
Посмотри на laravel последний
 
Сверху