Всем злое бу! В свободное время решил на этот раз написать библиотеку "Лерма" по работе с различными Бд. Сама библиотека пока не реализована со своей миграцией и составлений структур запроса, а лишь ее драйвера ( обвертки ). Сталкивался с некими нюансами в MySQLi, по решению которых не хватала практика. И все же за несколько дней ( план, составление на будущее привязки к Lerma, реализация ), завершил. Старался по мере возможностей использовать некие анспекты более лаконичнее, дабы не написать совсем тупой мусор. Спойлер: Драйвер MySQLi PHP: $arguments = array_values ( $arguments ); extract ( $arguments, EXTR_PREFIX_ALL, 'bind' ); $a = []; foreach ( $arguments AS $k => $arg ) { $a[] = &${ 'bind_' . $k }; } $arguments = array_merge ( [ implode ( '', $types ) ], $a ); $this -> statement -> bind_param( ...$arguments ); #call_user_func_array ( [ $this -> statement, 'bind_param' ], ); Это писец.. Самый большой затык предоставила функция bind_param, где нужно было создать переменные как ссылки. Почему закомментина call_user_func_array ? Поздновато спомнил об операторе ... и с ее возможностью не только принимать n кол-во аргументов, но и передавать их. В остальных случаях ( хорошо что документацию скачал - без инета кодил ), листал доки по работе с MySQLi. Познакомился с ее api о передаче небуфер данных. Есть несколько мест в написанном коде где сомневаюсь в правильном и качественном применении. Спойлер: Драйвер PDO Особого тут ничего нет, так как PDO ближе схоже с моейм задумкой по интеграции в мигрант Лермы. Данные драйвера находятся в тест стадии и будут еще переписываться, что добавляться, что изменяться в более верный подход. По стилю вывода информации пока выбраны стандартные NUM / ASSOC / BOTH / OBJ выводы. Для тестов создал файлик index.php в папке drivers PHP: <?php error_reporting ( E_ALL | E_STRICT ); $lerma = require ( dirname ( __FILE__, 3 ) . '/Interfaces/Lerma/IDrivers.php' ); $lerma = require ( dirname ( __FILE__, 3 ) . '/Configures/Lerma.php' ); $params = $lerma -> {$lerma -> driver}; $a = require $lerma -> driver . '.php'; $s = $a -> prepare( 'SELECT * FROM usraccount WHERE id = ?' ) -> execute( [ 5 ] ) -> fetch(); var_dump($s); /* PDO::FETCH_ASSOC 2 MYSQLI_NUM PDO::FETCH_NUM 3 MYSQLI_BOTH PDO::FETCH_BOTH 4 PDO::FETCH_OBJ 5 MYSQLI_ASSOC 1 pdo[ 1 => PDO::FETCH_NUM, 2 => PDO::FETCH_ASSOC, 3 => PDO::FETCH_BOTH, 4 => PDO::FETCH_OBJ ] MySQLi[ 1 => MYSQLI_NUM 2 => MYSQLI_ASSOC 3 => MYSQLI_BOTH 4 => 4 ] */ Прилагающий конфиг: PHP: <?php return new class { private const USER = 'root'; private const PASSWORD = ''; # Назначение драйвера для подключения базы данных public $driver = mysqli::class; # Параметры для драйвера mysqli public $mysqli = [ 'host' => '127.0.0.1', 'user' => self::USER, 'password' => self::PASSWORD, 'dbname' => 'single', 'port' => 3306 ]; # Параметры для драйвера PDO public $PDO = [ 'dns' => 'mysql:host=127.0.0.1;dbname=single;charset=utf8', 'user' => self::USER, 'password' => self::PASSWORD, 'options' => [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => FALSE, ] ]; }; В переменную driver заносится название подключаемого скрипта MySQLi / PDO. Особого нет.. конфиг как конфиг, но нет... Переменные для настроек расширений в драйверах должны идти четко последовательно как назначенны в массивах для каждого. Тобишь не допустим рандом, для примера: PHP: $mysqli = [ 'port' => 3306, 'host' => '127.0.0.1', 'password' => self::PASSWORD, 'dbname' => 'single', 'user' => self::USER ]; В итоге получаю то что хотелось: PHP: <?php # Обычный запрос $ca = $a -> query( 'SELECT * FROM usraccount WHERE id BETWEEN 5 AND 25' ); if ( $ca -> rowCount() > 0 ) { var_dump ( 'query', $ca -> fetch( 4 ), '----' ); # или -> fetchAll( 4 ) } $ca = $a -> prepare( 'SELECT * FROM usraccount WHERE id IN ( ?,?,? )' ) -> execute( [ 4, 27, 666 ] ); if ( $ca -> rowCount() > 0 ) { var_dump ( 'prepare', $ca -> fetch( 4 ), '----' ); # или -> fetchAll( 4 ) } Жду критики --- Добавлено --- Забыл https://github.com/MouseZver/My-garbage-code/tree/master/Lerma
не знаю как кому)) но мне бы хотелось что бы в оператор IN можно было сразу массив отправлять) что то типа такого)) PHP: $smcFunc['db_query']('', ' DELETE FROM {db_prefix}themes WHERE variable = {string:current_column} AND value NOT IN ({array_string:new_option_values}) AND id_member > {int:no_member}', array( 'no_member' => 0, 'new_option_values' => $newOptions, 'current_column' => $context['field']['colname'], ) );
Это сама Лерма будет делать PHP: Lerma::select( [ 4, 27, 666 ], function ( Placebo $Placebo ) { $Placebo -> column( ... ); $Placebo -> table( ... ); $Placebo -> where( придумать ) -> in( [ 1,2,3 ] ) ... ; $Placebo -> order( 'DESC' ); } );
Это не драйвер, а бибилиотека-обертка. Драйверы - это про другое. --- Добавлено --- А если в where нужны подзапросы? А если нужно бабахнуть что-то Спойлер: ЭДАКОЕ ?
ИМХО)) лучше сделать несколько простых запросов - чем один вложенный)) а так то мне больше нравится тип записи такой как у меня в примере)) чем вот такой как в AR Yii2))
Это не всегда возможно. И не всегда правильно. Если бы я эту дуру растащил на пачку запросов поменьше и жонглировал этим всем руками в оперативке, а не отдал на совсеть MySQL, то оно бы выполнялось раз в 10 дольше и жрало бы соответственно.
Я не просто так про это спросил. Это камень предкновения для кверибилдеров. SQL - это язык. Нельзя его просто взять и упихнуть в пару команд, к сожалению. В итоге ты либо ограничиваешь пользователя, либо изобретаешь собственный SQL, где вместо операторов будут команды твои, что нифига не упрощает, либо просто забьешь на кверибилдинг и будешь принимать чистые запросы под бинды. Ты загляни в этот ужас, что я скинул. Там разве что каким-то чудом нет джоинов. В остальном там куча IF-ов, работы с датами и один EXISTS. Это тоже все придется реализовывать. В итоге у тебя запросы через кверибилдер будут больше и сложнее, чем чистые. Тут вся логика в том, что человек, не знающий тот же MySQL и с кверибилдером хрен что сделает, потому как все равно полностью от особенностей и логики конкретной БД ты не абстрагируешься. А человеку знающему MySQL проще долбануть запрос "как есть". Хотя бы по причине того, что чистые запросы разрабатывать можно прямо в IDE, подцепленной к БД, в рилтайме, я вот скрин из Штормов скинул. Там это безумно удобно делать. А мне теперь осталось только скопипастить эту дуру в проект. В случае же с кверибилдерами надо еще будет день голову ломать, как этот запрос на кверибилдер переписать.И уповать на то, что он правильно "скомпилируется".
видел и по хуже когда листал темы про JOINы Будет моя проблема, есть над чем голову поломать --- Добавлено --- + и тогда не будет нужды в миграции. В итоге получаем скукоту на несколько сотен строк - весь проект
Ды JOIN-ы это не сложно. С ними один раз плотно поработать и они становятся естественны и прозрачны. Это только поначалу они голову ломают. В мускуле есть вещи поприкольнее. Например case ... when. Или те же IF-ы, которых несколько видов и у каждого своя логика. И я о тех конструкциях, что используются в запросах, а не в хранимых процедурах. Ну дык KISS же. Чем проще, тем лучше. Хз, у меня в проекте тоже самописная либа для работы с БД вертится. Ее интерфейс сводится к передаче ей запроса, флага транзакции(нужна или нет), флага, отвечающего за вид возвращенного результата, имени свойства, которое нужно использовать вместо номеров строк, если, конечно это нужно. Порой пригождается. Из этого всего обязательный параметр только запрос. Работа с ней выглядит так: PHP: $data = API::send_query($query)['result'] ?? []; усе. В переменной $data будет лежать результирующий массив. Можно убрать "['result']" и получить от функции ответ, содержаний как результат выборки/ошибку, так и пачку служебных данных, типа последнего id, если юзался insert, сведений об обработке запроса и по мелочи.
Сорри за оффтоп, но вспомнил недавний коммент: и подумал, как бы сам @MouseZver ответил? Наверное, что-то вроде: "Образцовый говнокод! Немного договнокодить и можно на чемпионат по говнокоду отправлять"
Да ну. Никто же не заставляет всё делать через кверибилдер. Что удобно через него - делаешь через него. Что удобно напрямую - делаешь напрямую. Я уже писал как-то, для меня главное достоинство Query Builder, что его можно через пол системы прогнать, до того, как формируется окончательный запрос. Я это использую для динамического навешивания комбинаций фильтров и прочего. Естественно, чтоб пользоваться кверибилдером, знать SQL необходимо - об этом даже речи не ведётся.
К слову, сам по себе query builder имеет смысла разве что как удобная обертка, т.к. стандартные пыховские интерфейсы слишком многословны. По настоящему удобно становится тогда, когда это все обвязано и интегрировано в фреймворк. Когда при изменении записи ты можешь сериализовать модель и отправить её по средствам соккетов на фронт, где все изменения тут же отобразятся. Когда на определенные события ты можешь определенным образом отреагировать, к примеру отправив уведомление или письмо на создание / изменение чего-то там. Ну или логировать все изменения нужной модели. Короче, когда модель дает каркас для построения приложения вокруг данных.
MySQLi PHP: <?php class Lerma { private static $instance, $mysqli = [ 'host' => '127.0.0.1', 'user' => 'root', 'password' => '', 'dbname' => 'single', 'port' => 3306 ]; protected static function instance() { if ( self::$instance === NULL ) { self::$instance = self::driver(); } return self::$instance; } public static function __callStatic( $method, $args ) { return self::instance() -> $method( ...$args ); } protected static function driver() { return new class ( self::$mysqli ) { private $connect; private $statement; private $result; public function __construct ( array $params = [] ) { if ( empty ( $params ) ) { throw new Exception( 'Params expects most parameter values, returned empty' ); } $params = array_values ( $params ); $this -> connect = new mysqli( ...$params ); $this -> connect -> set_charset( 'utf8' ); if ( $this -> connect -> connect_error ) { throw new Exception( 'Error connect (' . $this -> connect -> connect_errno . ') ' . $this -> connect -> connect_error ); } } protected function error( $obj ) { if ( $obj -> errno ) { throw new Exception( $obj -> error ); } } protected function result() { if ( is_a ( $this -> statement, mysqli_stmt::class ) ) { return $this -> result; } return $this -> statement; } public function query( string $sql ) { $this -> statement = $this -> connect -> query( $sql ); $this -> error( $this -> connect ); return $this; } public function prepare( string $sql ) { $this -> statement = $this -> connect -> prepare( $sql ); $this -> error( $this -> connect ); return $this; } public function execute( array $arguments ) { if ( !is_a ( $this -> statement, mysqli_stmt::class ) ) { throw new Exception( 'Not execute' ); } $types = array_map ( function ( $val ) { if ( !in_array ( $type = gettype ( $val ), [ 'integer', 'double', 'string' ] ) ) { throw new Exception( 'Invalid type ' . $type ); } return $type{0}; }, $arguments ); $arguments = array_values ( $arguments ); extract ( $arguments, EXTR_PREFIX_ALL, 'bind' ); $a = []; foreach ( $arguments AS $k => $arg ) { $a[] = &${ 'bind_' . $k }; } $arguments = array_merge ( [ implode ( '', $types ) ], $a ); $this -> statement -> bind_param( ...$arguments ); $bool = $this -> statement -> execute(); $this -> result = $this -> statement -> get_result(); $this -> error( $this -> statement ); return ( $this -> result === FALSE ? $bool : $this ); } public function fetch( int $fetch_style = 3 ) { switch ( $fetch_style ) { case 1: return $this -> result() -> fetch_array( MYSQLI_NUM ); break; case 2: return $this -> result() -> fetch_array( MYSQLI_ASSOC ); break; case 3: return $this -> result() -> fetch_array( MYSQLI_BOTH ); break; case 4: return $this -> result() -> fetch_object(); break; default: throw new Exception( 'Invalid fetch_style ' . $fetch_style . ' is not switch' ); } } public function fetchAll( int $fetch_style = 3 ): array { switch ( $fetch_style ) { case 1: return $this -> result() -> fetch_all( MYSQLI_NUM ); break; case 2: return $this -> result() -> fetch_all( MYSQLI_ASSOC ); break; case 3: return $this -> result() -> fetch_all( MYSQLI_BOTH ); break; case 4: $all = []; while ( $res = $this -> fetch( $fetch_style ) ) { $all[] = $res; } return $all; break; default: throw new Exception( 'Invalid fetch_style ' . $fetch_style . ' is not switch' ); } } public function fetchColumn() { return $this -> result() -> fetch_array( MYSQLI_NUM )[0]; } public function rowCount(): int { return $this -> result() -> num_rows; } public function InsertId(): int { return $this -> result() -> insert_id; } public function __call( $method, $arguments ) { return $this -> statement -> $method( ...$arguments ); } }; } } не тестил, побыстрому накидал
Обновлюшка... PDO deprecate Драйвер сломал лицо с его дальнейшей поддержкой. То бишь самая большая обвертка которая в неких местах тупит. Также снесло ураганом константу FETCH_BOTH и доставило новые вкусняшки FETCH_BIND FETCH_COLUMN Итог: PHP: <?php error_reporting ( E_ALL ); use Aero\Database\Lerma AS Lerma; spl_autoload_register ( function ( $name ) { $LermaSpace = [ Aero\Configures\Lerma::class => 'Configures\Lerma', Aero\Database\Lerma::class => 'Database\Lerma', Aero\Database\Migrate::class => 'Database\Migrate', Aero\Interfaces\LermaDrivers::class => 'Interfaces\Lerma\IDrivers' ]; include strtr ( ( $LermaSpace[$name] ?? $name ), '\\', DIRECTORY_SEPARATOR ) . '.php'; } ); /* Мульти заправка таблицы данными с n - строк [ [ ], - 1 [ ], - 2 ... - n ] */ Lerma::prepare( 'INSERT INTO lerma ( name ) VALUES ( ? )', [[ 'aaaa' ], [ 'dgdf' ], [ 'awefaw' ], [ 'dszvdszfsf' ], [ 'd' ], [ 'rrrrr' ]] ); # Познаем крайний идентификатор echo Lerma::InsertID() . PHP_EOL; # пакуем весь результат, запрашивая все айдишки с возвратом числовым индексом $ids = Lerma::query( 'SELECT id FROM lerma' ) -> fetchAll( Lerma::FETCH_NUM ); # Можно и фантазией пошалить $r = Lerma::prepare( [ 'SELECT name FROM %s WHERE id BETWEEN ? AND ?', 'lerma' ], [ 10,20 ] ); #echo $r -> fetchColumn(); /* Господа: прошлый запрос завис на сервере. В результате новенького запроса, старенький уйдет на покой автоматом. Биндим результат напрямую с сервера */ $lerma = Lerma::prepare( [ 'SELECT CONCAT ( id, " %2$X ", name ) FROM %s WHERE id BETWEEN ? AND ?', 'lerma' , 666 ], [ 1,12 ] ); while ( $concat = $lerma -> fetch( Lerma::FETCH_BIND | Lerma::FETCH_COLUMN ) ) { /* ..алилуя.. */ echo $concat . PHP_EOL; } $a = Lerma::query( 'SELECT id FROM lerma LIMIT 5' ) -> fetchAll( Lerma::FETCH_COLUMN ); $b = Lerma::query( 'SELECT id FROM lerma LIMIT 5' ) -> fetchAll( Lerma::FETCH_NUM ); var_dump($a,$b);
@MouseZver респектище за проделанную работу в последнее время сравниваю всё с ZF PHP: $s = $a->query('SELECT * FROM usraccount WHERE id = ?', [5])->toArray();
Новая присыпка константами! Расфасовка... fetch( int $style, $argument = null ) FETCH_NUM - нумерованные индексы FETCH_ASSOC - ассоциативные индексы FETCH_OBJ - дурной класс содержащий результат строки FETCH_BIND - связь с астралом FETCH_BIND | FETCH_COLUMN - связь с астралом и вывод результата с первой колонки. Ругается (ниже). FETCH_COLUMN - выводит содержимое первой колонки. Ругается матом, если в запросе несколько колонок запрошено. FETCH_KEY_PAIR - выводит массив содержащий [ column1 => column2 ]. Так же ругается, если в запросе не две колонки. FETCH_FUNC - Бабкины семечки. Результат помещается в ваш function тесто, где происходит желаемая обработка данных. fetchAll( int $style, $argument = null ) FETCH_NUM - ... FETCH_ASSOC - ... FETCH_OBJ - ... FETCH_COLUMN - ... FETCH_KEY_PAIR - ... FETCH_KEY_PAIR | FETCH_NAMED - Спасает перезапись одного и того же индекса. Создает массив в индексе и помещает значения у которых индексы ( column1 ) совпали. FETCH_UNIQUE - http://phpfaq.ru/pdo/fetch#FETCH_UNIQUE FETCH_GROUP - http://phpfaq.ru/pdo/fetch#FETCH_GROUP FETCH_GROUP | FETCH_COLUMN - Содержит первую колонку + группировка. FETCH_FUNC - Ванга. Дано в таблице: id - name - num PHP: [ 1 => [ 'Aero', 111 ], 2 => [ 'Lerma', 111 ], 3 => [ 'Migrate', 111 ], 4 => [ 'Database', 111 ], 5 => [ 'Configures', 222 ], 6 => [ 'Interfaces', 333 ], 7 => [ 'LermaDrivers', 333 ], ] Примеры fetchAll: PHP: print_r ( Lerma::query( 'SELECT name FROM lerma' ) -> fetchAll( Lerma::FETCH_COLUMN ) ); Array ( [0] => Aero [1] => Lerma [2] => Migrate [3] => Database [4] => Configures [5] => Interfaces [6] => LermaDrivers ) -------------- PHP: print_r ( Lerma::query( 'SELECT num, name FROM lerma' ) -> fetchAll( Lerma::FETCH_KEY_PAIR ) ); Array ( [111] => Database [222] => Configures [333] => LermaDrivers ) -------------- PHP: print_r ( Lerma::query( 'SELECT num, name FROM lerma' ) -> fetchAll( Lerma::FETCH_KEY_PAIR | Lerma::FETCH_NAMED ) ); Array ( [111] => Array ( [0] => Aero [1] => Lerma [2] => Migrate [3] => Database ) [222] => Configures [333] => Array ( [0] => Interfaces [1] => LermaDrivers ) ) -------------- PHP: print_r ( Lerma::query( 'SELECT * FROM lerma' ) -> fetchAll( Lerma::FETCH_UNIQUE ) ); Array ( [1] => Array ( [0] => Aero [1] => 111 ) [2] => Array ( [0] => Lerma [1] => 111 ) [3] => Array ( [0] => Migrate [1] => 111 ) [4] => Array ( [0] => Database [1] => 111 ) [5] => Array ( [0] => Configures [1] => 222 ) [6] => Array ( [0] => Interfaces [1] => 333 ) [7] => Array ( [0] => LermaDrivers [1] => 333 ) ) -------------- PHP: print_r ( Lerma::query( 'SELECT num, name FROM lerma' ) -> fetchAll( Lerma::FETCH_GROUP ) ); Array ( [111] => Array ( [0] => Array ( [name] => Aero ) [1] => Array ( [name] => Lerma ) [2] => Array ( [name] => Migrate ) [3] => Array ( [name] => Database ) ) [222] => Array ( [0] => Array ( [name] => Configures ) ) [333] => Array ( [0] => Array ( [name] => Interfaces ) [1] => Array ( [name] => LermaDrivers ) ) ) -------------- PHP: print_r ( Lerma::query( 'SELECT num, name FROM lerma' ) -> fetchAll( Lerma::FETCH_GROUP | Lerma::FETCH_COLUMN ) ); Array ( [111] => Array ( [0] => Aero [1] => Lerma [2] => Migrate [3] => Database ) [222] => Array ( [0] => Configures ) [333] => Array ( [0] => Interfaces [1] => LermaDrivers ) ) -------------- PHP: print_r ( Lerma::query( 'SELECT num, name FROM lerma' ) -> fetchAll( Lerma::FETCH_FUNC, function ( $a,$b ) { return [ ( $a / 390 ), strtoupper ( $b ) ]; } ) ); Array ( [0] => Array ( [0] => 0.28461538461538 [1] => AERO ) [1] => Array ( [0] => 0.28461538461538 [1] => LERMA ) [2] => Array ( [0] => 0.28461538461538 [1] => MIGRATE ) [3] => Array ( [0] => 0.28461538461538 [1] => DATABASE ) [4] => Array ( [0] => 0.56923076923077 [1] => CONFIGURES ) [5] => Array ( [0] => 0.85384615384615 [1] => INTERFACES ) [6] => Array ( [0] => 0.85384615384615 [1] => LERMADRIVERS ) )
Некие правки... - Исправлена функциональность константы FETCH_UNIQUE Собирала в массив с нумерованными индексами остальные колонки, теперь индексы ассоциативные. PHP: ( [1] => Array ( [name] => Aero [num] => 111 ) /* ... */ ) - Удален метод fetchColumn, так как есть аналог fetch( Lerma::FETCH_COLUMN ) Добавлены следующие константы: FETCH_CLASS - записывает данные в класс, после запускается конструктор. Имя класса задается в fetch|all во втором аргументе. FETCH_CLASSTYPE - тоже самое, но название класса берется с первой колонки результата. примеры потом... в школу опаздываю(с)
Обновляем кое что... PHP: Lerma::query( [ 'UPDATE lerma SET name = "%s" WHERE id = 1', quotemeta ( Aero\test\Aero::class ) ] ); Создаем наш тестовый класс... с наследовательностью. Aero.php PHP: <?php namespace Aero\test; class Bar { public function __construct() { $this -> num = $this -> num + 3; } } class Aero extends Bar { protected $id; public function __set( $name, $value ) { $this -> $name = $value; } } --- Добавлено --- Присваивает результат свойствам заданного класса, после запускает конструктор. PHP: print_r ( Lerma::query( 'SELECT name, id, num FROM lerma LIMIT 3' ) -> fetchAll( Lerma::FETCH_CLASS, Aero\test\Aero::class ) ); Код (Text): Array ( [0] => Aero\test\Aero Object ( [id:protected] => 1 [name] => Aero\test\Aero [num] => 114 ) [1] => Aero\test\Aero Object ( [id:protected] => 2 [name] => Lerma [num] => 114 ) [2] => Aero\test\Aero Object ( [id:protected] => 3 [name] => Migrate [num] => 114 ) ) --- Добавлено --- Так же но, наименование класса берется с первой колонки. С остальных присваивает свойствам результат. PHP: print_r ( Lerma::query( 'SELECT name, id, num FROM lerma LIMIT 1' ) -> fetchAll( Lerma::FETCH_CLASSTYPE ) ); Код (Text): Array ( [0] => Aero\test\Aero Object ( [id:protected] => 1 [num] => 114 ) ) --- Добавлено --- Добавление Юникью константы приводит еще к записи наименования класса в индекс результата PHP: print_r ( Lerma::query( 'SELECT name, id, num FROM lerma LIMIT 1' ) -> fetchAll( Lerma::FETCH_CLASSTYPE | Lerma::FETCH_UNIQUE ) ); Код (Text): Array ( [Aero\test\Aero] => Aero\test\Aero Object ( [id:protected] => 1 [num] => 114 ) ) --- Добавлено --- Если задать второй параметр true, в индекс присвоиться лишь само имя класса Код (Text): Array ( [Aero] => Aero\test\Aero Object ( [id:protected] => 1 [num] => 114 ) )