PHP+SQL начинающим: Повышаем уровень программирования.

    Данная статья сугубо практическая и затрагивает единственный аспект – как повысить уровень програмирования в PHP при работе с SQL базой данных (в дальнейшем мы постараемся затронуть и другие аспекты программирования) Сразу же стоит оговориться, что есть высокий уровень программирования, дабы избежать лишних упреков. Под выским уровнем понимается достижение цели при минимальном и быстро получаемом коде (количества строк кода, символов в строке), даже если это идет в ущерб эффективности использования вычислительных ресурсов.

    В настоящее время процессорное время, память и дисковое пространство стоит настолько дешево по сравнению с рабочим временем инженерного персонала, что им можно пренебрегать. Скорость разработки прикладной задачи гораздо важнее того, на сколько эффективно она работает. Если разработчик вместо 30 строк кода пишет только 10, то верятность ошибки снижется в три раза. Дальнейшее сопровождение и модификации более короткого кода тоже несомненно требует гораздо меньшего времени и человеческих ресурсов. Не забывайе, что мы не рассматриваем здесь системные решения, когда во главу угла ставится именно эффективность кода.

    В данном статье мы создадим обертку для стандартной библиотеки PHP mysql. Не составит большого труда самостоятельно адаптировать ее для других баз данных. Это не будет попыткой создания ORM. У ORM совсем другие задачи, которые идут в разрез с нашей целью повысить уровень программирования и сократить код.

    В качестве рабочего примера возьмем воображаемую таблицу данных пользователей следующей структуры:

    CREATE TABLE users (
     id int auto_increment,
     category tinyint,
     name varchar(25),
     password char(32),
     email varchar(100)
    );


    Напомним, что обычным способом получения данных из таблицы будет примерно такой:

    $result = mysql_query(«SELECT * FROM users»);
    $users = array();
    while ( $row=mysql_fetch_array($result) )
    {
     $users[] = $row;
    }


    Что-то подсказывает, что такая конструкция из 5-6 строк повторяемая от раза к разу может (и должна!) быть заменена на более компактную. Понятно, что внутри цикла мы на самом деле можем производить какие-то более полезные действия, чем просто накопление массива. Здесь вступет в силу парадигма высокоуровневого програмирования, которая говорит, что не стоит смешивать в одно такое системное действие, как выборка результатов из ресурса базы данных с его прикладным использованием. Если вы даже могли бы и обойтись без массива $users, то теперь вам придется его получить и уже дальше работать с ним в режиме прикладной задачи.

    Вводим новую функцию:

    $users = sql_get_rows("SELECT * FROM users");


    Ну вот и все. 5-6 строк кода, в которые еще надо вчитаться и понять, что они делают, заменены одной четкой и понятной строкой. Этот пример конечно чисто академический, на практике надо частенько как-то облагораживать получаемый результат. Например, мне хотелось бы получать напрямую доступ к строке с с заданным id. Рыскать по массиву в поисках нужного $users[‘id’] тоже не совсем правильно. Мы придусмотрели это:

    $users = sql_get_rows("SELECT * FROM users", 'id');


    Теперь в нашем массиве $users индексы идут не просто по порядку номеров, а соответствуют id данного пользователя. Мы пошли дальше и предусмотрели многомерные или лучше сказать древовидные массивы. Например вам нужно перечислить всех пользователей, но сгруппировав их по категориям. Пожалуйста:

    $users = sql_get_rows("SELECT * FROM users", 'category', 'id');


    Теперь рассмотрим частные случаи. Например вы знаете, что получите в результате всегда одну строку. Здесь мы предлагаем другую функцию:

    $user = sql_get_row("SELECT * FROM users WHERE id=$id");


    Почему другая функция. Ну во первых, если можно где-то ненароком повысить эффективность кода, то почему бы нет. Но это здесь не главное. Главное то, что человек, который будет читать этот код после вас, будет четко видеть, что результатом является одна строка. Кстати функция сама всегда добавляет в конце «LIMIT 1».
    Ну и еще одна вырожденная функция:

    $qty = sql_get_value("SELECT count(*) FROM users");


    Как вы поняли, она возвращет единственную скалярную величину.

    Для остальных запросов, которые не возвращают результата мы предусмотрели такую функцию:

    $id = sql_query("INSERT INTO users ...");
    $qty = sql_query("DELETE FROM users WHERE email='' ");


    Заметьте, что на самом деле они тоже возвращают результат, и очень даже полезный. Для INSERT'а возвращется код auto_increment’а. Для DELETE и UPDATE – количество обработанных строк. Осталось только заполнить пробелы в месте многоточия в примере выше. Там, как вы, понимаете должны стоять данные, которыми заполнаются поля.

    Вводим правило: данные должны поступать всегда в виде ассоциативного массива и никак иначе. Поверьте, это в всегда удобнее и гибче. Например вы прочитали строку из таблицы. Она приходит к вам в виде ассоциативного массива. Вы заменяте одельные значения, возможно удаляте какие-то поля (напр. ‘id’) и отдаете массив обратно на UPDATE или INSERT. Очень эффективно. Если увеличилось количество полей в таблице, то код практически нигде не надо подправлять.

    Вводим еще одну вспомогательную функцию:

    $set = sql_set($fields);
    $id = sql_query("INSERT INTO users $set");


    Не трудно догадаться, что функция sql_set() генерирует опцию SET со списком полей и данных: SET name='Vasya', password='56F54AC84',email='vasya@pupkin.com'

    Таким образом, введя несколько высокоуровневых функций, мы повысили уровень программы. Длина тех кусков кода, которе отвечают за работу с базой сократилась примерно 5-7 раз, но самое главное повысилась читабельность кода и его семантическая чистота. Мы гарантируем, что в обычной прикладной задаче (то есть в 99% случаев) вам более не понадобиться обращаться к фунциям низкого уровня. Данных функций будет достаточно для решения практически любых прикладных задач.
    В завершение приводим текст библиотеки (благо он совсем короткий). Любознательный читатель найдет там пару полезные вещей, которые не упомянуты в статье. Предупреждаем, что библиотека написана под error_reporting(E_ALL^E_NOTICE); Почему так – тема другой статьи о повышении уровня программирования. Но любой волен изменять в этой библиотеке все по своему вкусу.

    //=========================================================
    function sql_get_rows($query,$key_col=false,$key_col2=false)
    {
      $array= array();
      $res= sql_query($query);
      if (mysql_num_rows($res)>0)
      {
       if ($key_col)
       {
        if ($key_col2) while ($item= mysql_fetch_assoc($res)) $array[$item[$key_col]][$item[$key_col2]]=$item;
        else while ($item= mysql_fetch_assoc($res)) $array[$item[$key_col]]=$item;
       }
       else while ($item= mysql_fetch_assoc($res)) $array[]=$item;
      }
      mysql_free_result($res);
      return $array;
    }
    //=========================================================
    function sql_get_row($query)
    {
      $res= sql_query($query.' LIMIT 1');
      $array= mysql_fetch_assoc($res);
      mysql_free_result($res);
      return $array;
    }
    //=========================================================
    function sql_get_value($query)
    {
      $res= sql_query($query);
      $array= mysql_fetch_row($res);
      mysql_free_result($res);
      return $array[0];
    }
    //=========================================================
    function sql_query($query)
    {
     mysql_query($query) or sql_query_die(mysql_error(),$query);
     if (substr($query,0,6)=='INSERT') $res= mysql_insert_id();
     if (!$res) $res= mysql_affected_rows();
     return $res;
    }
    //=========================================================
    function sql_set($data, $skipslashes=false)
    {
      sql_protect($data);
      $set_text='';
      foreach ($data as $col=>$val)
      {
       if (!$skipslashes) $val= addslashes($val);
       $set_text.= ",`$col`='$val'";
      }
      $set_text= substr($set_text,1); // remove very first comma
      return 'SET '.$set_text;
    }
    //=========================================================
    function sql_query_die($error,$query)
    {
      $backtrace=debug_backtrace();
      foreach ($backtrace as $step=>$trace) if (substr($trace['function'],0,4)!='sql_') break;
      $step--;
      die(«SQL: $error. Generated at: {$backtrace[$step]['file']}:{$backtrace[$step]['line']}\nFull query: '$query'»);
    }
    * This source code was highlighted with Source Code Highlighter.

    Средняя зарплата в IT

    120 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 6 007 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

    Комментарии 45

      0
      Отформатируйте, пожалуйста.
        0
        Видимо в этот момент автор топика всё ещё исправлял статью. :)
        +1
        Имхо ActiveRecord намного удобнее
          +1
          использую AdoDB или SimpleDB и не знаю проблем
            –2
            Я конечно рад за вас с ActiveRecord,AdoDB или SimpleDB. Но это все ORM, задача которого нивелировать разницу в БД-х, а не повысить читабельность программы.
              0
              Почитайте прос симлп дб
              http://dklab.ru/lib/DbSimple/
              один из пунктов идеологии гласит: Библиотека не должна выравнивать различия между разными СУБД на уровне языка SQL.
                0
                Я был не прав по поводу DbSimple, автоматом перечислив в ряду с другими. Это не ОРМ и предлагает она ту же концепцию, что и я. Естественно глубже и с ООП, и т.п.
                +1
                Действительно??? Нет, вы правда в этом уверенны? Просто... я наверное хочу признатся, что у меня, к великому моему сожалению, вышел неправильный ActiveRerod, давайте я вам приведу небольшой пример, что бы вы сами поняли
                $user = new User();
                $user['username'] = 'root';
                $user['password'] = 'qwerty';
                $user->usergroup['name'] = 'admins group!';
                var_dump($user['usergroup'], $user->usergroup);

                $user->save();
                на что собственна в бд получим:
                user(id, username, password, usergroup, email):
                1231, root, md5(qwerty), 91231, root@thishost.com

                а в Usergroup(id, name):
                91231, admins group!

                в браузере увидим что-то вроде
                int 123

                object(Usergroup)[2]
                protected 'properties' => array(...)


                Эх, придется все переписывать... а было так удобно...
                  0
                  Я же не против ActiveRecord или чего-то другого. Я показал методику повышения уровня, причем сознательно без применения ООП. Есть поверье, что высокоуровневое программирование возможно только на обьектах, и надо учить и применять ООП, потому, что это мега-круто.
                    0
                    Это не мега круто. Просто это действительно удобно, правильно и имеет очень много плюсов (и примерно столько же минусов, ведь ничего соверешнного нету) по сравнению с процедурным стилем.

                    Попробуйте в процедурном стиле поработать с десятками связанных сущностей, которые разбросанны по нескольким источникам данных. Думаю что через три часа мучений вы первый побежите набрасывать два слоя абстракций модели данных и еще орм сверху.
                      0
                      Я совсем не против слоев абстракции. Просто каждому овощу свое время.
                      Если я был не понят с идеей статьи, то это моя вина. Плохо изложил. А народ пока пусть хоть chop()у порадуется.
                  +1
                  А вы вообще знаете что такое ORM? AdoDB или SimpleDB - это не ORM. ORM расшифровывается Object-relational mapping. Технология носит такое название, поскольку позволяет поставить соответсвие между реляционной БД и объектами домена вашего приложения (это совершенно необходимо при использовании модели предметной области). Это, как раз для улучшения кода в первую очередь и нужно. Вы просто попробуйте попользоваться такой системой для php: Doctrine, например, или Zend_Db_Table. Вы сразу почувствуете преимущества.
                +5
                А можно поинтересоватся, где тут облегчение мне жизни? То, что я теперь буду работать с голыми данными из базы, но используя другую функцию, если раньше мы писали так
                $result = mysql_query("SELECT * FROM users");
                $users = array();
                while ( $row=mysql_fetch_array($result) ) {
                  // много кода, в котором мы что-то делаем с row, к примеру
                }
                то сейчас мы будем писать так что-ли:
                foreach(sql_get_rows("SELECT * FROM users") as $row) {
                  // абсолютно тот же код.
                }

                Люди, наверное не зря придумали такие вещи как Абстракция бд, модель данных, activerecord, datamapper.

                Когда же вы научитесь учится сами, прежде чем учить чему-то других...
                  –4
                  Если вы знате и умеете пользоваться такими словами, как Абстракция бд, модель данных, activerecord, datamapper, то эта статья не для вас. Правда непонятно, почему вы не видите огромной разницы в тех двух кусках, что вы привели. Первый кусок невозможно корректно вписать в MVC, а второй - без проблем.
                    +1
                    Очень, очень хочется посмотреть на то, как второй кусок кода будет вписан в mvc(в народе: разделим наше приложение на шаблонизатор, самописный класс\функции для работой с бд, и будем их дергать в контроллере, кидая в шаблонизатор).

                    Почему же не вижу, вижу, в первом кусе я забыл убрать $users = array(), а во втором foreach появился ;)

                    И действительно, эта статья очень поможет начинающим программистам, дав понять, как не стоит писать, даже если "очень очень" хочется.
                      –1
                      То, какие данные достаютса из базы решается в модели, а как отображаются - во View. В вашем куске это не разделяемо в принципе.
                  +2
                  Кстати:

                  $rows = array();
                  while ($row = mysql_fetch_object($res)) {
                  $rows[] = $row;
                  }

                  Можно заменить одной красивой строчкой:

                  for ($rows = array(); $row = mysql_fetch_object($res); $rows[] = $row);

                  И ещё: нашёл красивый способ убрать последнюю занятую:

                  $str = "one, two, ";
                  $str = chop($str, ", ");

                  Оно какбэ почти звучит как предложение: «отрезать запятую в конце».
                    –2
                    > for ($rows = array(); $row = mysql_fetch_object($res); $rows[] = $row);

                    Образец того, за что надо бить линейкой по рукам.
                      +3
                      А ваш код, наверное, образец, с которого нужно писать стандарты кодирования ;)
                        +1
                        Простите, за что?
                          –2
                          Потому же, почему в академическом примере чередования 1 и 2 в цикле нельзя делать как 3-$i.
                            0
                            Но пример уважаемого посмотреть профиль mr_stupid даже соответствует тому, что вы пропагандируете:

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

                            Почему же нужно бить линейкой по рукам?
                              –2
                              Вы же понимаете, что эти слова означают повышение читабельности программы, а не чисто количественное ее сжатие во чтобы ни стало. Уважемый mr_stupid понизил читабельность.
                              +2
                              Смею предположить что на C/C++ вы никогда не писали. А очень зря, ибо пример mr_stupid-а очень нагляден и очевиден.

                              кидайте вы это php, и учитесь нормальным языкам.
                                –1
                                Для справки: за 20 лет программистского стажа писать на всякихх приходилось. И сейчас пишу не только на PHP. А вот статью про PHP написал. Не возражаете?
                                  +1
                                  Для справки: за 15 лет программистского стажа писал на многих языках.

                                  Легче относитесь к критике, и не считайте что вас окружают одни школьники.

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

                                  И вообще, надо радоваться.
                          0
                          за chop() спасибо, не знал о таком финте))
                            0
                            это не финт, а алиас rtrim, она еще с 4-й версии имеет необязательный параметр со списком символов.
                            $s = rtrim($s, "\r\n");
                          +2
                          А за меня зендфреймворк думает.
                            0
                            Ваш sql_set не оптимален, т.к. к примеру он берет в скобки системные функции например md5(), NOW() и прочие.
                            print sql_set(array("name" => 'disc', 'pass'=>"md5('disc')"));
                              0
                              Я пишу выше именно про SQL функции, пример с мд5 возможно не удачен, просто первое что скопипастил :), можете представить там NOW() :)
                                –1
                                Вы знаете, не было проблем. Иногда пишется:
                                "UPDATE table set `time`=NOW() WHERE id=$id"

                                а иногда так:
                                $fields['password']=md5($fields['password']);
                                $fields['time']=time();
                                sql_set($fields);
                                  0
                                  надеюсь, $id не напрямую из $_GET/$_POST берется.
                                    0
                                    Если было бы напрямую, стояло бы WHERE id={$_GET[id]}. Но и потом нельзя же в эту тему и безопасность впихивать. Слишком сложно будет.
                              0
                              Помоему на каждый "чих" создавать функцию это лишнее. + Код без вот этих ваших ф-й лично мне был бы понятнее, а так возможно придется рыскать по файлам и искать какую либо из них и разбиратся чтоже она и как делает.
                                +1
                                Статья неплохо подойдёт тем кто только начал изучать php, она — хороший пример перехода на чуть более высокий уровень программирования (оторваться от низкоуровневых, встроеных, php функций, использовать свои более высокоуровневые). Следующим шагом неплохо было бы почитать про асбтракцию в бд, объектную модель, ORM, чего советую и автору.
                                  +1
                                  Ужасно :( Стоит использовать как наглядное пособие на тему "Как не стоит прогрессировать в программировании на PHP".
                                    +3
                                    > Под выским уровнем понимается достижение цели при минимальном и быстро получаемом коде (количества строк кода, символов в строке) [...] Если разработчик вместо 30 строк кода пишет только 10, то верятность ошибки снижется в три раза.

                                    Это точно не так.
                                    Чтобы сделать код простым, часто делают как раз наоборот.
                                    Сравните (простите за С в этом топике, я не знаю конкретно PHP, но думаю, что смысл сохранится на любом языке):

                                    int x1=getSomeX()+10;
                                    int y1=getSomeY(1)*2+5;
                                    int x2=x1*2+20;
                                    int y2=getSomeY(2)*2+5;
                                    int c=func3(x1,y1,x2,y2);


                                    и

                                    int x=getSomeX();
                                    int c=func3(x+10,getSomeY(1)*2+5,x*2+30,getSomeY(2)*2+5);
                                    // а представьте, что у этой функции 10 параметров


                                    Где легче ошибиться? Что труднее отлаживать?

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

                                    Попробуйте взять любой (не написанный специально для этого случая) кусок кода в 30 строк и сжать в 10. Будет нечитабельный для постороннего ужас.

                                    Я-то как раз сторонник писать в 10 строк вместо 30 - часто красивее, но читабельности не добавляет (в отличие от ошибок, если быть невнимательным или переборщить с лаконичностью).

                                    > Дальнейшее сопровождение и модификации более короткого кода тоже несомненно требует гораздо меньшего времени и человеческих ресурсов.

                                    Вот это - наверно мой любимый кусок кода (спёрто из какой-то книги):
                                    y-=x++;
                                    Это лаконично и красиво, но тот, кто будет сопровождать и модифицировать, будет долго вспоминать вас.

                                    Так что первые два абзаца статьи очень спорны.
                                      0
                                      Плохо. Очередной карявый велосипед.
                                      Для новичков, желаетльно, сразу научиться писать правильно...а не так...поэтому советую просто поиграть с CodeIgniter'ом. Очень прост в изучении и имеет отличную документацию.
                                        0
                                        Присоединяюсь к совету. Сильно просветляет голову: даёт понять об MVC и тому подобным умным фразам и сокращениям :) Использование CodeIgniter'а помогло мне перейти к ООП и постепенно начать интересоваться паттернами и так далее.
                                          0
                                          Согласен. С точки зрения обучения интересна также реализация классов работы с БД в Zend Framework.
                                        0
                                        Меня как плохого программиста, всегда бесит, когда хорошие программисты кладут болт на структуру базы:

                                        Почему не так как ниже?

                                        CREATE TABLE `users` (
                                        `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY ,
                                        `category` TINYINT UNSIGNED,
                                        `name` VARCHAR(25),
                                        `password` CHAR(30),
                                        `email` VARCHAR(100)
                                        );
                                          0
                                          Наверное потому, что это иллюстративный материал и структура базы в данном случае не важна. Здесь много чего не затронуто - и защита от инъекций и даже о подключении к базе ни слова. Это опущено сознательно.
                                            0
                                            Я несколько раз в месяц слышу "ну поставил я индекс", применять "составные индексы" вообще заподло похоже и т.п. собственно все примеры "иллюстративные", но изначально надо показывать нормально, т.к. у подавляющего большинства и работающие структуры идентичны приведенным вами.
                                          0
                                          "данные должны поступать всегда в виде ассоциативного массива и никак иначе."

                                          в этом случае ассоциативный массив не подойдет
                                          list($var1, $var2) = sql_get_row('SELECT var1, var2 FROM ...');

                                          придется юзать array_values

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

                                          Самое читаемое