Своя ORM на PHP -24



Привет, Хабр!

Думаю в жизни каждого веб-разработчика наступает момент, когда хочется сделать что-то свое, внести вклад в развитие своей отрасли.

Такой момент настал у меня. Спустя 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. Ибо возможностью экранировать запросы пренебрегать не стоит.

То есть у нас будет класс, который устанавливает соединение с базой данных. А для выполнения операций над БД мы создадим свои методы.

Еще нам нужен файл конфига, где будут храниться данные для установления соединения с БД
Окей, реализуем.

Файл config.php
<?php
	define('DB','bd_name');// Название БД
	define('User', 'root');// Имя пользователя
	define('Pass', '');// Пароль пользователя


Файл Ryf.php
<?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.

Как будет выглядеть запрос:

Файл index.php
$RyF->Select('table')
	->execute();


Файл Ryf.php
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.

Пока что все просто.

Пару слов о фишке
В ООП есть замечательная вещь — $this. При выполнении метода мы так или иначе что-то возвращаем.

А при таком виде записи мы возвращаем из метода сам объект.

Что нам это дает?

Возвращая сам объект после отработки метода, мы получаем возможность использовать этот объект повторно. А значит выполнять цепочки вызовов.

То есть, когда выполняется код $RyF->Select('table') у нас возвращается сам $RyF и мы можем продолжить вызывать методы сколько нашей душе угодно.

Ограничиваем получение записей


Повышаем градус.

Теперь мы хотим добавить функциональности и получать только определенные записи. В SQL для этого используется оператор Where.

Окей, сделаем это.

Файл RyF.php
<?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();
	}
	


Здесь происходит дополнительная обработка запроса. То есть экранирование.

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

Как выглядит наш запрос сейчас:

Файл index.php
<?php
$RyF->Select('users')
	->where(['name' => '= tester'])
	->execute();


Данные в метод приходят в виде ассоциативного массива. Перебирая этот массив, мы модифицируем его так, чтобы можно было передать в «execute» для корректного экранирования.
Проще говоря, мы превращаем ['name' => 'username'] в [':name' => 'username'], а так же дописываем в запрос — «WHERE `name`=:name».

Теперь у нас возникает другая проблема — после выполнения одного запроса к БД мы не можем выполнить другой, потому что наши данные все еще хранятся в полях класса.

Фиксим это отдельным методом, который будет сбрасывать значения полей класса.

Файл RyF.php
...
private function set_default(){
	$this->sql_query = ""; // Сбрасываем строку запроса
	$this->values_for_exec = array(); // Сбрасываем массив значений для экранирования
}
...


Гуд, теперь наш код работает адекватно.

Добавление записей


Продолжаем повышать градус.

Следующим шагом будет добавление записей в БД.

Теперь наша задача проделать ту же самую операцию, что и с методом where, для массива с данными для добавления записей.

Файл RyF.php
...
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;
}
...


А теперь осознаем что только что произошло.

К нам пришел запрос вида:

Файл index.php
$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 будет выглядеть так:

Файл RyF.php
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 — достаточно простая операция, поэтому не вижу особого смысла расписывать.

Он выглядит так:

Файл RyF.php
...
public function Delete($table){ // Метод для удаления записей из таблицы
	$this->sql_query = "DELETE FROM `$table`"; // Формируем запрос
	$this->type = 'delete';
	return $this;
}	
...


Навороты


Еще живы? Хотите больше?

Рад, что это так. Потому что теперь мы к нашим базовым операциям будем добавлять навороты.
И предлагаю начать с таким простых, но в тоже время необходимых операций, как LIMIT, ORDER BY.

Файл RyF.php
	
...
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.

Файл RyF.php
	
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 мы видим что именно не получилось сделать, а значит даем потенциальную возможность злоумышленнику узнать данные о структуре базы данных.

Как это реализовать?

Наиболее простым решением будет создать флаг, который будет отвечать за вывод ошибок.
Например, это будет выглядеть так:

Файл RyF.php
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):

  1. MetaDone
    /#20064646 / +4

    Раз вы захотели вывесить свое творение в публичный доступ то приведите код и саму библиотеку в актуальное состояние — добавьте установку через composer, уберите конфиги через константы, сделайте инициализацию через конструктор чтоб было

    $RyF = new RyF($driver,$user, $password, $host, $port);
    

    добавьте namespaces, уберите жесткую привязку к mysql, добавьте исключения вместо «die($info[2])», отформатируйте код согласно psr-2, напишите тесты
    И по традиции к публикациям с самопальными сборщиками запросов — github.com/auraphp/Aura.SqlQuery

    • SamDark
      /#20065138

      Ради справедливости нужно отметить, что и Aura.SqlQuery — такой же «самописный» сборщик. Просто его автор несколько опытней.

  2. skyeff
    /#20064660 / +2

    Это не ORM и даже не DBAL. Это примитивнейший Query Builder, коих вагон и маленькая тележка. Зачем нужен такой велосипед, ну кроме как в целях самообразования, не понятно. В реальных проектах лучше использовать что-то более массовое.

  3. Kenya
    /#20064744 / +1

    Так это же не ORM. ORM подразумевает связь сущностей ООП и записей в БД. А это, как писали выше, просто библиотека с реализацией Query Builder

  4. vlreshet
    /#20064748 / +2

    А чем вам Eloquent не угодил? Или Doctrine?

    • crmMaster
      /#20064940 / +3

      Автор просто до них не догуглился, судя по цитате
      > Наиболее известным является XCRUD, RedBeanPHP и прочие.

    • SamDark
      /#20065152

      Ну это же нормально попробовать написать своё. Тренировка, понимание. Поможет потом при работе с той же Doctrine (не в этом случае, а вообще).

  5. ArsenAbakarov
    /#20064802 / +4

    «Надеюсь, что данный материал был полезен для вас.»
    Надеюсь что Вы не используете это в проде

  6. orion76
    /#20064894

    Нууу… деды… заворчали..-)
    А помните, как всё начиналось?
    Всё было впервые и вновь ..(с)

  7. norguhtar
    /#20065056

    Ох. Вы что не дочитали Фаулера? А между прочим у него там есть data mapper и описание его. А так справедливо замечено, что в данный момент у вас query builder и это всего лишь часть ORM.

  8. komandakycto
    /#20065122 / +1

    3.5 года мало чтобы писать свою «ORM». Такими поделками только карму себе сольёте.
    image

  9. FanatPHP
    /#20065244 / +4

    Начиная читать эту статью, я честно надеялся, что это пусть и ученическая, но хотя бы добротная попытка сделать небольшой квери билдер. И собирался указать только на одну проблему — очевидную SQL инъекцию, наличие которой было очевидно еще до прочтения.


    Но по мере чтения замечания копились, как снежный ком, как к самому коду, так и к статье.


    Поэтому первый совет на будущее. Написать программу в пол-тыщи строк кода — это фигня. Написать статью, которая про нее рассказывает — вот это настоящая работа. В статье куча опечаток...


    ['name'->'userName','email'->'email']
    ['name' => '= tester']

    UPDATE `table` SET `name`=':name',`email`=':email'

    … и непонятных туманных пассажей типа


    Здесь происходит дополнительная обработка запроса. То есть экранирование.
    Пожалуй, это самая сложная часть всего проекта. Осознав ее работу, вы точно поймете что и как устроено.

    Ну я как бы и пришел читать статью, чтобы мне объяснили, как все устроено. Но автор, видимо, это и сам себе не очень хорошо представляет (поскольку никакого "экранирования" в предыдущем блоке кода не происходит) и поэтому вместо объяснений приходится делать туманные пассы руками "осознав, вы точно поймете".


    приступая к написанию статьи, надо выучить два железных правила:


    1. Проверять к работу каждой строчки кода.
    2. Честно признаваться себе в непонятных местах, что свое понимание здесь хромает, и делать небольшое расследование. Самому, а не перекладывая его на читателя.

    Теперь о коде.


    Во-первых, как и обещал — sql инъекция. Имена таблиц и полей здесь не защищены вообще никак. И это всегда приводит к плачевным результатам.
    Что интересно — эта проблема ставит крест на самой идее самопального квери-билдера. И правильно ставит. Не нужно лениться. Не так уж и сложно написать запрос руками.


    Методы insert/update вполне подойдут для настоящего ORM, где все имена полей и таблиц заранее жестко прописаны в описаниях соответствующих классов. Если же таких предварительно заданных значений в скрипте нет, то такие вот самопальные квери-билдеры — это прямая дорога для инъекций.


    И даже если и без классической инъекции, без SQL кода в именах полей — все равно функция, которая принимает неконтролируемый набор полей приведет к тому, что новый пользователь, к примеру, добавит себе 100500 денег на баланс.


    И не надо мне рассказывать что "всё контролируется на уровне приложения". приложения большие и сложные, и там можно где-то в одном месте забыть проконтролировать. пример я приводил выше. Задача обеспечения безопасности работы с БД должна лежать на коде, работающем с БД.


    Дальше коротко


    • название класса — это ужас. Я понимаю, все проходили через эту стадию, назвать что-то своим именем. Но тем не менее, название класса должно отражать его смысл. А эно можно потешить в неймспейсе.
    • return $q->fetchall(); чудовищно ограничивает функциональность ПДО. А если нам нужно получить только одно значение, например результат count(*)? выковыривать его потом из многомерного массива? А если нам надо получить пары ключ-значение? ПДО это умеет из коробки, а с этим билдером придется колупаться вручную.
    • по-хорошему, класс для работы с БД (с методами instance, execute и пр) должен быть совершенно отдельным от квери билдера
    • синглтон — это ооочень спорное решение, которое подходит только для традиционного похапе-гуанокода, и не годится для ООП.
    • set_default() — адов костыль. Нужно делать раздельные объекты для каждого запроса.
    • раздел про обработку ошибок — это одно полное фиаско. В реальности всё совершенно наоборот. Никакой переменной "prodaction" в классе, отвечающем за работу с БД, быть не должно. Он не должен сам решать, куда и как выводить ошибки. Его дело ошибку породить. А уж как она будет обработана — это дело настроек уровня приложения.
      • про die() уже сказали — это адъ.
      • режим PDO::ERRMODE_EXCEPTION должен быть включён всегда.
      • то, что будет видеть пользователь, задается совсем другими настройками.

    Для общего развития (на английском, но гугл-переводчик в помощь)


    • Bonio
      /#20065340

      синглтон — это ооочень спорное решение, которое подходит только для традиционного похапе-гуанокода

      Почему? Для работы с БД это кажется удобным.

      • Urvin
        /#20065398

        Потому что оно удобно только когда приложение работает только с одной БД, например.

        • Bonio
          /#20065424

          У меня есть приложение, которое работает с одной БД. Я там как раз синглтон использую для инициализации PDO. Хотелось бы понять, почему это плохо и как делать правильнее.

          • FanatPHP
            /#20065506

            Это зависит от архитектуры.


            • если это простой процедурный код то синглтон ок
            • если объектный, где все объекты создаются вручную — то передавать инстанс ПДО в конструктор каждому объекту, в котором он нужен
            • если объекты создаются автоматически, то использовать для этого dependency injection container. Ну или фабрику какаую-нибудь, у которой в ресурсах есть этот инстанс.

            • Bonio
              /#20065546

              Объектный.
              А допустимо ли создавать объект класса внутри другого класса? Вместо передачи его в конструкторе, например?

              • FanatPHP
                /#20065566

                Сильно зависит от ситуации. С точки зрения хороших практик, лучше всегда передавать. Тогда всегда можно будет этот объект подменить, и тем самым изменить поведение, не меняя кода!


                Для объекта, который работает с БД, тут в принципе без вариантов, поскольку соединение с одной и той же БД должно быть создано строго только один раз за время выполнения скрипта.

    • zzzmmtt
      /#20068276

      Да тут и сигнлтоном то и не пахнет. Конструктор открыт, __clone и __wakeup не реализованы, соответственно синглтон превращается… в гуанокод.

  10. index0h
    /#20065598

    Друг, открой для себя Doctrine и не выдумывай постное УГ.


    1. PSR-4 + PSR-2, на дворе 2к19!
    2. composer — must have.
    3. Что делать если надо 2 подключения к БД? Константы в настройках — не катят.
    4. Прочитай про sql инъекции и поразмысли, что будет, если $table = '`; drop table myTable; -- '
      public function Select($table){ // Реализовываем метод для получения данных из БД
          $this->sql_query = "SELECT * FROM `$table` ";
    5. Забудь про существование die, юзай исключения.
    6. Почитай, может пригодиться https://toster.ru/q/276441#answer_723827

  11. MrMYSTIC
    /#20066216

    $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()
    

    Это ещё не синглтон.

  12. zzzmmtt
    /#20068306

    Посмотрел в реп автора, там всё плохо. О PSR автор не слышал видимо, с английским тоже всё не лучшим образом, то что это поделие совсем не ORM уже не раз написали, про поддержку чего-либо отличного от mysql тоже указали, а где обработка исключений? Сделать insert с исключением по unique key, то что вернёт метод? И как потом с этим работать?
    Начать неплохо бы с http://phptherightway.ru/ и https://www.php-fig.org/psr/, потом почитать книгу «Паттерны проектирования» (Эл. Фримен, Эр. Фримен, К. Сиерра, Б. Бейтс), да она про Java, но в первую очередь об ООП.
    Выше писали про композер и тесты… Мне кажется, что тут это совсем не нужно, равно как и эта статья на хабре.

  13. Compolomus
    /#20068466

    Привет 2010 год)

    • FanatPHP
      /#20068512

      Ну, строго говоря, всегда есть кто-то, для кого в данной точке развития наступает 2010 год :)
      Или даже скорее 2000, поскольку первые квери билдеры для пхп появились уже тогда.


      В принципе, задел у автора неплохой, только он очень торопится. и теоретическая база хромает. но это все поправимо.

      • Compolomus
        /#20068606

        Ну я примерно в 2010 такое и написал, сейчас у меня тоже есть билдер. Чуть повеселее, можете глянуть, хотелось бы услышать вашу критику
        https://github.com/Compolomus/SQLQueryBuilder

        • FanatPHP
          /#20068964

          Хм, очень интересное решение.
          Я правильно понимаю, что оно отвязано от собственно исполнения запросов, и чтобы получить результат, надо написать что-то вроде


          $builder = new Builder('users');
          $query = $builder->delete(5);
          $params = $builder->placeholders();

          и потом выполнять запрос в PDO либо враппере на его основе?

          • Compolomus
            /#20069014

            Именно так. Запрос собирается вместе с плэйсхолдерами. Я пробовал чистый PDO переписать на билдер, получилось покороче

            • FanatPHP
              /#20069090

              Ну в любом случае, идея правильная — как я писал выше, само выполнение запросов должно быть отделено от билдера. Только я думал, что билдер будет использовать класс БД для выполнения, но можно и наоборот — некий третий класс будет использовать и класс БД и билдер для работы с запросами.


              Сам класс, конечно, по сравнению с поделкой в этом посте — это небо и земля. Есть практически все что нужно. Я бы только отдельно отметил, что класс только для mysql. Ну или сделал специфические части, такие как escapeField() настраиваемыми.

              • Compolomus
                /#20069182

                Ну сначала думал доделать функционал, потом уже думать за разные драйверы. По хорошему надо эскейпер переписать, так как там для простоты сделано и для значения и для массива значений. В планах изначально было ещё несколько классов, билдер для схем, орм, сущность. Но времени как обычно нехватает