Pull to refresh

Comments 28

Очень полезная статья! Года так для 2000-го…
Просто машина времени какая-то! :)


FanatPHP Ну это же перебор, ты же сам на клубе никому не советуешь юзать напрямую мускуль методы.

Очень правильный комментарий. Я и сам еле домучал этот перевод, разрываясь между тем что с одной стороны, он давно опоздал, а с другой — в том-то и проблема, что нормального мануала так до сих пор нет, все говнякают по-старинке, по-быстрому переписав код с mysql_query(). В 2022.


И в этом смысле лучше позже, чем никогда.
Я бы сказал так — в ленте этот материал лишний, но на Хабре он должен быть. Из ленты он быстро уедет, а на сайте останется, будет куда гуглу нубов привести.


Ну и надо сказать что даже в 2010-м половины этого туториала бы не было. Про mysqli_report() я узнал в 2013, распаковка аргументов появилась в 2014, а без неё функция prepared_query получилась бы на два экрана отборного говнокода. mysqlnd стала стандартом для сборок РНР буквально пару лет назад, а без неё нет get_result(), а это значит снова пара экранов говнокода, чтобы получить несчастный результат fetch_assoc(). Передача параметров в execute(), как в PDO, и вовсе появилась только в этом году.


Так что если говорить о юзабельной библиотеке mysqli, то в общем примерно как раз.


По поводу методов напрямую. Если говорить о работе, то разумеется, никто и не использует. Но речь-то про обучение. Когда человек учится, то это как раз нормально — применять методы напрямую, чтобы потом, используя ORM, понимать как оно работает под капотом. А то сейчас развелось "специалистов" по Ларавели, которые за всю свою успешную карьеру SQL запрос в глаза не видели.

mysqlnd стала стандартом для сборок РНР буквально пару лет назад

Ну как бы mysqlnd стандарт де факто уже давно еще с пхп 5.5 если не раньше. Но не будем здесь срач разводить, хабр не клуб и срачи тут не любят :)

Никак нет, стандартом де-факто mysqlnd стал только недавно. До начала 2020-х топики "Call to undefined function mysqli_get_result()" появлялись очень часто. Только с появлением семёрки mysqlnd стал доминировать в сборках, а вот в восьмёрке уже практически вытеснил libmysql совсем.

Да, спасибо за материал. Мне пригодилось.

Или все действительно так плохо со знаниями у джунов, раз нужны такие статьи?


P.S. Ник такой же как на клубе здесь у меня давно забанен :)
P.P.S. Писать могу редко, потмоу ответы по эстонски :)

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


Дело как раз не в джунах. А в том что даже офигевающие от собственной крутости синьор-помидоры на Тостере пишут такую же ересь, "or die()" и вот это вот всё. Потому что "пыха — это же язык для лохов", и кто угодно в нём эксперт. А то что этот "эксперт" студентом видел пару строчек говнокода и с тех пор с языком не сталкивался — это его не волнует.

Дело как раз не в джунах. А в том что даже офигевающие от собственной крутости синьор-помидоры на Тостере пишут такую же ересь, "or die()" и вот это вот всё

Это да боль и печаль.


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


Только с появлением семёрки mysqlnd стал доминировать в сборках, а вот в восьмёрке уже практически вытеснил libmysql совсем.

Ну как бы сами разработчики пхп сразу после выпуска mysqlnd говорили что юзайте его а старый вариант будет депрекейтед и неподдерживаемым (и если не ошибаюсь это было еще в 2009 году с выходом 5.3). Т.е. адекватный разраб должен был адаптировать свой код для новых реалий. Плюс в репах всегда был выбор или ставить легаси php-mysql (юзающий мускульную либу) или php-mysqlnd. Так что юзанье старья это проблемы сборщиков таких чудных сборок, а не проблемы системы. Т.к. в RHEL деривативах, учитывая что пхп почти всегда юзается Remi реп, проблем c php-mysqlnd никогда не было. Уверен то же самое и для дебиан/убунту дистров.


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

Добавлю, что начиная с PHP 8.1 режим MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT используется по умолчанию.
Были уже вопросы на тостере вида «А чего это вместо моего любимого сообщения из die() какая-то фигня выводится?».

Следовательно, вы и не должны писать код, который проверяет результат выполнения запроса — в случае ошибки mysqli сообщит о ней автоматически, благодаря функции mysqli_report() о которой шла речь выше. 

Пока вы не начнете писать API.

Это кстати интересная тема.
Но для начала надо понять, какое API имеется ввиду — так сказать, для внутреннего или для внешнего потребления. Если для внешнего — то ни одной причины проверять запросы вручную нет, весь форс-мажор отрабатывается единообразно.
А вот какие-то внутренние API, пакеты для использования в чужом коде — вот тут да, интересно. Но я бы тоже скорее обрабатывал возникающие в пакете ошибки глобально, на уровне пакета. И выкидывал какое-нибудь MyPackageException, к котором прицепом идёт уже исходное исключение.

Я о том, что вывод ошибки может быть не в виде текста/html, а скажем в JSON/XML. Более того, формат вывода может зависеть от входных параметров. Ну и не всегда при ошибке в каком-то SQL запросе нужно валить весь процесс без суда и следствия.

Помилуйте, но мы же здесь говорим не про вывод, а про выброс. Наше дело сообщить об ошибке, а как она там будет выводиться — дело десятое. Как напишем — так и будет. На домашнем компе пусть полный трейс с расцвеченным куском кода вываливает. А на бою — надо HTML, покажем HTML, надо XML — будет XML. А понадобится в JSON — не проблема переписать на JSON. Причём в одном месте, глобальном обработчике ошибок, который один за вывод и отвечает — а не за каждым запросом потом с лопатой ходить. Это же банальная эффективность труда программиста.

Чтобы не быть голословным, вот пример, который как раз и предназначен для вывода в HTML, очень легко переделать под JSON:


<?php

error_reporting(E_ALL);

function myExceptionHandler ($e)
{
    error_log($e);
    http_response_code(500);
    if (filter_var(ini_get('display_errors'),FILTER_VALIDATE_BOOLEAN)) {
        echo $e;
    } else {
        echo json_encode(['error' => true, 'code' => 500]);
    }
    exit;
}

set_exception_handler('myExceptionHandler');

set_error_handler(function ($level, $message, $file = '', $line = 0)
{
    throw new ErrorException($message, 0, $level, $file, $line);
});

register_shutdown_function(function ()
{
    $error = error_get_last();
    if ($error !== null) {
        $e = new ErrorException(
            $error['message'], 0, $error['type'], $error['file'], $error['line']
        );
        myExceptionHandler($e);
    }
});

И теперь, после добавления этого кода в проект, любая неперехваченная ошибка будет отдaвать аккуратный JSON!


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

Отлично, как раз не хватало его в статье ;)

$result->fetch_assoc()

while ($row = $result->fetch_assoc()) {
    $users[] = $row;
}

fetch_assoc по мне так уже лишнее, query возвращает mysqli_result типа IteratorAggregate в 8.0+ или Traversable до 8.0, поэтому можно просто

foreach ($result as $row) {    
    $users[] = $row;
}

Кстати, отличное замечание! Я почему-то думал, что там возвращается MYSQLI_BOTH, как в fetch_array() по умолчанию — но нет, там жёстко зашито MYSQLI_ASSOC. Полезная информация!


Хотя с другой стороны, я только что себя поймал на том, что привыкая в последнее время всё писать явно, без магии, я предпочту написать "лишний" вызов fetch_assoc(), просто для ясности. Но это уже конечно отдельная тема :)

Оба варианта имеют место быть. Мне кажется, что введение Traversable, а потом и IteratorAggregate это как раз путь к получению ожидаемого поведения вмысле типизации.
У нас щас минимальная версия в проектах это 7.4, а где то уже и 8.1. На fetch_assoc я "забил" и сокращаю до foreach. У - Удобно!

Мне вот интересно, а как много сейчас в проде напрямую юзают мускуль функции?
Ведь все равно даже при ручных запросах к базе все равно получается много повторяющегося кода. prepare, bind, query, fetch. В 8-ке в некоторых случаях можно от fetch избавиться, но все равно кода дофига. А это значит будет юзаться или стороняя или собственно написанная либа/хелпер в качестве прокладки. И тогда какая разница нужен fetch_assoc в результате или не нужен. Все равно это внутреннее апи хелпера.

Довольно часто. Во-первых, индусы, у них копипаста — это основной рабочий инструмент. Во-вторых, даже в проектах, интенсивно использующих ORM, сплошь и рядом из него достают объект соединения с БД (DB::connection()->getPdo()), и выполняют запрос напрямую, когда запрос слишком сложный или по каким-то другим причинам нежелательно использовать средства ORM. Но это практически всегда будет PDO. ORM-ов на основе mysqli я не видел.

Ну даже когда речь идет об индусском коде и если мне надо будет написать больше 10 запросов в коде, я напишу свой хелпер (если конечно его надо будет писать, а не использовать что-то готовое из симфони или что-то другое живое/популярное)


Даже когда речь об орме, почти всегда он работает поверх какой либо другой абстракции (по крайней мере абсолютно все популярные реализации), где не надо делать prepare, bind, query, fetch, а есть один метод query (или несколько различных методов для разных типов запросов) сразу с биндингом и фетчингом. Вызов одного метода это явно проще чем вызов 4 методов. И это стало стандартом де факто для любого более менее адекватного орма уже давно (еще с первых версий зенда)

ORM-ов на основе mysqli я не видел.

Как минимум Doctrine и Eloquent. У них под капотом doctrine/dbal, которому вообще пофигу какой драйвер стоит. Да хоть OCI или ещё что.

Это как раз то о чем я писал выше.
Поэтому у меня и стоял вопрос, а разве кто-то сейчас пользуется mysqli функциями напрямую, а не какой-либо абстракцией поверх

Как-то я очень сомневаюсь за елоквент, мне как раз казалось что в области баз данных у Тейлора собственная гордость и синдром "not invented here".


А насчет доктрины — с одной стороны всё равно какой драйвер, там действительно = хоть редис, хоть сфинкс, но с другой — нужно же писать адаптер. А писали ли кто-нибудь для доктрины адаптер mysqli — это вопрос.

Спасибо, вижу! Вполне законный драйвер, не то что какая-то поделка на коленке для Елоквента.

Кроме того, переводчик, также как и вы, считает, что PDO является более продвинутым API для работы с БД, чем mysqli.

Крайне сомнительное заявление.
Во-первых, откуда взялось сравнение между мягким и теплым, а, во-вторых, как абстракция, которая призвана впихнуть невпихуемое за 1 общий интерфейс может быть лучше, чем реализация для конкретной СУБД?

Да и что такого есть в PDO, что может перекрыть mysqli/pq? Зато в обратную сторону такая функциональность внезапно есть.

Единственный плюс PDO - это теоретическая возможность смены СУБД, что в реальности не даёт ровным счётом ничего, так как в любом +/- адекватном проекте в запросах все равно фигурируют платформо-зависимые функции, да и смена базы данных - крайне редкая затея в принципе.

Спасибо за интересный комментарий! Я с удовольствием отвечу по всем пунктам.


Сначала — откуда взялось сравнение. Очень просто, mysql до сих пор является основной БД в РНР приложениях, а для работы с этой СУБД в РНР как раз и используются эти два драйвера.


Рекомендация же использовать PDO — вещь довольно распространённая. Скажем, на Стаковерфлое практически под каждым постом про mysqli добровольные агитаторы всегда поставят плашку "все равно только начинаете учить, не тратьте время на mysqli, лучше сразу начинайте ПДО". Да и на Хабре часто можно было встретить такой комментарий. И в целом среди разработчиков такая идея была довольно распространённой, я и сам к ней руку приложил. И, надо сказать, до последнего времени такое отношение было вполне оправданным.


Тому было две причины:


1. Юзабилити


Во-первых, при своем появлении, "конкретная реализация" mysqli не могла составить конкуренции вообще никому. Это была тонюсенькая надстройка, как это принято в ПХП, над официальным API MySQL. Из-за чего простейшие операции требовали немыслимое количество кода, и в итоге подготовленные выражения могли использовать только отчаянные мазохисты. Один только факт, что при использовании подготовленных выражений нельзя было вызвать краеугольную функцию всея похапе — mysqli_fetch_array()! Использование подготовленных выражений, а особенно — в виде минимальных абстракций, когда мы заранее не знаем конкретный набор передаваемых в запрос параметров или получаемых из БД полей — превращалось в несколько экранов отборного говнокода.


При том что PDO как раз с самого начала предоставляла почти идеальный интерфейс по работе с подготовленными выражениями, когда некоторые подготовленные запросы можно было выполнить буквально в одну строчку, $pdo->prepare($sql)->execute($params);. Не говоря уже об огромном наборе готовых хелперов, которые позволяют получить результат запроса сразу в десятках различных форматов, от значения единственной колонки, типа count(*) до замысловатых группировок.


У mysqli же заняло очень много времени развиться в что-то юзабельное, и осязаемые результаты начали появляться совсем недавно. О чём, кстати, и говорится в данной статье. Ключевыми моментами были:


  • открытие сообществом режима выброса исключений, и выставление его по умолчанию в РНР 8.0. При том что целые поколения разработчиков проверяли результат запросов mysqli по-старинке, через or die(...) — и, кстати, продолжают и сейчас, хотя это стало совершенно бесполезно
  • появление драйвера mysqnd, в котором появилась, наконец, возможность использовать fetch_array() с подготовленными выражениями (правда всё равно через отдельную функцию-прокладку) плюс несколько не таких значительных улучшений
  • добавление в РНР 5.6 оператора распаковки аргументов, который позволил наконец-то делать то, что изначально было в PDO — относительно просто передавать в запрос заранее неизвестное количество аргументов без плясок с бубном прыгая на одной ножке в ночном горшке.

Только начиная с 8.2 mysqli впервые в истории начнет в чём-то превосходить PDO в плане юзабилити, позволяя выполнять подготовленные SELECT-ы в одну строчку, чего до сих пор не умеет PDO. Хотя учитывая, что для большинства киллер-фичей PDO являются именованные параметры, то mysqli в этом смысле не догонит PDO вообще никогда.


2. Таки универсальность


А во-вторых, в пользу PDO играла возможность работать с разными БД. Это практически никогда не нужно для конкретного приложения, но вот для любой надстройки для работы с БД является просто киллер-фичей, ради которой стоит пожертвовать парочкой малоизвестных особенностей специфических драйверов. Практически все современные ORM, DBAL, просто хелперы — базируются на PDO. И "всеядность" современных фреймворков в плане работы с различными СУБД зиждется именно на этой фиче.


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

Sign up to leave a comment.

Articles