Разделение DBAL на два класса, Connection и Statement

AnrDaemon

Продвинутый новичок
Затем, что следующий запрос в текущем соединении, в зависимости от типа БД, …
а) …выполнится и создаст новый результсет, не заменяющий текущий,
б) …выполнится и создаст новый результсет, заменяющий текущий,
в) …не выполнится и пошлёт тебя сначала закрыть результсет, с которым ты работаешь,
г) …что там ещё взбредёт на ум создателям БД.
Что именно произойдёт, и как это обрабатывать - вопрос к драйверу, не к Connection, и тем более не к Statement.
То, что там написано clone, вовсе не озчает, что новый коннект будет создан. Это внутренняя логика работы класса, и ты её контролируешь так, как тебе нужно.
Конечно, если ты пишешь свой класс под единственную БД с заранее известным поведением, можешь забыть всё, что я тут написал.
 

Фанат

oncle terrible
Команда форума
Затем, что следующий запрос в текущем соединении, в зависимости от типа БД, …
Дело в том, что все описанные тобой ситуации прекрасно решаются по ходу разработки.
Ты, наверное, столкнулся с такими пару раз, не разобрался, и как воркэраунд решил выстрелить по воробьям из нового соединения.
Так вот, уверяю тебя - это никогда не нужно.
 

AnrDaemon

Продвинутый новичок
Наверное. :)
Убедили. Ещё раз столкнусь с подобной проблемой - буду решать внимательнее.
 

WMix

герр M:)ller
Партнер клуба
Фанат, мне кажется, что ты постоянно ищешь границу между простым и сложным. все эти обертки, это описание сложного мира, для удобство тестирования, разделения по задачам и ленивости обьектов 'lazy'.

в мире frameworks, когда автоматически наинициализировалась куча обьектов за тебя, для твоего же удобства, чтоб можно было концентрироваться по максимуму на логике, вопрос ленивости очень актуален. то что существует обьект типа модель или даже resultset, еще не значит что нужно соеденяться с базой данных или отдавать запрос, до тех пор пока явно не обратишься по чтению записи. и если пытаться такое делать, то придется делить позадачно, или получишь кашу.

то что было написано, с целью обьяснить новичкам, правильно (безопасно) пользоваться базой данных, великолепно решает первую поставленную задачу. а вот цель которую ты приследуешь сейчас мне лично не совсем понятна.
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
Statement это только запрос, нормально он только собирается но не исполняется. исполняется Driver'ом.
Driver имеет "переменную с коннектом (mysqli resource) какая бы она не была". имеет публичный метод типа query который принимает Statement и возвращает ResultSet. а тот в свою очередь имеет методы типа getOne(). хотя можно опять кастрировать и сказать что getOne() принадлежит Driver'у. так грубо можно представить.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
WMix, откладывать запросы до момента обращения к данным не бывает надо обычно, только по связям в AR
а если Фанат выделит парсер кастомных типов - круто
 

WMix

герр M:)ller
Партнер клуба
PHP:
$user = new User;
$user->birthDate='12.12.2012';
echo $user->howOld();
 

Фанат

oncle terrible
Команда форума
то что было написано, с целью обьяснить новичкам, правильно (безопасно) пользоваться базой данных, великолепно решает первую поставленную задачу. а вот цель которую ты приследуешь сейчас мне лично не совсем понятна.
Интересный вопрос!
На самом деле, вся загвоздка в слове "великолепно" - на самом деле всё не так великолепно, как хоелось бы. Со временем я стал замечать недостатки, которые заложил в систему. И хочется их исправить.
По сути это чисто косметические правки. К примеру, невозможно передать параметры массивом. То есть, если, у нас переменное число параметров, то надо извращаться с call_user_func_array(). Мелочь, а неприятно. Вот такого рода правки я и хочу сделать - не принципиальные а чисто улучшение юзабилити
 

Фанат

oncle terrible
Команда форума
а если Фанат выделит парсер кастомных типов - круто
А можешь примерно рассказать, в каком виде он дожен хранить/взвращать результат?
Я тут начал делать класс парсер, но как-то глупо себя чувствую: у меня ведь используется preg_split, и в итоге получается массив, в котором по очереди плейсхолдеры и куски запроса между ними. Этот массив и воозвращать?
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
с точки зрения пользователя, в принципе, хватит такого API, когда я делаю объект, даю ему запрос с плейсхолдерами, в сеттер кладу данные, вызываю условный run(), и могу из getSql() забрать текст конечного запроса,
в идеале ему еще можно передать объект адаптера для конкретного драйвера базы

используется preg_split, и в итоге получается массив, в котором по очереди плейсхолдеры и куски запроса между ними. Этот массив и воозвращать?
этот массив нужен только компилятору sql
я бы сделал такую структуру:
[управляющий класс] создает [объект запроса], в который можно передать параметры.
[управляющий класс] получает команду выполнить и вызывает [класс-парсер] с preg_split.
Результат парсинга, массив твой, можно передать в [компилятор-адаптер] конкретного драйвера базы и получить [объект запроса].
Потом [управляющий класс] создает [исполнитель-адаптер], передает в него [объект запроса], исполняет, и возвращает объект результата PDOStatement / mysqli_result
 
Последнее редактирование:

Фанат

oncle terrible
Команда форума
в принципе, хватит такого интерфйейса, когда ты ему даешь запрос с плейсхолдерами, в сеттер кладешь данные, вызываешь условный run(), и можешь из getSql() забрать текст конечного запроса,
в идеале ему еще можно поставить адаптер конкретного драйвера базы
Ну то есть получается такой парсер-подстановщик.Вообще, очень здравая и логичная идея.

Но... Ты не представляешь, как мне хочется заимплементить поддержку нативных плейсхолдеров!
Чтобы было три режыма:
- эмуляция
- нативный режим, но с парсингом, для получения типов для bind_param.
- чисто нативный режим, когда запрос не парсится, а уходит в БД как есть

при этом предложенный тобой вариант годится для первого пункта, но не годится для второго... А в третьем он, понятное дело, не нужен
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
для 3го будет заглушка - что получила, то и вернет,

"парсинг для получения типов для bind_param" - можно, компилятор вместо строки sql будет возвращать объект запроса, в котором будут храниться типы плейсхолдеров и данные.
В случае с эмуляцией адаптер-исполнитель получит sql и выполнит его.
В случае pdo_bind_param он возьмет список параметров из объекта запроса и забайндит данные по типам.
 
Последнее редактирование:

Фанат

oncle terrible
Команда форума
с парсингом - это чтобы из
Код:
WHERE foo = ?i AND bar = ?s
получить запрос
Код:
WHERE foo = ? AND bar = ?
и строку 'is', чтобы скормить её в bind_param.
парсер вернет тебе типы плейсхолдеров, а компилятор сделает из них то, что нужно
ааааа, точно!
то есть да, класс-парсер может вернуть либо запрос с подставленными данными, либо с плейсхолдерами - что попросят.
логично. так и сделаю.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
я бы сделал единый api парсера, который всегда возвращает объект запроса, в объекте всегда есть sql, а данные с типами для байндинга в драйвер могут быть, а могут не быть
 

Фанат

oncle terrible
Команда форума
я бы сделал единый api парсера, который всегда возвращает объект запроса, в объекте всегда есть sql, а данные с типами для байндинга в драйвер могут быть, а могут не быть
Запрос, в теории, может быть тяжёлым.
Так что я бы сделал не всегда, а по запросу.
В конструкторе парсим,
при обращении к методу getSQL - рендерим.
при обращении getPrepared - возвращаем стейтмент
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Неважно какой запрос - в php копирование при изменении, "создаваться" будет только сам объект, а оверхед на создание объекта маленький.

при обращении к методу getSQL - рендерим.
если компилятор совмещен с парсером - не сделать адаптеро-зависимую генерацию sql, я бы делал в отдельном классе,
или ты про методы управляющего объекта, которые декорируют парсер с компилятором?
 

WMix

герр M:)ller
Партнер клуба
всеже чувствую тебе придется сделать 3 класса

и да простит меня Аллах
PHP:
interface Executor{
   public function execute($sql);
}

class Statement {
   public function __construct( $args ){
     $this->args = $args;
   }

   public function getRaw(){
     $query = '';
     $raw  = array_shift($this->args);
     $array = preg_split('~(\?[nsiuap])~u',$raw,null,PREG_SPLIT_DELIM_CAPTURE);
     $anum  = count($args);
     $pnum  = floor(count($array) / 2);
    // bla bla bla
     return $query;
   }
}


class SafeMyResult {

   private $result; // mysqli_result
   private $statement;
   private $executor;

   public function __construct( Statement $s, Executor $e ){
     $this->statement = $s;
     $this->executor = $e;
   }

   public function getOne(){
     $this->result = $this->executor->execute(
       $this->statement->getRaw()
     );
     // ...
     //mysqli_free_result($this->result);
    return "hallo";
   }
}

class SafeMySQL implements Executor{

   private $conn;

   public function query(){
     return new SafeMyResult(
       new Statement( func_get_args() ),
       $this
     );
   }

   public function execute( $sql ){
     return mysqli_query( $this->conn, $sql );
   }
}

$db = new SafeMySQL($opts);
$name = $db->query(
  'SELECT name FROM table WHERE id = ?i',
  $_GET['id']
)->getOne();
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
подумав :)
PHP:
class SafeMyResult implements Iterator ...{ }
class SafeMySQL implements Executor{

   private $conn;

   private function _result(Statement $s){
     return new SafeMyResult( $s, $this );
   }

   public function execute( $sql ){
     return mysqli_query( $this->conn, $sql );
   }

   public function getOne(){
     return $this->_result( new Statement( func_get_args() ) )->getOne();
   }

   public function getResult(){
     return $this->_result( new Statement( func_get_args() ) );
   }
}

$db = new SafeMySQL($opts);

// сохраним обратную совместимость
$name = $db->getOne('SELECT name FROM table WHERE id = ?i',$_GET['id']);

// и добавим отдельный результат
$name = $db->getResult('SELECT name FROM table WHERE id = ?i',$_GET['id'])->getOne();
 

fixxxer

К.О.
Партнер клуба
WMix, мне не нравится в таком подходе создание statement-а внутри, может, я захочу свой statement с кастомными плейсходерами.

Если уж на то пошло, то сам SafeMysql должен быть фабрикой-фасадом, с соединением с базой работает SafeMysqlConnection, который вообще тупой как PDO и умеет просто prepare-execute-fetch*, запрос в виде native placeholders + bindings строится в SafeMysqlStatement (хотя тут еще отдельно парсер можно выделить), SafeMysqlExecutor же выполняет через SafeMysqlConnection query+bindings из statement-а и возвращает SafeMysqlResult. :)
 

флоппик

promotor fidei
Команда форума
Партнер клуба
WMix, мне не нравится в таком подходе создание statement-а внутри, может, я захочу свой statement с кастомными плейсходерами.

Если уж на то пошло, то сам SafeMysql должен быть фабрикой-фасадом, с соединением с базой работает SafeMysqlConnection, который вообще тупой как PDO и умеет просто prepare-execute-fetch*, запрос в виде native placeholders + bindings строится в SafeMysqlStatement (хотя тут еще отдельно парсер можно выделить), SafeMysqlExecutor же выполняет через SafeMysqlConnection query+bindings из statement-а и возвращает SafeMysqlResult. :)
И тут внезапно оказывается, что в итоге написали PDO :)
 
Сверху