Комментарии 59
Я сейчас не буду вам рассказывать о том, что это пустая затея и использовать на реальных проектах ее не стоит. Но ради повышения скила пойдет, почему бы и нет.

Но дам пару советов:

1. Не надо заставлять людей пихать все классы в один файл — жутко неудобно
2. Не используйте get_called_class() для SQL запросов. Лучше получайте его заранее и сохраняйте, если уж так хочется его использовать.
3. Почему у вас метод Add() возвращет ID вставленной записи (так и должно быть), а метод Remove() просто true/false? Надо бы возвращать кол-во удаленных строк, как и делает MySQL

Вообще для подобных нужд можно использовать вот это: http://www.phpactiverecord.org/
Довольно неплохая библитечка, проверенная временем, так сказать. Правда не обновлялась давно, но там особо и нечего обновлять наверное.
Да я бы не сказал, что не обновляется: https://github.com/jpfuentes2/php-activerecord/commits/master
Я просто посмотрел на дату релиза на сайте и 2010/2013 год :)

Хотя, сам им пользуюсь для простенького проекта — очень даже круто работает. Шустро, память не жрет как та же доктрина.
Возможно remove не возвращает кол-во удалённых записей, потому что в методе remove строго прописано LIMIT 1.
А что это меняет?
Возможно вызов метода ничего не удалит, тогда должен вернуться ноль, даже если там LIMIT 1
Надо бы возвращать кол-во удаленных строк

Кому надо?
Не очень хорошо представляю зачем. Команда на удаление одной записи, возвращает статус. В чём разница 0/1 или false/true?
true — успешно завершенная операция ( 0 или 1 строка удалены). Может ли это быть где-то важно? Я думаю что может, но где — сейчас не придумаю.
0 удаленных строк для объекта, который только что из базы получен или только что в неё записан — не штатная ситуация, по-моему.
Вы забыли ссылку на гитхаб.
p.s. вам еще рано писать на хабр, попробуйте еще раз через годик-другой
Извините, но ваш код заставил меня страдать.
Пробежимся по главному:
1. Почитайте на досуге PSR-1 и PSR-2 http://www.php-fig.org/psr/. На русском есть тут.
2.
static public function setup(mysqli $dbi,$enc = "utf8"){
		if (is_object($dbi)){
			...
		}else{
			throw new Exception("Параметр $dbi не является объектом mysqli", 1);
			return false;
		}
	}

Во-первых: mysqli морально устарел и мне кажется, что лучше использовать PDO.
Во-вторых: вы в интерфейсе функции чётко указали что хотите видеть в первом аргументе mysqli, и он не должен быть пустым. По этому PHP до версии 7 будет выдавать ошибку, а в 7й версии сам выкидывать TypeError если пришло то что не нужно. Если использовать функцию set_error_handler и преобразовывать стандартные ошибки в исключения, то можно существенно упростить код.

3.
SET NAMES '$enc'

$query .= " WHERE `$id` = ".self::escape($this->$id)." LIMIT 1";

private static function escape($string) {
		return mysqli_real_escape_string(self::$db,$string);
	}

Первый случай найдёте в стандартах, а для второго и третьего я бы порекомендовал использовать параметаризированные запросы из PDO

4.
$Update[] = "`".$k."` = ".self::RenderField($this->$k);

Тут лучше подойдёт sprintf

И в целом, как уже было сказано выше, для серьёзного проекта это вряд ли пригодится.
Во-первых: mysqli морально устарел и мне кажется, что лучше использовать PDO.

См таблицу «Сравнение опций MySQL API в PHP» на http://php.net/manual/ru/mysqli.overview.php: Рекомендовано MySQL для разработки новых проектов: Расширение PHP mysqli: Да — отдается предпочтение.
Mysqli не устарел морально и нужно смотреть, что для своей задачи использовать лучше.
Согласитесь, что проще изучать общий синтаксис PDO а не API конкретного драйвера. Вся прелесть PDO, что переход на другой источник данных занимает меньшее время, а если ORM позволяет, то и вовсе подключаться к любой БД используя стандартные методы абстрактного класса.

И по поводу сравнение опций:
API поддерживает подготавливаемые запросы на стороне клиента: Нет(mysqli) Да(PDO)

Я считаю, что это важный пункт, оверхед на парсинг запроса конечно не так велик, но всё зависит от проекта. По крайней мере из таблицы я не смог увидеть минусов PDO.
Подготавливаемые запросы на стороне клиента — это псевдоподготавливаемые запросы, это все равно, что использовать mysqli_real_escape_string().
Сударь, вы не совсем правы. Вот прув.

Подготавливает SQL запрос к базе данных к запуску посредством метода PDOStatement::execute(). Запрос может содержать именованные (:name) или неименованные (?) псевдопеременные, которые будут заменены реальными значениями во время запуска запроса на выполнение.


Вызов PDO::prepare() и PDOStatement::execute() для запросов, которые будут запускаться многократно с различными параметрами, повышает производительность приложения, так как позволяет драйверу кэшировать на клиенте и/или сервере план выполнения запроса и метаданные, а также помогает избежать SQL инъекций, так как нет необходимости экранировать передаваемые параметры.


Таким образом, это и подтверждает то, что я хотел сказать, а именно: для проекта, где один и тот же запрос может вызываться много раз с разными параметрами, то подготовка запросов будет выгоднее, т.к не будет постоянного парсинга запроса. По этому я и сказал, что оверхед при выполнение одного подготовленого запроса незначителен, а если этот запрос выполняется n раз, то лучше сократить издержки.
Добавлю на всякий случай — если для автора уж слишком «проблемно» изучение PDO, то подготавливаемые запросы есть и в mysqli для ооп и процедурного стиля: пруф. При использовании такого подхода разница между mysqli/pdo по синтаксису будет минимальна, а в дальнейшем поможет плавно перейти к PDO.
Цитата касается подготовленных запросов на сервере.
Поищите как работают эмулированные.

По поводу повторных запросов.
Это работает только с подготовленными выражениями на сервере.
В случае одинарных запросов производительность просядает, поэтому и используется по умолчанию эмуляция.

Также я не особо понимаю смысла однотипных запросов, если можно выполнить один запрос с WHERE `column` IN (val1, ..., valN).

По Вашей ссылке также:
>Замечание:

>Эмулируемые подготовленные запросы не создаются на сервере баз данных, поэтому PDO::prepare() не может проверить правильность построенного запроса.
Так, давайте разберёмся с PDO::prepare(), по моему вы что-то не поняли из документации или из моих объяснений. Я утверждаю что prepare компилит запрос, проверяя правильность синтаксиса и ждёт когда выполнится execute с параметрами. Соответственно для двух запросов, у которых разные только параметры нет смысла компилить запрос два раза, т.к sql код одинаковый (параметры не в счёт).
Да, логично что prepare не хранит компилированные запрос где-то у себя, он возвращает объект, который нужно сохранить в статическое свойство класса, а при повторном вызове похожего запроса просто взять уже скомпилированный запрос а не вызывать заново prepare, сделать это можно в пару строчек:

protected static $_cache = array();
....
if (!isset(self::$_cache[md5($query)])) {
    self::$_cache[md5($query)] = $this->_handler->prepare($query);
}
return self::$_cache[md5($query)]->execute($params);

Накатал на коленке, надеюсь суть будет понятна.

Теперь разберёмся с замечаниями, которые вы нашли по моей ссылке.
Либо я не правильно понимаю что это значит, либо вы, но я утверждаю что prepare именно разбирает запрос и проверяет в нём ВСЁ, за исключением значения параметров. Т.е если в запросе ошибка синтаксиса, или отсутствует поле, то это выявится именно на этапе prepare, и именно на это уходит время, и именно по этому при повторном запросе проще брать его из кеша. Я так понял что вы упёртый и мне не поверите, по этому я запустил запрос и протестировал, и сейчас вам докажу (вы что-то на пруфы не расщедрились):

Берём запрос:
select2 a.subject  from `...` a

Получаем Exception:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select2 a.subject ....' at line 1

и стеком:
#0 path/to/file: PDO->prepare('select2 a.subje...', Array)


Ещё один:
select a.subject2 from `...` a

Получаем Exception:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'a.subject2' in 'field list'

и стеком:
#0 path/to/file: PDO->prepare('select a.subjec...', Array)


Надеюсь я вас убедил, если не верите, то запустите сами.

Теперь по поводу того, зачем выполнять один запрос несколько раз, если можно сделать column in. Объясняю:
Допустим у нас в ORM есть метод byId у которого есть запрос вида:

select * from `table` where `id`=:id

Далее в коде мы где-то хотим получить объём с id=1 и мы делаем Entity::byId(1). Далее мы с этим объектом работаем, и потом хотим получить объект с id=2 для каких-то других целей через 100500 строчек кода, и делаем Entity::byId(2). Так вот по-скольку эти строчки не идут друг за другом, и я не могу знать какие объекты мне будут нужны позднее, я использую параметаризированный запрос, который будет иметь один и тот же скомпилированный объект, а на этапе вызова заменять параметр :id на id нужного объекта, тем самым избегая затрат на парсинг и анализ этого запроса.

Надеюсь понятно изложил.
>Теперь разберёмся с замечаниями, которые вы нашли по моей ссылке.
и
>вы что-то на пруфы не расщедрились

Так ссылка-то Ваша… :)

Кстати, там же:
>Например, вы не можете привязать несколько значений к одному параметру для SQL выражения IN().

То есть для каждого value в IN(value1, ..., valueN) или заводить свой именованный параметр, или не использовать подготовленные выражения :)
Если заводить свой именованный параметр, то повторное использование заканает, если будет одинаковое число параметров.

>Теперь по поводу того, зачем выполнять один запрос несколько раз, если можно сделать column in.

В разных местах обычно нужно выбрать разные данные, * вибирать не совсем правильно. :)

И еще раз поищите и подумайте, что делают эмулированные, которые по умолчанию. Там нет никакого плана запроса, просто данные искейпятся. Сделано это для поддержки старых серверов, которые не имеют серверных подготовленных выражений.

http://stackoverflow.com/questions/15718148/php-prepared-statements-turn-emulation-off (первый ответ)
https://forum.phalconphp.com/discussion/9183/are-rawsql-prepared-statements-emulated-or-for-real
Перевернули всё с ног на голову.

Так ссылка-то Ваша… :)


Ссылка моя, но я же не её автор. Вы скопировали кусок, который говорит что запросы «PDO::prepare() не может проверить правильность построенного запроса», я вам доказал что может, а вы снова доказываете что не может)

То есть для каждого value в IN(value1, ..., valueN) или заводить свой именованный параметр, или не использовать подготовленные выражения :)


Да, с IN вечная проблема, но вы опять всё переврали, т.к я это вы предлагали его использовать, а я объяснял почему он не удобен и не нужен в данном конкретном случае.

В разных местах обычно нужно выбрать разные данные, * выбирать не совсем правильно. :)


Во-первых, пример тестовый. Во-вторых, если бы вы внимательно прочитали, то поняли, что byId нужен только в рамках одной сущности, а одна сущность это одна таблица, а в ней для любой записи набор полей одинаковый. А когда сущность другая там и запрос будет уже select * from table2 а не select * from table1 и кеш будет уже для каждой таблицы. Т.е для каждой таблицы byId будет хранить кеш запроса, т.е одна компиляция на одну таблицу, что существенно меньше, чем одна компиляция на один запрос к одном таблице. Посчитайте на калькуляторе

Похоже я всё-таки допёр что вы пытаетесь мне объяснить)

Да, эмуляция запроса у меня включена, и да, именно она и проверяет синтаксис и поля. По сути это такой тестовый запрос, только без данных и реальных действий. Если её отключить, то всё это будет проверяться уже при execute.

При execute да, данные просто эскейпятся, это никто и не отрицал.
По поводу плана, тоже всё верно. Его нельзя полностью построить т.к нет ещё значений, которые могут повлиять на выбор индекса.

Тут я ещё хотел написать, что ошибался, что execute делает заново разбор запроса и не учитывает результат prepare, но потом прочитал
http://php.net/manual/ru/pdo.prepared-statements.php

и понял, что по сути возможность компилить запросы и хранить их в БД зависит от самой БД.
В mySQL это http://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html

И почитайте как раз ссылку с php.net, там доступно рассказано то, что я уже так долго пытаюсь вам объяснить)
Его нельзя полностью построить т.к нет ещё значений, которые могут повлиять на выбор индекса.

Я конечно не спец по БД, но для любого подготовленного запроса можно построить план запроса, просто в PHP это не имеет смысла, ибо подготовка жрёт ресурсы, а так как она будет выполняться при каждом запросе к скрипту смысл в ней отпадает, если вы конечно не демона на пыхе пишете.
Подготовка имеет смысл в приложениях, где она происходит один раз — при старте приложания (например в яве [если запросы не динамические])

Подготовка имеет смысл. Например если использовать веб-сокеты. Или чтение большого массива данных (например из файла, или бд) и их вставка в бд
> Например если использовать веб-сокеты.
Это подпадает под
> демона на пыхе пишете
Я бы не хотел разводить холивар о том, что лучше. У обоих библиотек есть свои плюсы и минусы. Я лишь подчеркнул, что mysqli не является устаревшем.
Вы назвали свое творение ORM, но ничего про отношения между объектами нет. Значит это просто сборщик запросов. Как практика — норм, вот пример кошерного сборщика запросов — https://github.com/auraphp/Aura.SqlQuery
Справедливости ради, ORM не про отношения объектов, а про маппинг объектов на отношения (таблицы) РСУБД и обратно.
Честно говоря почему то люблю читать комменты про чужие неуклюжие велосипеды на php и так как изыскано каждый раз комментирующие поливают автора разными субстанциями. Возможно это какой-то фетиш, листая хабр каждый раз как вижу php, я написал <новое творение> и минусы… переходишь и получаешь неприкрытое удовольствие.
Потому что в php чаще всего разработчики не ищут готовых решений, не сравнивают или не желают разбираться а пишут кривой велосипед без стандартов.
Велосипеды — это здорово, особенно в таком возрасте, когда ты считаешь себя самым одаренным программистом в мире))
Велосипеды это совсем не здорово, у меня изза велосипедов всякие мелкие скрипты останавливались на архитектуре. Вечно возишься со своими велосипедами потому что они косячат, юнит тестирование не слышал, как построить архитектуру не знаешь потому что не смотрел на готовые решения никогда. Велосипеды это вредно даже для начинающего программиста. Имхо конечно, лучше сразу учить как оно адекватно делается.
Программирование это «грязная задача», потому от просмотра «как оно адекватно делается» программист не повысит свой скил, а только научится копипастить чужие решения. Лучше совмещать теорию с написанием велосипедов, это облегчит понимание и осознание теории.
Таки возражу. Как мне кажется, если сразу учить, как оно делается адекватно, то знание сформируется на уровне «потому что так надо». Если же предварительно набить шишек, катаясь по граблям на велосипеде без седла с квадратными колесами, и потом уже с осознанием некой неправильности происходящего пойти за просветлением в умные книжки, то знаниям будет за что зацепиться, будет с чем сравнить нормальные решения и уже будем некое понимание, почему на самом деле нужно писать так, а не этак. Но разумеется, нужно держать баланс. Ещё такая ситуация, когда пишешь фигню а потом видишь нормальное решение, она отрезвляет, перестаёшь считать себя «самым одаренным программистом в мире»)
велосипеды хороши в качестве самообучения. Но вредны, если не покрыть их тестами. К примеру автор статьи сделал что-то работающее. Можно посоветовать ему соединить свое творение с http://codeception.com/ — тогда и появится минимальное разделение ответственности, может библиотека перестанет быть божественным объектом и в итоге все станет более-менее юзабельным
ой!
Поставил плюс в карму исключительно авансом, а то коллеги прибегут да раскритикуют, что потом никакой «рекавери» не поможет. А желание поделится наказывать не стоит.

По сути:
1 — у Вас заметный пробел в базовых понятиях. Вы почитали документацию, но одной документации мало. Например понятие статики у вас несколько искаженное. «Метод существует у класса или у объекта»? что простите?) К данным это условно применимо, но не к методам. Почитайте какой-то учебник. А вообще постарайтесь минимизировать количество статических вызовов. для начала можете сделать синглтон. А вообще лучше использовать какой-то сервис-локатор. Тогда Вы сможете передавать класс работы с базой (обертку над ПДО) своим активрекорд или построителям, что даст Вам возможность делать разные конекты для разных таблиц, в разных базах (в том числе на разных СУБД).
2 — про устаревшую библиотеку равно как и сам подход с эскейпингом отставший лет на 5-7 вам уже сказали. PDO для Вас выбор обязательный.
3 — изучите получше принципы SOLID и DRY. Это не просто свод правил. Это практические принципы улучшающие любой код.
3.1. — стоит отделять генерацию запросов от логики самих активрекорд. Это даст Вам возможность просто добавить еще один класс для другой СУБД просто слегка подправив запросы, но не потеряв совместимость с исходным мускулом.
3.2. — get_class_vars() работоспособен здесь и сейчас. Но как только я расширяю класс дополнительным функционалом, добавляю новые статические поля, и весь код поломается. Вообще для простоты советую создать метод или статическую переменную с массивом имен полей, а сами поля хранить в другом массиве (не статическом), отдавая из через _гет и _сет.
3.3. — имя поля с ид определяется исходя из того кто первый? эм… серьезно? Ну опять таки, даже если и так, то стоит такую вещь вынести в отдельное место, отдельным методом, чтобы при изменении логики не менять весь код. Вообще этот подход стоит применить везде. Вот захотите вы сделать метод поиска по произвольному условию, и что? писать новый метод? И для изменения метод, и для поиска, и для удаления...?

ПС: а вообще продолжайте. Если будете учиться на своих ошибках, то быстро научитесь.
ППС: не читайте перед обедом советских газет и людей с большими научными регалиями. Как правило их знания в ИТ успевают устареть на несколько поколений, чему пример ваш подход с эскейпингом вместо ПДО.
2 — про устаревшую библиотеку равно как и сам подход с эскейпингом отставший лет на 5-7 вам уже сказали. PDO для Вас выбор обязательный.

Насчет подхода согласен, а вот про библиотеку, см. комментарий выше
Ну она устаревшая именно потому что подход устаревший. Мы с вами можем и сами грамотно обернуть чтобы просто менять на другие движки попутно написав эмуляцию параметров где это нужно. Но:
1 — зачем если есть из коробки универсальное?
2 — новичек не потянет.
Сырое.
Нету JOINов.
Вместо self::$db->query(«SET NAMES '$enc'»); лучше mysqli_set_charset($link, $enc);
И, наверное, не стоит жестко завязываться на mysqli, как и на любой другой.

Зачем $id = key(self::_getVars()); в Save(), Remove()?

Да и вообще ORM — фигня.
Смысл прописывать поля, которые есть у таблицы?

Также у Вас не пройдет частичное обновление записей, также можно обновить только по первичному ключу.

>if (isset($mValue[0]) && !empty($mValue[0]))

Достаточно !empty($mValue[0]

Также отступы слева во всех примерах кода стоило бы уменьшить.

Ну такое.
У вас вообще своеобразное видение программирования) И честно говоря этот ваш «Тренд» порядком надоел.
Та при чем тут мой тренд, когда в посте откровенная ерунда?

Не нравится тренд, найдите пост, где я продвигаю свой тренд, и там насрите.
зачем?? дать вам пищу для мозгов? уж лучше продолжайте гонять фекалии по вашему пищеварительному циклу.
Для начала нужно определиться уже кто срал, а кто не срал. И это не вам решать срать мне или не срать. Более того — не вам решать — подходящее это место или нет. Я сказал то что думаю о вас и вашем «Творчестве» — и я почему то подозреваю что я не один такого направления мыслей.
В мире есть одна тенденция — если рядом с вами человек которого вы считаете хандоном — и он это никак не опровергает. То он хандон и для него это не очень хорошо — для вас это плохо тем что у вас скилл притягивать хандонов.

У тенденции есть одна оговорка — если рядом с вами нет тех кого вы считаете хандонами — но вы про кого то все равно думаете что он хандон — у вас скилл притягивать хандонов на большом расстоянии.

Тенденция действует на всех. Но вам насрать ведь верно? вы опять какую-нито чушь сейчас сморозите.
То есть Вы считаете, что я притягиваю гандонов, а Вы — не гандон?

Может все с точностью до наоборот?
Вы — гандон и сами лезете, никто Вам не притягивает? :)

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

Собственно чем не подошли существующие аналоги?
Судя по коду автора — существующие было слишком сложно понять
Вспомнил себя, в свое время мне не хватало вот такой книги, чтобы мои библиотеки приближались к какой-либо стройной архитектуре и четкому разделению ответственностей:
www.phptherightway.com/#databases

Буду лучом света в этом чане нечистот комментариев. (не воспринимать как абсолютное одобрение)


Вполне нормальная статья для начинающего.


Про злобных комментаторов: не обращай внимания, просто ровно все они писали подобные велосипеды поначалу (а то и хуже) и им так же говорили, что они не правы, вот теперь пришла их очередь наставлять на путь истинный. (Пруфы? Пруфов не будет — все и так это понимают)


Про комментаторов, чьи слова порядком непонятны: временно забудь, повелосипедируй ещё немного в своё удовольствие, потом потыкай какой-нибудь готовый, поддерживаемый сообществом framework (только трупы не тормоши), потом прочти книгу банды четырёх, затем стоит прочесть "Шаблоны корпоративных приложений" и просветление придёт к тебе.


И на будущее: не выкладывай свои велосипеды сюда (по крайней мере пока они полсотни звёзд на гитхабе не наберут, или ты не будешь в них абсолютно уверен), карму потом восстанавливать устанешь.


Ну и добро пожаловать на хабр!

Очередная обертка над mysqli? Зачем? Сколько было подобных статей, и каждую из них сливали…
Вот лучше бы изучили какую-нибудь доктрину, или eloquent...

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.