Думаю в жизни каждого веб-разработчика наступает момент, когда хочется сделать что-то свое, внести вклад в развитие своей отрасли.
Такой момент настал у меня. Спустя 3,5 года программирования на PHP я решил сделать что-то полезное для других разработчиков.
Для новичков
Работа с базой данных — это неотъемлемая часть back-end разработки и, дабы облегчить нашу не легкую участь, существуют ORM-ки. Наиболее известным является XCRUD, RedBeanPHP и прочие.
По сути — это просто кусок кода, который позволяет вам вместо длинных SQL запросов использовать простые и интуитивно понятные команды.
Небольшой экскурс в ближайшее будущее.
Сейчас мы работаем с БД(через PDO) так:
<?php
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
$pdo = new PDO("mysql:host=localhost;dbname=".DB,User,Pass,$opt);// установили соединение с БД
$pdo->exec("SET CHARSET utf8"); // установили кодировку
$query = $pdo->prepare("INSERT INTO `users` (`name`,`email`) VALUES (:username,:email)"); // сформировали запрос
$arr_to_execute = ['name'=>'username','email'=>'email'];// сформировали данные для экранирования
$insert = $query->execute($arr_to_execute); // выполнили запрос
А к концу статьи будем работать так:
<?php
$RyF = new RyF();
$RyF->Insert('users')
->values(['name'->'userName','email'->'email'])
->execute();
Думаю профит очевиден.
Что ж, пора заканчивать с введением и приступать к коду. Мы ведь собрались ради него.
Общая задумка
Что такое ORM? Зачем нам это?
Это кусок кода, который выполняет работу с БД. Более того, в этом куске кода заранее происходит обработка входящих данных, что позволит меньше заботиться о безопасности.
Приступаем к реализации
Какие у вас возникают ассоциации после описания задумки? У меня в голове сразу же возникает понимание, что мы будем работать с ООП. А еще мы будем работать с PDO. Ибо возможностью экранировать запросы пренебрегать не стоит.
То есть у нас будет класс, который устанавливает соединение с базой данных. А для выполнения операций над БД мы создадим свои методы.
Еще нам нужен файл конфига, где будут храниться данные для установления соединения с БД
Окей, реализуем.
<?php
define('DB','bd_name');// Название БД
define('User', 'root');// Имя пользователя
define('Pass', '');// Пароль пользователя
<?php
include_once('config.php');
class RyF{
protected $pdo;
function __construct(){
$opt = [// Базовые настройки работы PDO
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Формат ошибок
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC // Устанавливаем режим возвращения данных в массиве
];
$this->pdo = new PDO("mysql:host=localhost;dbname=".DB,User,Pass,); // Устанавливаем соединение с БД
$this->pdo->exec("SET CHARSET utf8"); // Устанавливаем кодировку
}
}
Хорошо, продолжим.
Мы умеем устанавливать соединение с БД, теперь нужно научиться совершать 4 базовые операции CRUD(Create, Read, Update и Delete).
Получение записей
Начнем с простого. Создадим метод для получения записей из БД.
Слегка модифицируем код.
Необходимо добавить поле класса, которое будет содержать сам запрос и было бы неплохо создать несколько методов, которые будут этот запрос обрабатывать. А именно — select, execute.
Как будет выглядеть запрос:
$RyF->Select('table')
->execute();
class RyF{
protected $pdo;
private $sql_query; // Сам запрос
function __construct(){
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
$this->pdo = new PDO("mysql:host=localhost;dbname=".DB,User,Pass,$opt);
$this->pdo->exec("SET CHARSET utf8");
}
public function Select($table){ // Реализовываем метод для получения данных из БД
$this->sql_query = "SELECT * FROM `$table` "; // Начинаем формировать строку запроса
return $this; // Небольшая фишка
}
public function execute(){ // Метод выполнения запроса
$q = $this->pdo->prepare($this->sql_query); // Подготавливаем запрос для выполнения
$q->execute(); // Выполняем запрос
if($q->errorCode() != PDO::ERR_NONE){ // Обрабатываем случай ошибки
$info = $q->errorInfo();
die($info[2]); // Если при выполнении запроса произошла ошибка, то скажем об этом
}
return $q->fetchall(); // Возвращаем массив данных
}
}
При вызове метода Select('table') мы начинаем формировать строку запроса. После этой команды она имеет вид
SELECT * FROM `table`
Потом выполняем этот запрос в методе execute.
Пока что все просто.
А при таком виде записи мы возвращаем из метода сам объект.
Что нам это дает?
Возвращая сам объект после отработки метода, мы получаем возможность использовать этот объект повторно. А значит выполнять цепочки вызовов.
То есть, когда выполняется код $RyF->Select('table') у нас возвращается сам $RyF и мы можем продолжить вызывать методы сколько нашей душе угодно.
Ограничиваем получение записей
Повышаем градус.
Теперь мы хотим добавить функциональности и получать только определенные записи. В SQL для этого используется оператор Where.
Окей, сделаем это.
<?php
class RyF{
protected $pdo;
private $sql_query;
private $values_for_exec; // Массив значений для экранирования
function __construct(){
$this->sql_query = "";
$this->values_for_exec = array();
$this->pdo = new PDO("mysql:host=localhost;dbname=".DB,User,Pass,$opt);
$this->pdo->exec("SET CHARSET utf8");
}
...
public function where($where, $op = '='){ // Метод для обработки условия выборки
$vals = array(); // Массив значений, которые будут "подготовленными"
foreach($where as $k => $v){ // Превращаем строку в массив подготовленных значений
$vals[] = "`$k` $op :$k"; // Формируем строку, добавляя операцию
$this->values_for_exec[":".$k] = $v; // Заполняем массив полученными значениями
}
$str = implode(' AND ',$vals);
$this->sql_query .= " WHERE " . $str; // Модифицируем наш запрос
return $this;
}
...
public function execute(){
$q = $this->pdo->prepare($this->sql_query);
$q->execute($this->values_for_exec);
if($q->errorCode() != PDO::ERR_NONE){
$info = $q->errorInfo();
die($info[2]);
}
return $q->fetchall();
}
Здесь происходит дополнительная обработка запроса. То есть экранирование.
Пожалуй, это самая сложная часть всего проекта. Осознав ее работу, вы точно поймете что и как устроено.
Как выглядит наш запрос сейчас:
<?php
$RyF->Select('users')
->where(['name' => '= tester'])
->execute();
Данные в метод приходят в виде ассоциативного массива. Перебирая этот массив, мы модифицируем его так, чтобы можно было передать в «execute» для корректного экранирования.
Проще говоря, мы превращаем ['name' => 'username'] в [':name' => 'username'], а так же дописываем в запрос — «WHERE `name`=:name».
Теперь у нас возникает другая проблема — после выполнения одного запроса к БД мы не можем выполнить другой, потому что наши данные все еще хранятся в полях класса.
Фиксим это отдельным методом, который будет сбрасывать значения полей класса.
...
private function set_default(){
$this->sql_query = ""; // Сбрасываем строку запроса
$this->values_for_exec = array(); // Сбрасываем массив значений для экранирования
}
...
Гуд, теперь наш код работает адекватно.
Добавление записей
Продолжаем повышать градус.
Следующим шагом будет добавление записей в БД.
Теперь наша задача проделать ту же самую операцию, что и с методом where, для массива с данными для добавления записей.
...
public function Insert($table){
$this->sql_query = "INSERT INTO `$table` ";
return $this;
}
...
public function values($arr_val){
$cols = array(); // Создаем отдельный массив для колонок таблицы
$masks = array(); // Создаем отдельный массив для плейсхолдеров на место значений в запросе
foreach($arr_val as $k => $v){ // Применяем те же операции, что и для where
$value_key = explode(' ', $k);
$value_key = $value_key[0];
$cols[] = "`$value_key`"; // Собираем отдельно ключи
$masks[] = ':'.$value_key; // Собираем отдельно плэйсхолдеры для значений
$this->values_for_exec[":$value_key"] = $v; // Заполняем массив для экранирования корректными значениями
}
$cols_all = implode(',',$cols);
$masks_all = implode(',',$masks);
$this->sql_query .= "($cols_all) VALUES ($masks_all)"; // Превращаем полученные данные в корректный запрос
return $this;
}
...
А теперь осознаем что только что произошло.
К нам пришел запрос вида:
$RyF->Insert('users')
->values(['name'=>'username','email'=>'email'])
->execute();
Сначала мы превратили массив ['name'=>'username','email'=>'email'] в [':name'=>'username',':email'=>'email']. А за одно сложили отдельно ключи(названия колонок) и отдельно значения(плэйсхолдеры). То есть в одном массиве у нас находятся "`name`" и "`email`", а в другом ":name" и ":email". Затем мы превращаем эти массивы в строки, соединяя символом запятой.
Теперь используем это.
Помните вид корректного SQL запроса для добавления записей?
Он выглядит так:
INSERT INTO `table` (fileds) VALUES (values)"
И мы просто подставляем на место «fields» мы подставим ключи — "`name`, `email`", а вместо «values» подставим ":name, :email".
Ну и в execute мы уже настроили корректную обработку, передавая массив в функцию экранирования от PDO.
Добавим Update
Теперь научим нашу ORM обновлять записи.
Руководствоваться будем той же логикой, что и при добавлении. Ибо операции весьма схожи.
Разница будет лишь в формировании строки запроса. Так что наш метод values будет выглядеть так:
class RyF{
...
private $type;
public function Insert($table){
$this->sql_query = "INSERT INTO `$table` ";
$this->type = 'insert'; // Добавляем тип запроса
return $this;
}
public function Update($table){
$this->sql_query = "UPDATE `$table` ";
$this->type = 'update'; // Добавляем тип запроса
return $this;
}
public function values($arr_val){
$cols = array();
$masks = array();
$val_for_update = array(); // Отдельный массив для формирования строки обновления записей
foreach($arr_val as $k => $v){
$value_key = explode(' ', $k);
$value_key = $value_key[0];
$cols[] = "`$value_key`";
$masks[] = ':'.$value_key;
$val_for_update[] = "`$value_key`=:$value_key";
$this->values_for_exec[":$value_key"] = $v;
}
if($this->type == "insert"){ // Разделяем формирование строк запроса
$cols_all = implode(',',$cols);
$masks_all = implode(',',$masks);
$this->sql_query .= "($cols_all) VALUES ($masks_all)";
}else if($this->type == 'update'){
$this->sql_query .= "SET ";
$this->sql_query .= implode(',',$val_for_update);
}
return $this;
}
...
private function set_default(){
$this->type = ""; // Сбрасываем type после вы//запроса
...
}
...
}
Вы наверняка заметили, что у нас появилось дополнительное поле класса «type». Это самый простой способ разделять способы формирования строк запроса.
Ведь SQL запрос для обновления записи выглядит так:
UPDATE `table` SET `name`=':name',`email`=':email'
Вот мы и превращаем наши «сырые» данные в угодные PDO, просто отдельно в цикле формируя массив должным образом.
Добиваем последнюю важную операцию — Delete
Что ж, мы реализовали 3 операции и 4 базовых. Delete — достаточно простая операция, поэтому не вижу особого смысла расписывать.
Он выглядит так:
...
public function Delete($table){ // Метод для удаления записей из таблицы
$this->sql_query = "DELETE FROM `$table`"; // Формируем запрос
$this->type = 'delete';
return $this;
}
...
Навороты
Еще живы? Хотите больше?
Рад, что это так. Потому что теперь мы к нашим базовым операциям будем добавлять навороты.
И предлагаю начать с таким простых, но в тоже время необходимых операций, как LIMIT, ORDER BY.
...
public function order_by($val, $type){ // Создаем метод для выборки данных, отсортированных определенным образом
$this->sql_query .= "ORDER BY `$val` $type"; // Модифицируем строку запроса
return $this;
}
public function limit($from, $to = NULL){ // Создаем метод для выборки определенного количества записей
$res_str = "";
if($to == NULL){
$res_str = $from;
}else{
$res_str = $from . "," . $to;
}
$this->sql_query .= " LIMIT " . $res_str; // Модифицируем строку запроса
return $this;
}
...
Добавим производительности
Дело в том, что сейчас, если мы пользуемся этим кодом в рамках одного файла, то все хорошо.
Но в реальности все сложнее. Работая с MVC мы можем забыть, что где-то уже установили соединение с БД и открыть его заново. А это не есть хорошо.
Дабы исправить это недоразумение, был создан паттерн Singleton.
Добавим это в нашу ORM.
class RyF{
public static $instance; // Переменная для реализации Singleton
...
public static function Instance(){ // Метод для проверки было ли уже создано соединение с БД
if(self::$instance == NULL){
self::$instance = new RyF();
}
return self::$instance;
}
...
}
Для тех, кто не в теме, стоит пояснить как это работает.
Мы создаем статическую переменную $instance. Это нам нужно, чтобы при создании второго экземпляра класса, значение переменной $instance сохранилось(значения статических переменных общие для всех экземпляров класса).
То есть теперь мы будем создавать экземпляр класса так:
$RyF = RyF::Instance();
И тогда, при вызове статического метода Instance, мы проверяем — инициализирована ли переменная $instance. Если она не инициализирована(пустая), то мы впервые обращаемся к БД в нашем проекте. В этом случае мы присваиваем ей новый экземпляр ORM. А, если мы уже работали с БД, то мы просто вернем уже инициализированный экземпляр.
То есть теперь мы всегда устанавливаем только одно соединение с базой данных.
Более сложные запросы
Наша ORM не предусматривает, например, возможность использовать JOIN. Это косяк, который может испортить все. Сейчас я предлагаю временное решение. Да, понимаю, что оно не лучшее, но на данный момент я еще не придумал как лучше реализовать данную возможность в ORM.
Поэтому создадим костыль.
Мы дадим пользователю возможность выполнения произвольного запроса. То есть дадим возможность получить переменную $pdo для «ручного» создания запроса.
Для этого изменим тип поля класса и создадим метод:
class RyF{
public $pdo;
...
public function get_pdo(){
return $this->pdo;
}
...
}
Теперь пользователь может создавать более сложные запросы, оперируя полученной переменной так же, как если бы не использовал ORM.
Production и версия разработки
Достаточно часто можно встретить разделение production версии и режима разработчика продукта.
Почему бы не добавить это и в наш проект?
Окей, в чем будет отличие?
Отличие будет в выводе ошибок. Согласитесь, что было бы не круто, если пользователь, переходя по ссылке, увидит отчет о том, что MySQL криво обработал запрос и возвращает Fatal Error.
Было бы лучше показать если не страницу ошибки, то хотя бы пустой экран.
К тому же, скрывая отчеты об ошибках работы БД, мы повисим безопасность проекта, который использует эту ORM. Ведь в ошибках PDO мы видим что именно не получилось сделать, а значит даем потенциальную возможность злоумышленнику узнать данные о структуре базы данных.
Как это реализовать?
Наиболее простым решением будет создать флаг, который будет отвечать за вывод ошибок.
Например, это будет выглядеть так:
class RyF{
private $production;
...
function __construct($prodaction = false, $array = array()){ // По умолчанию включаем режим разработчика и даем возможность в ручную указать настройки для работы PDO
$opt = $this->set_option($this->prodaction, $array = array()); // Передаем данные в метод
}
...
private function set_option($prodation, $array){ // Ограничиваем вывод ошибок
$opt = array();
if(!$this->prodaction){
if($array){ // Если разработчик передал свои настройки, то уважаем его мнение
$opt = $array;
}else{
$opt[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
$opt[PDO::ATTR_DEFAULT_FETCH_MODE] = PDO::FETCH_ASSOC;
}
}else{
if($array){
$opt = $array;
}else{
$opt[PDO::ATTR_DEFAULT_FETCH_MODE] = PDO::FETCH_ASSOC;
}
}
return $opt;
}
}
На этом завершаю свой рассказ. Это первая рабочая версия данного продукта, она покрывает все потребности для создания относительно простых сайтов и приложений.
Надеюсь, что данный материал был полезен для вас.
Полную версию кода можно найти в репозитории по ссылке.
Комментарии (32)
skyeff
23.04.2019 14:14+2Это не ORM и даже не DBAL. Это примитивнейший Query Builder, коих вагон и маленькая тележка. Зачем нужен такой велосипед, ну кроме как в целях самообразования, не понятно. В реальных проектах лучше использовать что-то более массовое.
Kenya
23.04.2019 14:30+1Так это же не ORM. ORM подразумевает связь сущностей ООП и записей в БД. А это, как писали выше, просто библиотека с реализацией Query Builder
vlreshet
23.04.2019 14:30+2А чем вам Eloquent не угодил? Или Doctrine?
crmMaster
23.04.2019 15:01+3Автор просто до них не догуглился, судя по цитате
> Наиболее известным является XCRUD, RedBeanPHP и прочие.
SamDark
23.04.2019 15:39Ну это же нормально попробовать написать своё. Тренировка, понимание. Поможет потом при работе с той же Doctrine (не в этом случае, а вообще).
ArsenAbakarov
23.04.2019 14:39+4«Надеюсь, что данный материал был полезен для вас.»
Надеюсь что Вы не используете это в проде
orion76
23.04.2019 14:53Нууу… деды… заворчали..-)
А помните, как всё начиналось?
Всё было впервые и вновь ..(с)
norguhtar
23.04.2019 15:20Ох. Вы что не дочитали Фаулера? А между прочим у него там есть data mapper и описание его. А так справедливо замечено, что в данный момент у вас query builder и это всего лишь часть ORM.
komandakycto
23.04.2019 15:35+13.5 года мало чтобы писать свою «ORM». Такими поделками только карму себе сольёте.
FanatPHP
23.04.2019 15:56+4Начиная читать эту статью, я честно надеялся, что это пусть и ученическая, но хотя бы добротная попытка сделать небольшой квери билдер. И собирался указать только на одну проблему — очевидную SQL инъекцию, наличие которой было очевидно еще до прочтения.
Но по мере чтения замечания копились, как снежный ком, как к самому коду, так и к статье.
Поэтому первый совет на будущее. Написать программу в пол-тыщи строк кода — это фигня. Написать статью, которая про нее рассказывает — вот это настоящая работа. В статье куча опечаток...
['name'->'userName','email'->'email'] ['name' => '= tester']
UPDATE `table` SET `name`=':name',`email`=':email'
… и непонятных туманных пассажей типа
Здесь происходит дополнительная обработка запроса. То есть экранирование.
Пожалуй, это самая сложная часть всего проекта. Осознав ее работу, вы точно поймете что и как устроено.Ну я как бы и пришел читать статью, чтобы мне объяснили, как все устроено. Но автор, видимо, это и сам себе не очень хорошо представляет (поскольку никакого "экранирования" в предыдущем блоке кода не происходит) и поэтому вместо объяснений приходится делать туманные пассы руками "осознав, вы точно поймете".
приступая к написанию статьи, надо выучить два железных правила:
- Проверять к работу каждой строчки кода.
- Честно признаваться себе в непонятных местах, что свое понимание здесь хромает, и делать небольшое расследование. Самому, а не перекладывая его на читателя.
Теперь о коде.
Во-первых, как и обещал — sql инъекция. Имена таблиц и полей здесь не защищены вообще никак. И это всегда приводит к плачевным результатам.
Что интересно — эта проблема ставит крест на самой идее самопального квери-билдера. И правильно ставит. Не нужно лениться. Не так уж и сложно написать запрос руками.
Методы insert/update вполне подойдут для настоящего ORM, где все имена полей и таблиц заранее жестко прописаны в описаниях соответствующих классов. Если же таких предварительно заданных значений в скрипте нет, то такие вот самопальные квери-билдеры — это прямая дорога для инъекций.
И даже если и без классической инъекции, без SQL кода в именах полей — все равно функция, которая принимает неконтролируемый набор полей приведет к тому, что новый пользователь, к примеру, добавит себе 100500 денег на баланс.
И не надо мне рассказывать что "всё контролируется на уровне приложения". приложения большие и сложные, и там можно где-то в одном месте забыть проконтролировать. пример я приводил выше. Задача обеспечения безопасности работы с БД должна лежать на коде, работающем с БД.
Дальше коротко
- название класса — это ужас. Я понимаю, все проходили через эту стадию, назвать что-то своим именем. Но тем не менее, название класса должно отражать его смысл. А эно можно потешить в неймспейсе.
- return $q->fetchall(); чудовищно ограничивает функциональность ПДО. А если нам нужно получить только одно значение, например результат count(*)? выковыривать его потом из многомерного массива? А если нам надо получить пары ключ-значение? ПДО это умеет из коробки, а с этим билдером придется колупаться вручную.
- по-хорошему, класс для работы с БД (с методами instance, execute и пр) должен быть совершенно отдельным от квери билдера
- синглтон — это ооочень спорное решение, которое подходит только для традиционного похапе-гуанокода, и не годится для ООП.
- set_default() — адов костыль. Нужно делать раздельные объекты для каждого запроса.
- раздел про обработку ошибок — это одно полное фиаско. В реальности всё совершенно наоборот. Никакой переменной "prodaction" в классе, отвечающем за работу с БД, быть не должно. Он не должен сам решать, куда и как выводить ошибки. Его дело ошибку породить. А уж как она будет обработана — это дело настроек уровня приложения.
- про die() уже сказали — это адъ.
- режим
PDO::ERRMODE_EXCEPTION
должен быть включён всегда. - то, что будет видеть пользователь, задается совсем другими настройками.
Для общего развития (на английском, но гугл-переводчик в помощь)
Bonio
23.04.2019 16:11синглтон — это ооочень спорное решение, которое подходит только для традиционного похапе-гуанокода
Почему? Для работы с БД это кажется удобным.Urvin
23.04.2019 16:18Потому что оно удобно только когда приложение работает только с одной БД, например.
Bonio
23.04.2019 16:21У меня есть приложение, которое работает с одной БД. Я там как раз синглтон использую для инициализации PDO. Хотелось бы понять, почему это плохо и как делать правильнее.
FanatPHP
23.04.2019 16:34Это зависит от архитектуры.
- если это простой процедурный код то синглтон ок
- если объектный, где все объекты создаются вручную — то передавать инстанс ПДО в конструктор каждому объекту, в котором он нужен
- если объекты создаются автоматически, то использовать для этого dependency injection container. Ну или фабрику какаую-нибудь, у которой в ресурсах есть этот инстанс.
Bonio
23.04.2019 16:39Объектный.
А допустимо ли создавать объект класса внутри другого класса? Вместо передачи его в конструкторе, например?FanatPHP
23.04.2019 16:42Сильно зависит от ситуации. С точки зрения хороших практик, лучше всегда передавать. Тогда всегда можно будет этот объект подменить, и тем самым изменить поведение, не меняя кода!
Для объекта, который работает с БД, тут в принципе без вариантов, поскольку соединение с одной и той же БД должно быть создано строго только один раз за время выполнения скрипта.
zzzmmtt
24.04.2019 09:24Да тут и сигнлтоном то и не пахнет. Конструктор открыт, __clone и __wakeup не реализованы, соответственно синглтон превращается… в гуанокод.
index0h
23.04.2019 16:47Друг, открой для себя Doctrine и не выдумывай постное УГ.
- PSR-4 + PSR-2, на дворе 2к19!
- composer — must have.
- Что делать если надо 2 подключения к БД? Константы в настройках — не катят.
- Прочитай про sql инъекции и поразмысли, что будет, если
$table = '`; drop table myTable; -- '
public function Select($table){ // Реализовываем метод для получения данных из БД $this->sql_query = "SELECT * FROM `$table` ";
- Забудь про существование die, юзай исключения.
- Почитай, может пригодиться https://toster.ru/q/276441#answer_723827
MrMYSTIC
23.04.2019 18:38$this->pdo = new PDO("mysql:host=localhost;dbname=".DB,User,Pass,);
Это будет работать только в 7.3, хотя, скорее всего, это просто опечатка автора.
Про PSR-2 уже говорили?
->where(['name' => '= tester'])
А если надо не равно? Или IS NOT NULL?
private $production; ... function __construct($prodaction = false, $array = array()){ // По умолчанию включаем режим разработчика и даем возможность в ручную указать настройки для работы PDO $opt = $this->set_option($this->prodaction, $array = array()); // Передаем данные в метод
То «a», то «u»
public static function Instance()
Это ещё не синглтон.
zzzmmtt
24.04.2019 09:31Посмотрел в реп автора, там всё плохо. О PSR автор не слышал видимо, с английским тоже всё не лучшим образом, то что это поделие совсем не ORM уже не раз написали, про поддержку чего-либо отличного от mysql тоже указали, а где обработка исключений? Сделать insert с исключением по unique key, то что вернёт метод? И как потом с этим работать?
Начать неплохо бы с http://phptherightway.ru/ и https://www.php-fig.org/psr/, потом почитать книгу «Паттерны проектирования» (Эл. Фримен, Эр. Фримен, К. Сиерра, Б. Бейтс), да она про Java, но в первую очередь об ООП.
Выше писали про композер и тесты… Мне кажется, что тут это совсем не нужно, равно как и эта статья на хабре.
Compolomus
24.04.2019 10:11Привет 2010 год)
FanatPHP
24.04.2019 10:21Ну, строго говоря, всегда есть кто-то, для кого в данной точке развития наступает 2010 год :)
Или даже скорее 2000, поскольку первые квери билдеры для пхп появились уже тогда.
В принципе, задел у автора неплохой, только он очень торопится. и теоретическая база хромает. но это все поправимо.
Compolomus
24.04.2019 10:35Ну я примерно в 2010 такое и написал, сейчас у меня тоже есть билдер. Чуть повеселее, можете глянуть, хотелось бы услышать вашу критику
https://github.com/Compolomus/SQLQueryBuilderFanatPHP
24.04.2019 11:42Хм, очень интересное решение.
Я правильно понимаю, что оно отвязано от собственно исполнения запросов, и чтобы получить результат, надо написать что-то вроде
$builder = new Builder('users'); $query = $builder->delete(5); $params = $builder->placeholders();
и потом выполнять запрос в PDO либо враппере на его основе?
Compolomus
24.04.2019 11:51Именно так. Запрос собирается вместе с плэйсхолдерами. Я пробовал чистый PDO переписать на билдер, получилось покороче
FanatPHP
24.04.2019 12:00Ну в любом случае, идея правильная — как я писал выше, само выполнение запросов должно быть отделено от билдера. Только я думал, что билдер будет использовать класс БД для выполнения, но можно и наоборот — некий третий класс будет использовать и класс БД и билдер для работы с запросами.
Сам класс, конечно, по сравнению с поделкой в этом посте — это небо и земля. Есть практически все что нужно. Я бы только отдельно отметил, что класс только для mysql. Ну или сделал специфические части, такие как escapeField() настраиваемыми.
Compolomus
24.04.2019 12:16Ну сначала думал доделать функционал, потом уже думать за разные драйверы. По хорошему надо эскейпер переписать, так как там для простоты сделано и для значения и для массива значений. В планах изначально было ещё несколько классов, билдер для схем, орм, сущность. Но времени как обычно нехватает
MetaDone
Раз вы захотели вывесить свое творение в публичный доступ то приведите код и саму библиотеку в актуальное состояние — добавьте установку через composer, уберите конфиги через константы, сделайте инициализацию через конструктор чтоб было
добавьте namespaces, уберите жесткую привязку к mysql, добавьте исключения вместо «die($info[2])», отформатируйте код согласно psr-2, напишите тесты
И по традиции к публикациям с самопальными сборщиками запросов — github.com/auraphp/Aura.SqlQuery
SamDark
Ради справедливости нужно отметить, что и Aura.SqlQuery — такой же «самописный» сборщик. Просто его автор несколько опытней.