Комментарии 59
Но дам пару советов:
1. Не надо заставлять людей пихать все классы в один файл — жутко неудобно
2. Не используйте get_called_class() для SQL запросов. Лучше получайте его заранее и сохраняйте, если уж так хочется его использовать.
3. Почему у вас метод Add() возвращет ID вставленной записи (так и должно быть), а метод Remove() просто true/false? Надо бы возвращать кол-во удаленных строк, как и делает MySQL
Вообще для подобных нужд можно использовать вот это: http://www.phpactiverecord.org/
Довольно неплохая библитечка, проверенная временем, так сказать. Правда не обновлялась давно, но там особо и нечего обновлять наверное.
Надо бы возвращать кол-во удаленных строк
Кому надо?
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 не устарел морально и нужно смотреть, что для своей задачи использовать лучше.
И по поводу сравнение опций:
API поддерживает подготавливаемые запросы на стороне клиента: Нет(mysqli) Да(PDO)
Я считаю, что это важный пункт, оверхед на парсинг запроса конечно не так велик, но всё зависит от проекта. По крайней мере из таблицы я не смог увидеть минусов PDO.
Подготавливает SQL запрос к базе данных к запуску посредством метода PDOStatement::execute(). Запрос может содержать именованные (:name) или неименованные (?) псевдопеременные, которые будут заменены реальными значениями во время запуска запроса на выполнение.
Вызов PDO::prepare() и PDOStatement::execute() для запросов, которые будут запускаться многократно с различными параметрами, повышает производительность приложения, так как позволяет драйверу кэшировать на клиенте и/или сервере план выполнения запроса и метаданные, а также помогает избежать SQL инъекций, так как нет необходимости экранировать передаваемые параметры.
Таким образом, это и подтверждает то, что я хотел сказать, а именно: для проекта, где один и тот же запрос может вызываться много раз с разными параметрами, то подготовка запросов будет выгоднее, т.к не будет постоянного парсинга запроса. По этому я и сказал, что оверхед при выполнение одного подготовленого запроса незначителен, а если этот запрос выполняется n раз, то лучше сократить издержки.
Поищите как работают эмулированные.
По поводу повторных запросов.
Это работает только с подготовленными выражениями на сервере.
В случае одинарных запросов производительность просядает, поэтому и используется по умолчанию эмуляция.
Также я не особо понимаю смысла однотипных запросов, если можно выполнить один запрос с WHERE `column` IN (val1, ..., valN).
По Вашей ссылке также:
>Замечание:
>Эмулируемые подготовленные запросы не создаются на сервере баз данных, поэтому PDO::prepare() не может проверить правильность построенного запроса.
Да, логично что 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 это не имеет смысла, ибо подготовка жрёт ресурсы, а так как она будет выполняться при каждом запросе к скрипту смысл в ней отпадает, если вы конечно не демона на пыхе пишете.
Подготовка имеет смысл в приложениях, где она происходит один раз — при старте приложания (например в яве [если запросы не динамические])
php.net/manual/ru/mysqlinfo.api.choosing.php
Поставил плюс в карму исключительно авансом, а то коллеги прибегут да раскритикуют, что потом никакой «рекавери» не поможет. А желание поделится наказывать не стоит.
По сути:
1 — у Вас заметный пробел в базовых понятиях. Вы почитали документацию, но одной документации мало. Например понятие статики у вас несколько искаженное. «Метод существует у класса или у объекта»? что простите?) К данным это условно применимо, но не к методам. Почитайте какой-то учебник. А вообще постарайтесь минимизировать количество статических вызовов. для начала можете сделать синглтон. А вообще лучше использовать какой-то сервис-локатор. Тогда Вы сможете передавать класс работы с базой (обертку над ПДО) своим активрекорд или построителям, что даст Вам возможность делать разные конекты для разных таблиц, в разных базах (в том числе на разных СУБД).
2 — про устаревшую библиотеку равно как и сам подход с эскейпингом отставший лет на 5-7 вам уже сказали. PDO для Вас выбор обязательный.
3 — изучите получше принципы SOLID и DRY. Это не просто свод правил. Это практические принципы улучшающие любой код.
3.1. — стоит отделять генерацию запросов от логики самих активрекорд. Это даст Вам возможность просто добавить еще один класс для другой СУБД просто слегка подправив запросы, но не потеряв совместимость с исходным мускулом.
3.2. — get_class_vars() работоспособен здесь и сейчас. Но как только я расширяю класс дополнительным функционалом, добавляю новые статические поля, и весь код поломается. Вообще для простоты советую создать метод или статическую переменную с массивом имен полей, а сами поля хранить в другом массиве (не статическом), отдавая из через _гет и _сет.
3.3. — имя поля с ид определяется исходя из того кто первый? эм… серьезно? Ну опять таки, даже если и так, то стоит такую вещь вынести в отдельное место, отдельным методом, чтобы при изменении логики не менять весь код. Вообще этот подход стоит применить везде. Вот захотите вы сделать метод поиска по произвольному условию, и что? писать новый метод? И для изменения метод, и для поиска, и для удаления...?
ПС: а вообще продолжайте. Если будете учиться на своих ошибках, то быстро научитесь.
ППС: не читайте перед обедом советских газет и людей с большими научными регалиями. Как правило их знания в ИТ успевают устареть на несколько поколений, чему пример ваш подход с эскейпингом вместо ПДО.
2 — про устаревшую библиотеку равно как и сам подход с эскейпингом отставший лет на 5-7 вам уже сказали. PDO для Вас выбор обязательный.
Насчет подхода согласен, а вот про библиотеку, см. комментарий выше
Нету 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]
Также отступы слева во всех примерах кода стоило бы уменьшить.
Ну такое.
Не нравится тренд, найдите пост, где я продвигаю свой тренд, и там насрите.
У тенденции есть одна оговорка — если рядом с вами нет тех кого вы считаете хандонами — но вы про кого то все равно думаете что он хандон — у вас скилл притягивать хандонов на большом расстоянии.
Тенденция действует на всех. Но вам насрать ведь верно? вы опять какую-нито чушь сейчас сморозите.
Да и вообще ORM — фигня
У автора нет ORM.
является результатом долгих мучений
Собственно чем не подошли существующие аналоги?
www.phptherightway.com/#databases
Буду лучом света в этом чане нечистот комментариев. (не воспринимать как абсолютное одобрение)
Вполне нормальная статья для начинающего.
Про злобных комментаторов: не обращай внимания, просто ровно все они писали подобные велосипеды поначалу (а то и хуже) и им так же говорили, что они не правы, вот теперь пришла их очередь наставлять на путь истинный. (Пруфы? Пруфов не будет — все и так это понимают)
Про комментаторов, чьи слова порядком непонятны: временно забудь, повелосипедируй ещё немного в своё удовольствие, потом потыкай какой-нибудь готовый, поддерживаемый сообществом framework (только трупы не тормоши), потом прочти книгу банды четырёх, затем стоит прочесть "Шаблоны корпоративных приложений" и просветление придёт к тебе.
И на будущее: не выкладывай свои велосипеды сюда (по крайней мере пока они полсотни звёзд на гитхабе не наберут, или ты не будешь в них абсолютно уверен), карму потом восстанавливать устанешь.
Ну и добро пожаловать на хабр!
Очередная обертка над mysqli? Зачем? Сколько было подобных статей, и каждую из них сливали…
Вот лучше бы изучили какую-нибудь доктрину, или eloquent...
ORM на php для MySQL, реальность (часть первая)