Все мы знаем, насколько неудобно работать с базами данных, вручную писать запросы, следить за безопасностью (sql-инъекции и тд). Мы решили исправить это, создав библиотеку. Описание: ModernPDO — это незаменимый инструмент для работы с базами данных. Отличительными особенностями данной библиотеки является простота использования, современный стандарт PHP (8.1), полное покрытие кода тестами, уровень PHPStan'а 9, уровень PSalm'а 1, а также единый стандарт кода. Доступный функционал: Поддержка CRUD действий. Поддержка сырых/готовых запросов. Поддержка транзакций. Поддерживаемые базы данных: MySQL/MariaDB PostgreSQL SQLite3 Ссылки: Репозиторий на GitHub Страница на Packagist
Описание обновления ModernPDO v3.0.0 Убраны create методы в ModernPDO и добавлены Drivers Убрана хардкод установка PDO атрибутов в ModernPDO Добавлена поддержка PDO исключений Переработаны билдеры запросов (добавлен класс Factory) Переписана установка кодировки подключения к MySQL/MariaDB Добавлена плашка code coverage Добавлен класс Escaper для экранирования значений Удалены файлы докера Переписаны интеграционные тесты (для упрощения добавления новых тестов) Переименованы методы Select'а all/one в rows/row Удалены методы Select'а firstBy/lastBy Добавлен трейт Limit для Delete/Select/Update Добавлен трейт OrderBy для Delete/Select/Update Добавлена поддержка joins в Select Добавлена возможность создавать/редактировать/удалять таблицы Добавлена поддержка функций(SUM, COUNT и тд) и условий(IN, BETWEEN и тд) Delete/Select/Update/Insert Добавлена поддержка ключей(Primary, Unique, Foreign) в create/alter table Новая версия тык
Удобная обвертка, которая запрещает использовать Easy интерфейс: Спойлер: Генераторы PHP: $statement = $lerma -> query( 'SELECT * ...' ); foreach ( $statement AS $row ) { // result $row } // OR iterator_to_array ( $statement ); Спойлер: MultiValues for prepare query PHP: $values = [ [ 'name1', 'group1', 'text1' ], [ 'name2', 'group2', 'text2' ], [ 'name3', 'group3', 'text3' ], ]; try { $lerma -> beginTransaction(); // uses rollBack if Exception $lerma -> prepare( [ 'INSERT INTO `%s`( `name`, `group`, `text` ) VALUES ( ?,?,? )', 'table' ], $values ); $lerma -> commit(); } catch ( \Nouvu\Database\Exception\LermaException ) { } // 2 ---------------- try { $lerma -> beginTransaction(); $lerma -> prepare( [ 'INSERT INTO `%s`( `name`, `group`, `text` ) VALUES ( ?,?,? )', 'table' ] ); foreach ( $values AS $row ) { $lerma -> execute( $row ); } $lerma -> commit(); echo $lerma -> InsertID(); // 3 } catch ( \Nouvu\Database\Exception\LermaException ) { $lerma -> rollBack(); } в доке: PHP: <?php /* Выполнение запроса с передачей ему массива параметров */ $sth = $dbh->prepare('SELECT name, colour, calories FROM fruit WHERE calories < ? AND colour = ?'); $sth->execute([150, 'red']); $red = $sth->fetchAll(); $sth->execute([175, 'yellow']); $yellow = $sth->fetchAll(); Спойлер: Использование напрямую prepare функцию У тебя же автоопределение метода запроса https://github.com/StulE-ru/ModernP...9084ddb91c979ec4df6adda/src/ModernPDO.php#L94 Я юзаю query чтобы юзать prepare. Ничего не напутано ? Спойлер: Прямой доступ к расширению(ям) PHP: $connect = $lerma -> connect() -> get(); В таком случае единственный правильный выбор падает на: PHP: $mpdo = new ModernPDO( pdo: $pdo, ); Нет возможности использовать setAttribute Нет возможности вывести get_last_insert_id. Что реально его нет, или я его пропустил ? Итого: Это обвертка запрещает использовать дальнейшие манипуляции после вызова query метода. Жесткий фасад Query builder Всё Если убрать Query builder с библиотеки, то в чем фишка тогда будет ? Дебага нет сконструированных запросов. --- Добавлено --- По опыту скажу: нет никакого смысла юзать queryBuilder, кроме как удобств, потому что, в проектах используют класс со статическим фасадом с запросами. Он обычно находится в Service/Database, либо Model/Database PHP: <?php declare ( strict_types = 1 ); namespace App\Service\Database; use Nouvu\Framework\Component\Database\StatementInterface; use function App\Foundation\Helpers\{ config, database }; class Alchemy { private const ASPECTS_TABLE = 'alchemy_aspects', COMPONENTS_TABLE = 'alchemy_components', POTIONS_TABLE = 'alchemy_potions'; public static function createTables(): void { database() -> query( [ ' CREATE TABLE IF NOT EXISTS %s( id INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL )', config( 'database.prefix' ) . self :: ASPECTS_TABLE ] ); database() -> query( [ ' CREATE TABLE IF NOT EXISTS %s( id INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, a1 TEXT NOT NULL, a2 TEXT NOT NULL, a3 TEXT NOT NULL, a4 TEXT NOT NULL )', config( 'database.prefix' ) . self :: COMPONENTS_TABLE ] ); database() -> query( [ ' CREATE TABLE IF NOT EXISTS %s( id INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, level INTEGER NOT NULL, min INTEGER NOT NULL, max INTEGER NOT NULL, a1 TEXT NOT NULL, a2 TEXT NOT NULL, a3 TEXT, a4 TEXT, a5 TEXT )', config( 'database.prefix' ) . self :: POTIONS_TABLE ] ); } public static function addAspect( string $name ): int { $a = database() -> prepare( [ 'INSERT INTO %s( name ) VALUES ( ? )', config( 'database.prefix' ) . self :: ASPECTS_TABLE ], [ $name ] ); return $a -> id(); } public static function findAspectByName( string $name ): ?object { return database() -> prepare ( [ 'SELECT * FROM %s WHERE name = ?', self :: ASPECTS_TABLE ], [ $name ] ) -> get( StatementInterface :: FETCH_OBJ ); } public static function findPotionById( int $id ): ?object { return database() -> query ( [ ' SELECT t1.id, t1.name, t1.level, t1.min, t1.max, t2.name AS a1, t3.name AS a2, t4.name AS a3, t5.name AS a4, t6.name AS a5 FROM %s AS t1 JOIN %2$s AS t2 ON t2.id = t1.a1 JOIN %2$s AS t3 ON t3.id = t1.a2 LEFT JOIN %2$s AS t4 ON t4.id = t1.a3 LEFT JOIN %2$s AS t5 ON t5.id = t1.a4 LEFT JOIN %2$s AS t6 ON t6.id = t1.a5 WHERE t1.id = %3$d ', self :: POTIONS_TABLE, self :: ASPECTS_TABLE, $id ] ) -> get( StatementInterface :: FETCH_OBJ ); } public static function addComponent( string $name, int ...$aspects ): int { $a = database() -> prepare( [ 'INSERT INTO %s( name, a1, a2, a3, a4 ) VALUES ( ?, ?, ?, ?, ? )', config( 'database.prefix' ) . self :: COMPONENTS_TABLE ], [ $name, ...$aspects ] ); return $a -> id(); } public static function addPotion( string $name, string $level, string $min, string $max, ?int ...$aspects ): int { $a = database() -> prepare( [ "INSERT INTO %s( 'name', 'level', 'min', 'max', 'a1', 'a2', 'a3', 'a4', 'a5' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? )", config( 'database.prefix' ) . self :: POTIONS_TABLE ], [ $name, ( int ) $level, ( int ) $min, ( int ) $max, ...$aspects ] ); return $a -> id(); } public static function viewPotions(): array { return database() -> query( [ ' SELECT t1.id, t1.name, t1.level, t1.min, t1.max, t2.name AS a1, t3.name AS a2, t4.name AS a3, t5.name AS a4, t6.name AS a5 FROM %s AS t1 JOIN %2$s AS t2 ON t2.id = t1.a1 JOIN %2$s AS t3 ON t3.id = t1.a2 LEFT JOIN %2$s AS t4 ON t4.id = t1.a3 LEFT JOIN %2$s AS t5 ON t5.id = t1.a4 LEFT JOIN %2$s AS t6 ON t6.id = t1.a5 ', self :: POTIONS_TABLE, self :: ASPECTS_TABLE ] ) -> all( StatementInterface :: FETCH_NUM ); } public static function viewAspects(): array { return database() -> query( 'SELECT * FROM ' . self :: ASPECTS_TABLE ) -> all( StatementInterface :: FETCH_NUM ); } public static function viewComponents(): array { return database() -> query( [ ' SELECT t1.id, t1.name, t2.name AS a1, t3.name AS a2, t4.name AS a3, t5.name AS a4 FROM %s AS t1 JOIN %2$s AS t2 ON t2.id = t1.a1 JOIN %2$s AS t3 ON t3.id = t1.a2 JOIN %2$s AS t4 ON t4.id = t1.a3 JOIN %2$s AS t5 ON t5.id = t1.a4 ', self :: COMPONENTS_TABLE, self :: ASPECTS_TABLE ] ) -> all( StatementInterface :: FETCH_NUM ); } public static function findAspectsByComponent( string $component ): ?array { return database() -> prepare( [ ' SELECT t2.name AS a1, t3.name AS a2, t4.name AS a3, t5.name AS a4 FROM %s AS t1 JOIN %2$s AS t2 ON t2.id = t1.a1 JOIN %2$s AS t3 ON t3.id = t1.a2 JOIN %2$s AS t4 ON t4.id = t1.a3 JOIN %2$s AS t5 ON t5.id = t1.a4 WHERE t1.name = ? ', self :: COMPONENTS_TABLE, self :: ASPECTS_TABLE ], [ $component ] ) -> get( StatementInterface :: FETCH_NUM ); } } Шаг и Мат. --- Добавлено --- Стооооп а где же Enum ?? PHP 8.1 же PHP: $lerma = Lerma :: create( driver: DriverEnum :: MySQLi ) -> setData( host: '127.0.0.1', username: 'root', password: 'root' ) -> setDatabaseName( dbname: 'dbtest' ) -> setCharset( charset: 'utf8' ) -> setPort( port: 3306 ) -> getLerma(); PHP: $lerma = Lerma :: create( driver: DriverEnum :: SQLite3 ) -> setFile( __DIR__ . '/dbtest.db' ) -> getLerma();
Не вижу смысла отвечать вам по каждому пункту, так как очевидно, что вы пытаетесь хоть кому-то втюхать Lerma (которая никому не нужна, ибо это мем). Но все же, если вдруг кому-то все таки потребуются эти возможности, то ModernPDO крайне гибкая библиотека, которая дает отличную возможность реализовать все его хотелки, используя отличную базу.
А причем тут сама Lerma ? Я тебе аргументирую что твой фасад, отделив от queryBuilder, не чем не лучше Простой обвертки Спойлер: Мем который умеет Хотя погоди, давай теперь по рекламирую все же Lerma работает не с PDO, а напрямую с Mysqli, SQLite. Эта библиотека например умеет: Именованные плейсхолдеры :name для Mysqli ... (без участия mysqli::execute_query) Генераторы Вывод результата с сервера без сохранения на клиентской части Кастомизация и работа напрямую с расширениями Автоматическое поднятие связи с сервером БД, если отвалился - Для ботоводства. Мульти-добавление значений INSERT`ом через астрал, вместо 100500 ( ?, ?, ?, ..... и т.д. ) Всё это делал не для продакшен. В каком месте гибкая ? В жестком фасаде, или конструктор query ? Уточняй уж.
Я не хотел и не хочу вам что-то доказывать, но как метко подметил мой друг, адекватные люди могут вам поверить. Так что поехали, вы сами этого захотели... > А причем тут сама Lerma ? При том, что примеры написаны на ней, а значит вы говорите, что она лучше, чем ModernPDO, что в корне неверно. > Я тебе аргументирую что твой фасад Не увидел в ваших нарциссических потугах аргументов. > отделив от queryBuilder, не чем не лучше Простой обвертки Резать либу пополам и чему-то удивляться... Но даже удалив все Actions и тд, это все равно будет лучше, чем ваша либа. > Мем который умеет Вы - большой молодец, продолжайте в том же духе нахваливать себя за накопленный в течение 5+ лет функционал, который никому кроме вас не нужен. А если серьезно, то это мем хотя бы потому, что вас просили написать тесты в 2017 году, на эту просьбу вы плевали с высокой колокольни, закончим тем, что репозиторий оформлен ужасно, код оформлен ужасно, адекватного тестирования до сих пор нет, при этом всем ваш код максимально не расширяемый, как мне, как пользователю, который вдруг юзает вашу либу, добавить поддержку других субд и еще много чего, что мне банально лень писать, так как вы все равно это исправлять не будете. > В каком месте гибкая ? В жестком фасаде, или конструктор query ? Уточняй уж. Понимаю, сложно понять в чем библиотека гибкая, особенно когда это написано в README Спойлер: README Пишите о том: где я неправ, что можно сделать лучше и тд. Не надо писать об отсутствии чего-то, что можно легко добавить.
Я не понял. Тебя смутило названия переменных или о том где стоял вопрос "Где Enum ? который был завезен в php 8.1" ?? и в качестве примера, привел реализацию со своего кода. У меня длиннее если что. А теперь перейдем к главному. Если про простую обвертку, то огорчу тебя. Она не моя, а от форумного сообщества PHPClub. И то что ты фасад по БД разбил на мелкие классы, то в скомпонованном виде и по функциональной части, она будет абсолютно одинакова с Простой обверткой, не более. Но кричать что твоя лучше и тем более в продакшен лепить - это как слепо-верующий фанатик, который только что открыл мир для себя, забыв что давно за тебя уже всё написали, в просторах Github выложено, действительно, уж более получше, по могучее будет инструмент, чем какой-то велосипед от какого-то Петьки. По функционалу и гибкости. Давай начнем по списку с Генераторов/Итераторов. В PDO есть уникальная способность как и в MySQLi, выводить результат запросов напрямую через итерацию циклов. Она хоть и не документированная, поэтому о ней мало кто знает. Знакомься: PDOStatement::getIterator самый быстрый способ по выводу. Пример: PHP: $stmt = $pdo -> prepare( 'SELECT * FROM table WHERE name = :name' ); $stmt -> execute( [ 'name' => $name ] ); foreach ( $stmt AS $result ) { // Iterations... } Видишь? я не использую fetch fetchAll. А у твоей библиотеки $statement переходит в твой фасад, где логика кода отсутствует по итераторному выводу, а именно тут. Т.е. не я, не вася не сможет использовать данный аспект. Но тут ты выходишь со своим аргументом, что якобы можно будет расширить/дополнить функционал. Отлично, попробуем ? Значит так, чтобы нативный $statement использовать, мне нужно переписать Statement.php, но его использует Factory.php его тоже переписывать, и пошло и поехало. Итого от и до переписывать придется. Петь, ты об интерфейсах слышал ? Если были бы у тебя типы данных с использованием интерфейсов, то все бы смогли решить половину вопросов. Выучи для чего паспорта такие. Так же и об подготовительных запросах. Пишу: PHP: $stmt = $pdo -> prepare( 'INSERT .... VALUES ( ?,?,? )' ); foreach ( $results AS $send ) { $stmt -> execute( $send ); } Здесь я смогу сколько угодно данных отослать в поток. Что у тебя ? будет запрос такого формата: PHP: $stmt = $pdo -> prepare( 'INSERT .... VALUES ( ?,?,? ), ( ?,?,? ), ( ?,?,? ), ( ?,?,? ), ( ?,?,? ), ( ?,?,? ), ( ?,?,? ),( ?,?,? ),( ?,?,? ),.....' ); Супер ? Пупер. Опять переписывать, делать форк и что остается ? Какой смысл использовать твою библиотеку ? Ради QueryBuilder ? Так их полным полно и на много лучше чем твой - это факт. ( Я не занимаюсь писькомерством ). Поэтому аргументирую, почему я называю это жестким фасадом. Начни воспринимать критику к своему говнокоду, а не как к личности, которая побежала по всем моим сообщениям, выяснять где и кому рекомендую(нет) Свой длинный инструмент с мемами. Мне поxуй до того момента, пока это кому-то станет действительно нужным. Начни с этого: PHP: public function getInstance(): \PDO { return $this -> pdo; }
Извините, что токсично ответил в предыдущих комментариях, мне тогда показалось, что вы хотите просто попиарить свой продукт и посраться. > Где Enum ? который был завезен в php 8.1 Не вижу смысла его использовать в моей либе, так как они накладывают ограничения на изменения, и без правки кода либы ничего расширить нельзя (добавить новый драйвер и тд). > Нет того и того в плане поддержки pdo. Да - много чего нет. На другом форуме мне уже объяснили, что либа в текущем варианте = query builder + простая обертка над pdo, при этом одно наслоилось на другое, в следующей версии исправлю. По факту вы говорите тоже самое, только из-за формы подачи я этого сразу не понял. Отвечаю на частые вопросы: Название ModernPDO потому, что так исторически сложилось, от PDO только название и самая простейшая обертка. Большой акцент идет на интерфейс к базам данных (в простонародье query builder). Почему этим должны пользоваться - я не знаю, я выложил это в open source потому, что сам использую в 2х проектах (другие варианты не подошли) Против критики я ничего не имею, наоборот, считаю крайне полезной. Планирую дальше развивать эту либу (явно разделить на PDO и query builder, добавлять поддержку новых бд и тд), и в будущем на основе этого сделать простую orm (мне несколько людей сказали, что это уже может кому-то реально помочь, а не упростить синтаксис)
Найди ошибку --- Добавлено --- Какое ограничение ? --- Добавлено --- Я писал об смысла использования любого query конструктора нет, кроме удобств. Т.к. это всё оформляется в статическом классе и ты сразу видишь что и почем, чем потом дебажить "что там получилось?". А это уже ни хрена не удобство.
> Найди ошибку Connect to? > Какое ограничение ? На безболезненное добавление новых бд и тп, с enum у вас прописан жесткий список в коде либы, а с использованием наследования и new MySQL/PostgreSQL и тд, ничего не мешает пользователю добавить class DB extends ModernPDO и тд + если есть список, то его нужно обрабатывать (иначе как получать объекты?) А это все в случае добавления придется менять, то есть помимо того, чтобы дописать новый(е) классы приходится менять код либы, а это не круто
В общем, код с интерфейсами ожидаю. Тогда можно будет об расширении говорить. https://www.php.net/manual/en/language.enumerations.methods.php --- Добавлено --- На Enum не давлю. Вопрос был из-за возможности в php 8.1
Спустя 2 месяца с воздержанной отметкой скачиваний библиотеки (428), я могу абсолютно заверить об самонабивания статистики для лживого показателя "интереса" программистов к данному репозиторию.
Вы правы лишь отчасти. Действительно, у программистов библиотека не вызвала какого-то интереса, но я никогда не преследовал цели показать "лживый интерес", так же как и не кичился этими скачиваниях. Скачивания берутся с packagist, который в свою очередь считает скачиванием любой composer install, поскольку я использовал библиотеку в нескольких проектов, которые стабильно обновлялись, соответственно установок было много, в какой-то момент проекты перестали обновляться, а значит и показатель замер на 428 скачиваниях.