Всем привет! Наверняка каждый из тех, кто много и постоянно пишет на PHP сталкивался с вопросом оптимизации и упрощения запросов в базы данных MySQL. Кто-то написал уже себе удобные классы/процедуры, кто-то нашел что-нибудь на просторах сети.

Поскольку у меня скрипты на PHP все больше и больше начинают сворачиваться к одной задаче — выборке из базы данных и передаче этих данных клиентским Java-скриптам, я себе облегчил участь тем, что создал удобный (для меня, конечно) класс по работе с базами данных MySQL.

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

Вашему вниманию предлагается класс exDBase, это по сути своей оболочка для класса mysqli. Сразу оговорюсь — я программист начинающий, и готов в комментариях или личных сообщениях получить массу критики за написанный код. Я не очень владею RegExp например, которые сильно бы упростили код, возможно есть и другие претензии. Но, тем не менее…

Вся библиотека содержится в одном файле — exdbase.php. Это файл содержит описание класса exDBase. Чтобы начать работать, нужно просто, скачав файл, прописать такую строчку:

require_once ('exdbase.php');

Для начала создадим экземпляр класса, это очень просто:

$DB = new exDBase (DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
if ($DB->error)
    echo "Ошибка соединения: $DB->error";

В свойстве $DB->error всегда будет ошибка последней операции (если была ошибка), либо оно будет пустым.

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

Теперь можно и поупражняться. Допустим у нас есть готовая база данных, и в ней есть таблица clients с полями:

ID — уникальный номер, автоинкремент
NAME — имя клиента
AGE — возраст клиента
AMOUNT — сумма покупок
BLOCKED — булева переменная, заблокирован клиент или активен
SETTINGS — личные параметры, мы их храним в формате JSON

Запрос в базу данных


Давайте получим все записи из таблицы 'clients'. Для этого существует метод fetchArray.

$res = $DB->fetchArray ('clients'); // получаем все записи в виде массива ассоциативных массивов
if ($res)
    foreach ($res as $client)
        echo print_r ($client, true); // выдаем все записи на экран

А если мы хотим получить только первую запись запроса? Для этого есть метод fetchFirst.

$res = $DB->fetchFirst ('clients'); // получаем первую запись в виде ассоциативного массива
if ($res)
    echo print_r ($client, true); // выдаем первую запись на экран

Но, нам вряд ли понадобится получать все записи из таблицы, всегда есть условия отбора (поле WHERE в команде SELECT). Как нам поступить? Да очень просто. Это второй аргумент методов fetchArray или fetchFirst.

Допустим, что мы хотим выбрать всех клиентов с именем John. В нашем классе это можно сделать двумя способами.

Первый — просто задать условие строкой вида «NAME = 'John'»

$res = $DB->fetchArray ('clients', "NAME = 'John'");

Второй — задать условие массивом:

$res = $DB->fetchArray ('clients', array ("NAME" => "John"));

А если есть еще условия? Например, возраст должен равняться 30 лет? Легко:

$res = $DB->fetchArray ('clients', array ("NAME" = "John", "AGE" => 30));

Таким образом можно объединять несколько условий поиска. Но равенство… А если мы хотим найти всех клиентов с именем John, которые старше 25 лет? Тут на помощь приходят специальные префиксы:

$res = $DB->fetchArray ('clients', array ("NAME" = "John", ">=AGE" => 25));

Кроме ">=" вы можете использовать: ">", "<", "<=", "!=", "<>", "!=". Таким образом можно создавать запросы разной степени сложности и всегда получать нужные ответы.

Третий параметр методов выборки из базы данных — это поля таблицы. Их можно задать как строкой (например: «NAME, AGE»), так и массивом: array («NAME», «AGE»).

$res = $DB->fetchArray ('clients', array ("NAME" = "John", ">=AGE" => 25), array ("NAME", "AGE"));

Четвертый и последний параметр методов выборки fetchArray и fetchFirst это порядок сортировки. Он также задается либо строкой (типа: «ID ASC, NAME DESC») либо массивом array («ID» => «ASC», «NAME» => «DESC»).

$res = $DB->fetchArray ('clients', array ("NAME" = "John", ">=AGE" => 25), array ("NAME", "AGE"), array ("ID" => "ASC", "NAME" => "DESC"));

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

Это делается методом setLimit(), вот так:

$DB->setLimit (10);
$res = $DB->fetchArray ('clients', "NAME = 'John'");

Метод setLimit() работает только на один запрос, после этого лимиты обнуляются.

Вставка новых данных


Для записи новых данных существует метод insert().

$id = $DB->insert ('clients', array ("NAME" => 'Peter', "AGE" => 27, "AMOUNT" => 1000.25));

Он возвращает значение первичного ключа автоинкремента (если такой задан в таблице). В нашем случае он вернет ID вставленной записи.

Обновление данных


Обновление данных осуществляется методом update().

$DB->update ('clients', array ("NAME" => 'Peter'), array ("AGE" => 30, "AMOUNT" => 2000.25));

Мы обновили все записи где имя (поле NAME) — это 'Peter'. Второй аргумент метода — это условие выбора, точно в таком же формате как WHERE для SELECT. Ну, а третий аргумент метода — это сами данные. Теперь у всех таких записей с именем 'Peter' возраст будет равен 30, а сумма — 2000.25.

Удаление данных


Если вы уже поняли логику работы библиотеки, то удаление дастся очень просто. Метод называется delete().

$DB->delete ('clients', array ("NAME" => 'Peter'); // удалить все записи с именем 'Peter'
$DB->delete ('clients', array (">AGE" => '20'); // удалить все записи с возрастом больше 20.

Вот такой вот первый краткий экскурс в библиотеку exDBase. Существует еще целый ряд других, более продвинутых функций, но об этом в другой раз.

Скачать файл можно по ссылке
(Выберите загрузить файл по ссылке как ...)

Всем хорошего кода!

Комментарии (60)


  1. Stalker_RED
    20.08.2018 17:32

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

    Я просто оставлю это здесь:
    php.net/manual/ru/pdo.prepared-statements.php
    ru.wikipedia.org/wiki/ORM


    1. zelenin
      20.08.2018 17:57

      Странно, что вы не нашли ни одной готовой

      даже на хабре подобных статей было вагон и маленькая тележка.


      1. kricha
        20.08.2018 18:03

        так это для хабра или все-таки для облегчения собственной работы? Но сути не меняет, такое уже есть и даже более интуитивное, чем ваш вариант.

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


  1. alexxxst
    20.08.2018 17:33

    Всё плохо :(


  1. maximw
    20.08.2018 17:34
    +1

    Велосипедостроение это хорошо для практики. Но почитайте про ActiveRecord, ORM, Query Builder. Реализаций этого добра много, есть из чего выбрать.


  1. unnutz
    20.08.2018 17:35
    +1

    Чтобы начать работать, нужно просто, скачав файл, прописать такую строчку
    composer

    А если посмотреть сюда, то можно увидеть список готовых велосипедов, все же 2018.


    1. staticlab
      20.08.2018 20:19
      +2

      Кстати его и скачать нельзя, потому что он выполняется на сервере :D


    1. sambusak
      21.08.2018 11:12

      Отличная подборка велосипедов! thx!


  1. lemproix
    20.08.2018 17:35

    Есть такая хорошая штука как (GitHub/Gitlab/Bitbucket/etc) и посмотрите пожалуйста ее, там всё это "добро" уже есть !


  1. rraderio
    20.08.2018 17:36

    Метод setLimit() работает только на один запрос, после этого лимиты обнуляются.

    $DB->setLimit (10);
    // т.е. если кто-то добавит новый код, то все может поломаться и у запроса ниже уже не будет LIMIT?
    $res = $DB->fetchArray ('clients', "NAME = 'John'");
    


  1. SDKiller
    20.08.2018 18:03

    Кто-то еще качает файлы по ссылке?
    Если уж хотите, чтобы люди ознакомились с вашим кодом, залейте на гитхаб


  1. kireevco
    20.08.2018 20:16
    +4

    Java-скриптам
    Это про JavaScript или про Java приложения?


  1. zenn
    20.08.2018 20:33
    +6

    Зачем «это» пропустили из песочницы… Я все понимаю, у всех разный уровень знания языка, но это ведь явный пример того, как не следует делать в 2018ом году. Просьба — верните «это» обратно в песочницу.


    1. Gesper
      20.08.2018 23:03

      Честно говоря, уже в 2010м так делать было не принято. То, что в статье показано — это уровень 90х. Хотелось бы код увидеть, но, судя по всему, автор не очень понимает как работает сервер и скачать исходник его обертки нам не светит


      1. Stalker_RED
        22.08.2018 11:57

        Кстати, в 2010 году Doctrine исполнилось 4 года, а Propel пять.


  1. stychos
    20.08.2018 20:54
    -1

    Ну хотя бы подобие ActiveRecord есть, уже неплохо. Следующий шаг — поддержка разных СУБД и маппинг на объекты.


    1. VolCh
      20.08.2018 22:05

      ActiveRecord это и есть маппинг )


      1. stychos
        20.08.2018 22:07
        -1

        Я имел в виду CodeIgniter-style ActiveRecord, там, конечно, скорее QueryBuilder, но тогда это так называлось :-) Хотя не уверен, поддерживает ли код автора Chaining вызовов, кода-то нет.


        1. zelenin
          21.08.2018 12:39

          вы разные вещи смешиваете. QueryBuilder — это построитель запросов. К объектам отношения не имеет.


          1. VolCh
            22.08.2018 12:42

            Точнее к маппингу записей базы на объекты и обратно отношения не имеет, если говорить о SQL QueryBuilder. Реализуют его обычно всё-таки на объектах, а не функциях :)


            1. zelenin
              22.08.2018 12:50

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


          1. stychos
            22.08.2018 22:17

            Вы тут какую-то демагогию развиваете. CodeIgniter ActiveRecord. Дохера занудами заумными все стали, а подумать о том, что конечному пользователю насрать, как оно там в деталях и отдельных частях называется — не судьба. А о том, что со временем и сами названия меняются — тем более.


            1. zelenin
              22.08.2018 22:35

              конечному пользователю насрать

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


              1. stychos
                22.08.2018 22:59

                А кто Вы, если не такой же типичный конечный пользователь фреймворков? Если не можете понять, что раньше понятия QueryBuilder и ActiveRecord отличались от нынешних и пеной у рта начинаете мне доказываете что я тут разные вещи смешиваю? А представляете? — некоторые помнят времена, когда в массах и mysql-биндингов в пхп не использовалось, и люди писали самодельные велосипеды на файлах.


                1. zelenin
                  22.08.2018 23:32

                  какой-то вы неприятный собеседник


                1. Fesor
                  23.08.2018 10:26
                  +1

                  раньше понятия QueryBuilder и ActiveRecord отличались от нынешних

                  Нет, раньше query builder как подход и active record как паттерн были тем же чем они являются сегодня. Это как бы суть названий и терминов — если они часто меняются, и у них нет четкого определения — то надо придумать новый термин с четким определением и перестать юзать что-то что вводит в заблуждение людей.


                  Причина по которой под ActiveRecord понимают все что угодно произрастает из безграмотности людей и подмены понятий (когда "паттерн" какой-то или идею подменяют конкретной библиотекой).


                  и люди писали самодельные велосипеды на файлах.

                  в 70-х? да, было такое. только тогда небыло ни mysql ни php да и концеп SQL-я только только зараждался. И нет ничего плохого в хранении данных в файлах, просто это сегодня имеет смысл только если вам надо специализированное хранилище (и вряд-ли вы что-то такое будете на php писать, хотя можно).


            1. Fesor
              23.08.2018 10:31
              +1

              А о том, что со временем и сами названия меняются — тем более.

              от того что вы юзаете code igniter термин query builder своего значения не поменял ни разу. Это штука которая позволяет вам строить запрос. причем за выполнение этого запроса или за работу с result set оно уже не отвечает.


              Причем, для того что бы вы могли с result set работать вам нужна какая-то информация о запросе (интроспекция) и проще всего это сделать добавив свой строитель запросов который будет эту инфу предоставлять для других компонентов вроде мэпперов каких и т.д. Ну и что бы не писать что-то типа $db->execute($query) и т.д. можно скрестить это дело и с коннекшен менеджером.


              Но это просто конкретное решение конкретной библиотеки и никакого отношения к терминологии все это не имеет.


              Что до "конечного пользователя" — проблема штук типа CI ActiveRecord в том что это создает ограничения применимости решений. Иногда хочется и не через PDO поработать а через какой-то асинхронный драйвер на модных нынче свуле, и оказывается что 99% всех квери билдеров это подкопирку содранные друг к друга куски гуано которые так просто не реюзать.


        1. VolCh
          23.08.2018 13:54

          Авторы CodeIgniter назвали, судя по вашей ссылке, `CodeIgniter ActiveRecord` свою имплементацию паттерна QueryBuilder


  1. Quber
    21.08.2018 02:41

    Собственно в первом комментарии всё правильно написали. Я бы добавил еще composer и git.


  1. nefone
    21.08.2018 06:38

    Жаль не получается посмотреть, что там в коде.


  1. peresada
    21.08.2018 07:20

    Про бесполезную для сообщества работу уже написали, но раз уж Вы сами упомянули, что Вы начинающий программист, то позвольте небольшой Code Review:

    Не пишите на PHP как на Python
    $res = $DB->fetchArray ('clients'); // получаем все записи в виде массива ассоциативных массивов
    if ($res)
        foreach ($res as $client)
            echo print_r ($client, true); // выдаем все записи на экран


    1. peresada
      21.08.2018 07:26

      Еще забыл

      $res = $DB->fetchArray ('clients'); // получаем все записи в виде массива ассоциативных массивов
      echo print_r ($client, true); // выдаем все записи на экран

      Вы правда считаете, что подобное комментирование полезно?


    1. zzzmmtt
      21.08.2018 09:17

      «event и error» а как же catch (\Exception $e)?


      1. peresada
        21.08.2018 09:24

        аналогично, всегда бешусь, когда IDE автодополняет catch с названием переменной $e и ленюсь это поправить


      1. Free_ze
        21.08.2018 11:29

        А вообще часто эвенты в catch приходят?)


        1. peresada
          21.08.2018 11:35

          Если строить велосипед а-ля sentry, то вполне может быть :)


          1. Free_ze
            21.08.2018 11:44

            Это отвратительно(


        1. zzzmmtt
          21.08.2018 11:44

          Тут вопрос скорее в сокращении имен переменных, вместо $someError, $someEvent, $someException — везде могут написать $e, и разбирайся потом как хочешь.


          1. Fesor
            21.08.2018 11:48

            ну у вас еще тип есть, который неплохо дополняет какое-то общее $error или $event. Опять же в этом случае нет смысла в суффиксах для типов (InvalidArgumentException вместо InvalidArgument).


            1. VolCh
              21.08.2018 12:29

              А бывает и суффиксов нет, и имя $e :(


            1. peresada
              21.08.2018 12:50

              Понятное дело, что IDE и знание проекта всегда помогут, всегда подскажут. Но если говорить о PHP, это часто подразумевает fullstack разработку, то есть периодически нужно и на JS переключаться, и с базой уметь работать. Так вот из-за того, что люди привыкают сокращать такие вещи или дают однотипные имена, получается неразбериха в том же JS, типа

              passwordInput.addEventListener('click', function(event) {
                var minClickAreaPercent = 0.8; // далее 80% от левого края срабатывает переключение
                var toggleIsAble = (event.offsetX / this.clientWidth > minClickAreaPercent);
              // какой-то код
              }

              В котором семантически правильней назвать аргумент функции не event (и уж тем более не e), а например mouseClick или mouseClickEvent, но да, людям лень потратить на 1\2 секунды больше на набор текста. К счастью, подобного рода проблемы не распространяются на долгоживущие проекты с постоянной командой разработки.


          1. Free_ze
            21.08.2018 11:59

            Кмк, если есть проблемы с идентификацией аргумента catch, то проблема вовсе не в этом. Я неместный в PHP, но события (если речь о «наблюдателе») через исключения кажутся крайне сомнительным решением. А сам язык позволяет указывать тип исключения. В отличие от функций, у catch едва ли можно подчеркнуть какую-либо семантику, которую не сможет выразить тип.


            1. zzzmmtt
              21.08.2018 12:22

              У меня лично проблем с идентификацией исключений нет, но именовать так переменные — не самый лучший вариант. Никто не запрещает использовать $e для ошибок, ивентов или исключений, но $someEvent будет гораздо понятнее, чем $e. Изначально у автора $res — resource или result? Предполагаем, что таки result, но автор мог и до $r тогда сократить, а тут уже гораздо больше вариантов интерпретации имени переменной другими разработчиками (как минимум row можно ещё предположить).
              Я и сам раньше грешил переменными типа $fido, $fids — мне понятно, что $fido — ресурс открытый fopen на чтение, fids — на запись, но потом сам начинаешь в этом путаться, переназначать случайно эти переменные.


              1. Free_ze
                21.08.2018 13:19

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

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

                Тянуть сюда аналогию с локальными переменными или формальными параметрами функций — можно, но, имхо, это уже педантизм)


                1. zzzmmtt
                  21.08.2018 13:35

                  Может быть и педантизм, но есть стандарты (PSR для php) и общепринятые практики, их было бы неплохо соблюдать, меньше придирок будет.


                  1. Free_ze
                    21.08.2018 13:52

                    Ну вот PSR как раз рекомендует $e.

                    К любым гайдам и стандартам нужно подходить критически, не отключать мозг, включая линтеры и автоформатеры. Некоторым людям порой свойственно изображать педанта и часами спорить о малозначимой ерунде. Это точно хуже, чем просто плохой стиль)


                    1. peresada
                      21.08.2018 14:07

                      PSR — это принцип форматирования кода, а не принцип семантического именования. PSR ругнется на название класса с маленькой буквы, но ему без разницы если класс называется Ee. В приведенном примере говорится лишь об отступах, скобках и порядке


                      1. unnutz
                        21.08.2018 14:37

                        Буду занудой, но PSR — это PHP Standard Recommendation. PSR-1 и PSR-2 действительно являются рекомендациями к форматированию и стилю кода, однако есть, к примеру, PSR-3, PSR-6 и PSR-7, которые описывают интерфейсы для логирования, кеширования и взаимодействия посредством HTTP-протокола соответственно.


                        1. VolCh
                          21.08.2018 16:14

                          Но они по факту тоже не объясняют и, тем более, не диктуют принципы именования. Они дают жёстко заданные имена, которые нужно использовать в конкретных кейсах для совместимости, но не задают даже имён наследников.


                      1. Free_ze
                        21.08.2018 14:41

                        Зачем же вы тогда сослались на него?) Ладно, предположим, что это недостаток конкретного документа (хотя судя по стайлгайдам других языков с нормальными примерами, никто еще не придумал внедрять семантику в имя переменной-исключения).

                        Хорошо, давайте исследовать «общепринятые практики» из живых проектов на PHP: YII, Composer, WordPress, Joomla и куча встреченных мной топ-проектов на GitHub (по звездам) используют "$e" или что-то не менее «семантичное», вроде "ex" или "err". Выходит, это авторское изобретение.


                        1. peresada
                          21.08.2018 14:54

                          Я не ссылался на PSR. А проблема заключается имхо в другом:
                          Выходит очередная статья, документация, книга, любая авторская работа. Если она становится популярна, как с точки размера аудитории, так и с точки зрения вклада в сообщество, то многие принимают ее за чистую монету и считают, что абсолютно все в этому труде нужно брать за эталон => в том числе берут оттуда такую информацию, которую автор возможно и не планировал донести.

                          Это явно прослеживается и в примере с try catch из PSR. Суть этого правила заключается в отступах, порядке и скобках, а саму переменную исключения добавили потому что без нее пример казался бы некорректным. Разумеется ее нельзя выкидывать даже для примера, поэтому по привычке добавили $e.

                          Именно по привычке, потому что еще раньше эту несчастную $e опубликовали в какой-нибудь известной статье, а автор этой статьи еще раньше прочитал известную книгу, где автор повествовал об обработке исключений, но решил сэкономить на печатном пространстве и сократил $exception до $e, ведь в этой книге вообще не было целей говорить о семантике и правилах хорошего тона в коде.


                          1. Free_ze
                            21.08.2018 15:13

                            Да, я не обратил внимания на то, как вы внедрились)

                            Это достаточно очевидное сокращение в данной синтаксической конструкции, чтобы для него воскрешать венгерскую нотацию. Еще и стандарт де-факто. Допустимая вкусовщина, а не хороший тон, кмк.


                    1. zelenin
                      21.08.2018 14:50

                      Ну вот PSR как раз рекомендует $e.

                      нет, он рекомендует расстановку пробелов и скобочек. То, что там написано $e, к стандарту не относится.


  1. Lure_of_Chaos
    21.08.2018 08:02
    +1

    Действительно, тема вечная: давно есть адекватные решения, но новички продолжают писать своих убийц pdo с sql injection и выборками в коде, но зато без *фатального недостатка*


  1. scalavod
    21.08.2018 10:42

    Получился бы отличный пост на первое апреля.


  1. MetaDone
    21.08.2018 10:58

    Как всегда ссылка на кошерный сборщик запросов — https://github.com/auraphp/Aura.SqlQuery


    1. Fesor
      21.08.2018 11:37

      Большинство php-ных строителей запросов чуть-чуть лучше чем тупая конкатенация строк. Увы. Неужто людям больше от жизни не надо?


      1. MetaDone
        21.08.2018 11:50

        видать задачи где подойдет именно «строитель запросов чуть-чуть лучше чем тупая конкатенация строк» простые и сводятся к обычным выборкам максимум с парой условий. Или же людям, которые занимаются в основном такими задачами, лень потыкать существующие отлаженные инструменты и проще сделать очередной костыль тупо под задачу и использовать его


      1. VolCh
        21.08.2018 12:21

        Если именно строители запросов, то что там особо больше ждать? Другое дело, если используются высокоуровневые абстракции типа ORM, Repository и Criteria.


        1. Fesor
          21.08.2018 13:38

          Где-то месяца 3-4 назад я тоже так думал а потом начал загоняться по всяким там возможностям выводить типы, верифицировать запрос (даже по частям) на соответствие схеме без необходимости запуска оного, вопросы композиции запросов (открыл для себя парочку вариантов на тупой конкатенации которые интереснее и удобнее чем то что предлагает большинство билдеров, через под запросы естественно). Ну и т.д.

          Что до абстракций типа ORM, репозиториев и критерий — все это прекрасно работает опять же в ситуации когда у тебя все выборки можно влипить в WHERE. без джойнов, подзапросов и прочих оконных функций. Чуть сложнее и абстракции эти перестают помогать (в том виде в котором они существуют в PHP) что опять же ставит вопрос «а нафига оно все надо!?» Ведь взять ту же доктрину (как бы я ее не любил) но на ее изучение нужно немало времени, в ней полно багов, это очень сложный продукт сам по себе… и тот факт что оно помогает мне только для очень простых задач меня очень расстраивает.