Легкая и быстрая — вот два слова, на которые мы молимся, создавая Diafan.CMS. У нас нет больших библиотек на случай атомной войны, а всё новое добавляется по необходимости. Общая логика системы доработана и отполирована за многие годы, поэтому в системе можно себе позволить ветвить и дорабатывать функционал, оставляя простым для понимания код и легкой для разработки CMS. Как это достигается? Мы сформировали несколько советов.

Наша система неоднократно завоевывала звание быстрой и оптимизированной системы, код которой максимально ярко показывает себя под нагрузками (первое исследование, второе исследование). В комментариях к исследованиям мы обещали, что напишем статью о том, как достигаются такие результаты. А где еще публиковать такую статью, как не на Хабре?

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

1. Масштабируйте Ваше приложение только по потребностям


Двигайтесь от простого к сложному.

Допустим, Вашему проекту понадобились SMS-уведомления. Код отправки SMS достаточно простой, в несколько строк: это просто валидация телефона и GET-запрос к серверу отправки SMS — прямо так его и интегрируйте.

Например, когда в Diafan.CMS потребовались SMS-уведомления администратору на поступление заказов, мы поместили этот простой код прямо в модуле «Магазин», в функции, формирующей заказ.

Затем SMS-уведомления нам понадобились в модуле «Обратная связь» и «Рассылки». Тогда мы вынесли этот функционал в виде глобальной фунцкции в отдельный файл includes/sms.php и спокойно использовали в разных модулях в таком виде:

include_once('includes/sms.php');
Sms::send($message, $to);

Когда потребовалось дополнить SMS-уведомления настройками и своим интерфейсом, мы перенесли этот функционал в отдельный модуль.

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

Суть в том, что не нужно на первых этапах разрабатывать какие-то сложные библиотеки для работы с новым функционалом, не нужно выносить 100500 настроек, которые запутают администратора сайта и потребуют написания кипы документации. А потом может не понадобится. Лучше сделать только то, что требуется на данном этапе.

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

Отказываясь от лишнего кода, который не используется на текущем этапе разработки, можно убить сразу двух зайцев:

  • полный контроль кода, исключающий логические ошибки и проблемы безопасности;
  • общий размер приложения не будет расти.

Более того, такой подход нас привел еще к одной особенности. Мы очень мало используем внешний код, плагины и библиотеки. Вопреки «велосипедной» ошибке, мы считаем, что лучше написать одну функцию под свои задачи, чем включить огромную стороннюю библиотеку.

2. Уделяйте внимание обработке URL-ссылок


Разбор ссылок сервером — это узкое место, через которое проходят все страницы сайта. Старайтесь делать это место как можно проще для сервера.

Чаще всего для адресной навигации по страницам сайта, вместо простых для сервера адресов вида site.ru/?PAGE=34, используется так называемое «ЧПУ» (человеко-понятные-урл), когда каждой странице сайта соответствует красивый строковый понятный адрес. Например, по адресу site.ru/o_kompanii/ открывается страница «О компании». Здесь «o_kompanii» — это строковое ЧПУ, интерпретируемое в site.ru/?url=o_kompanii по которому CMS ищет страницу с именем o_kompanii в базе и выводит её содержимое на сайте. Как если бы её запросить по PAGE=34. Однако, помимо строкового имени страницы, через адресную строку могут передаваться еще и некоторые параметры и их может быть много. Анализ таких ЧПУ может быть весьма затруднительным.

В Diafan.CMS всего один шаблон для построения ссылок: /строковоеЧПУ/числовойпараметр3/другойчисловойпараметр25/ 3, 25 — это значения параметров. Т.е. если в URL есть числовые параметры — значит перед ними стоят переменные, во всяком случае это самая частая структура.

Мы сразу отбрасываем все числовые параметры, а затем уже, если что-то осталось, смотрим строковую ЧПУ в базе данных и страницу, ей соответствующую. Если по ЧПУ страницу не находим, то начинаем прибавлять по одному параметру к ЧПУ и снова смотреть в базе. Да, такой алгоритм состоит из нескольких лишних SQL-запросов, но они будут только для таких особых страниц или для страниц 404, если искусственно подставить туда переменные. Что не нагружает базу для всех остальных (а их большинство) страниц.

Проще этого только некрасивые ссылки с $_GET данными, типа site.ru/?url=page:13&m=3&m2=25.

3. Запускайте функционал только по необходимости


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

Например, если в Diafan.CMS к товару нужно подключить рейтинг, мы просто обращаемся к переменной $this->diafan->_rating и она отдаст всю подключаемую часть модуля «Рейтинг». Так как при первом обращении к псевдопеременной $this->diafan_*** будет проверено наличие запрашиваемого модуля и выполнено корректное подключение к нему, как экземпляру класса. Это удобно и в использовании, и отсекает любой лишний функционал в текущий момент исполнения скрипта. Если рейтинг на странице не нужен, он не будет запускаться вообще.

4. Функционал, создающий дополнительную нагрузку, подключайте опционально


Не подключайте дополнительный функционал глобально во все модули, давайте возможность включить его опционально.
Например, в любом модуле, где используются комментарии, в Diafan.CMS есть опция «Подключить комментарии». Это исключает ситуации, когда комментарии к товарам нужны, а к новостям не нужны, и веб-мастер просто скрывает в шаблоне новостей вывод комментариев, но CMS продолжает выполнять SQL-запрос, искать комментарии к новостям, формировать все необходимые данные. Иными словами тратить ресурсы сервера понапрасну.

То же самое относится к любому другому функционалу, который мало-мальски использует ресурсы сервера. Не нужен счетчик посещений, рейтинг новостей, изображения к пунктам меню? Выносите их подключение в настройки и отключайте по умолчанию. Это позволит экономить ресурсы.

Это же касается скрытых настроек, например «Права доступа», когда нужно гибко настраивать ограничения доступа к разным конкретным материалам сайта для разных пользователей (Гость, Пользователь, Оптовик и пр.) Чтобы обеспечить эту возможность, проще всего ко всем SQL-запросам всех материалов добавлять:

… LEFT JOIN access AS a ON a.element_id='id_material' AND (e.access='yes' AND a.role='role_current_user'), который проверяет, есть ли у текущего пользователя доступ к запрашиваемому материалу.

А зачем нам эта нагрузка, если все новости видят все пользователи и этих ситуаций гораздо больше? Есть смысл добавлять этот LEFT JOIN к SQL-запросу опционально. В Diafan.CMS включение/выключение этой настроки просходит автоматически, без участия админа. То есть, если для какого-то материала нужно ограничить доступ, достаточно отметить настройку и запросы к БД усложняются. Если нет, то запросы простые.

5. Минимизируйте количество запросов к БД


Используйте prepare-функции.

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

$rows = SELECT * FROM shop; //запрос товаров
foreach ($rows as $row)
{
  echo "Товар".$row['id']; //вывод товара в цикле
  $img = SELECT * FROM img WHERE shopid=$row['id']; //запрос изображений к текущему товару
  foreach ($img)
  {
    echo "Картинка к товару".$img["link"]; //вывод изображения к текущему товару
  }
  }

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

Таким образом получается 1+10=11 отдельных запросов SELECT к SQL-серверу. Однако, оптимальнее сделать запрос товаров, запомнить все ID полученных товаров, и затем сделать всего один SQL-запрос: выбрать все изображения для всех наших товаров сразу. Получится всего 2 запроса SELECT, вместо 11.

$rows = SELECT * FROM shop; //запрос товаров
foreach ($rows as $row)
{
  $goods[] = $row['id']; //запоминаем полученные товары в массив
} $img = SELECT * FROM img WHERE shopid IN implode($goods[]); //запрос изображений для всех товаров
foreach ($img)
{
  $images[] = $img['link']; //запоминаем полученные изображения в массиве
}
foreach ($goods[])
{
    echo "Товар".$goods[]; //вывод товаров в цикле
    echo "Картинка к товару".$images[$goods[id]]; //и изображений к ним
  }


Но здесь проблема в том, что нарушается логика циклов и код становится неудобным. Нам удалось найти изящное решение, красивое для использования и оптимальное для сервера: функция prepare. Код получился такой:

$rows = SELECT * FROM shop; //запрос товаров
foreach($rows as $row)
{
  $this->diafan->_images->prepare($row['id'], 'shop'); //запоминаем товары
}
foreach($rows as $row)
{
  $images[$id] = $this->diafan->_images->get($row['id'], 'shop'); // запрос изображений функцией get
  echo "Товар".$row['id']; //вывод товаров
  echo "Картинка к товару".$images['id']; //и изображений к ним
}

Функция prepare просто запоминает все элементы, для которых нужно получить картинки. А особенность функции get в том, что SQL-запрос будет выполнен при первом вызове и результат тоже запомнится. Все остальные вызовы get из этого цикла будут работать с полученными в первый раз данными.

И так сделано для любого функционала: запрос ЧПУ для формирования ссылок, запрос тегов, запрос рейтинга и пр. Поэтому в Diafan.CMS обращения к SQL-серверу минимальны.

6. Используйте кэширование


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

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

Можно кешировать как участки сайта, блоки, списки, карточки и элементы, так и все страницы сайта. Подробнее.

Важно помнить, что статические ресурсы нагужают сервер на порядок меньше, чем динамические, поэтому старайтесь как можно меньше обращаться к SQL и РНР-интерпретатору. По умолчанию в Diafan.CMS используется файловый кэш, но подключить можно и Memcache. Мы кэшируем только данные. Все оформление в кэш не попадает. Это позволяет экономить место под кэш и как следствие быстрее с ним работать.

7. Минимизируйте динамическое получение данных


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

Например, в Diafan.CMS есть гибкая система скидок, когда у одного товара может быть несколько цен, каждая с установленной скидкой, еще и в разных валютах. Мы не высчитываем новые цены на сайте для каждого посетителя сайта. Мы делаем это при установке скидки в административной части сайта, считаем все различные варианты цены для выбранных товаров и собираем их в отдельную таблицу shop_price с признаком price_id. Исходная цена на товар определяется как id=price_id. Если id<>price_id, то это уже вариация цены либо со скидкой либо в основной валюте сайта. И в этой таблице сразу указывается для кого эта цена (если она установлена для какой-то конкретной группы пользователей).

Да, если товаров много, при добавлении или изменении скидки администратор будет ждать, глядя на крутящееся колесико. Зато посетителям сайта ждать не придется, ведь при выводе на сайте, а также при поиске, сортировке и прочих ёмких операциях система просто из всех доступных цен из одной таблицы по одному price_id выбирает самую маленькую и всё.

8. Используйте нормализацию БД и индексы в таблицах


Разносите сложную структуру информации по разным простым таблицам и используйте индексы по ним. Бывает, выгоднее сделать два простых запроса, чем строить один сложный INNER JOIN.

В Diafan.CMS в основном все смежные таблицы имеют примерно одну структуру: id, element_id, element_type, module_name. Запросы по ним предельно простые.
SELECT * FROM rating WHERE element_id=3 AND module_name='shop' AND element_type='element'
И по всем указанным полям обязательно стоят индексы.

Заключение


Если разобраться, мы не сообщили ничего нового и почти все пункты очевидны. Однако, теорию знают многие, но на практике применяют не всегда. Надеемся, наши советы помогут собраться, перестать лениться и создавать прекрасные, быстрые и оптимизированные приложения.
Поделиться с друзьями
-->

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


  1. SilverFire
    16.05.2016 19:39
    +10

    Тогда мы вынесли этот функционал в виде глобальной фунцкции в отдельный файл includes/sms.php и спокойно использовали в разных модулях в таком виде

    На дворе 2016 год, уже 5 лет как существует Composer. Тут вам и избавление от include 'sms.php', и запуск функционала по необходимости.


    В вашем примере с СМС вместо глобальных функций неплохо было бы сделать Helper-класс со статическими методами. А еще лучше — компонент приложения, зарегистрирован в Service Locator и доступный из экземпляра приложения — тогда будет и действительная гибкость настройки, и возможности для тестирования. А если заглянуть еще глубже, то отправку SMS сообщений нужно делать отложено через очереди, чтобы пользователь не ждал, пока ваше приложение сходит в чужое АПИ.


    В Diafan.CMS всего один шаблон для построения ссылок: /строковоеЧПУ/числовойпараметр3/другойчисловойпараметр25

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


    Минимизируйте количество запросов к БД

    А еще можно попробовать использовать ORM и решить многие описанные проблемы архитектурно. Кстати, все ваши примеры кода в этом блоке синтаксически неверны и жутко отформатированы (загляните в PSR).


    Вещи говорите местами правильные, но пора переходить на следующий уровень. PHP уже давно не такой, каким вы его показываете ;)


    1. vollossy
      16.05.2016 20:38
      +2

      Ну, кстати, ORM не всегда панацея от проблем — им тоже стоит с умом пользоваться, ибо есть вероятность достаточно сильно затормозить свой код.


      1. SilverFire
        17.05.2016 12:54

        Истину глаголите, сударь :) ORM — инструмент, а любой инструмент нужно использовать с умом


        1. oxidmod
          17.05.2016 12:56

          жаль это не касается того кода, что лежит у них в database.php и DB_mysql(i)


  1. oxidmod
    16.05.2016 20:14
    +1

    даже примеры с ошибками… печально


  1. diafan
    16.05.2016 22:56
    -9

    На дворе 2016 год, уже 5 лет как существует Composer.
    Ну ребята, если говорить о разработке чего-то нового, безусловно нужно использовать последние тенденции программирования, а когда продукт начинался в 2003-м, и в коробку сформировался в 2008-м, переписывать всю CMS раз в год под все новинки РНР нецелесообразно. Мы часто вынуждены обеспечивать и совместимость наших версий, и обновления для пользователей. И чаще стараемся дать людям функционал, чем подхватывать на лету всё новое, что выходит в РНР. Опять же, аудитория шире, программистов, понимающих классический РНР-код, больше ;).
    даже примеры с ошибками… печально
    В этих примерах стоит дисклеймер *здесь и ниже код условный, для наглядности. специально, чтобы не принимали код близко к сердцу, и даже специально чуть карикатуризировали его (приведенный код — не цитаты из DIAFAN.CMS, там всё несколько сложнее). Главное в статье — смысл, основы алгоритмизации при проектировании кода. А уже какими инструментами этого достигать — личное дело каждого.


    1. Evgeny42
      17.05.2016 00:02
      +7

      Ну ребята

      Ну ребята, если вы решили статью на хабру написать в 2016, то может использовать актуальный для этого года код? А то зайдут ребята зеленые, которые решат что иклюды и статичные методы это хорошо. И echo вызовы внутри кода тоже.

      Да и в целом статья просто вода. Не делайте медленно, делайте быстро. Используйте кеш и индексы, не используйте некрасивые ссылки. Спасибо, теперь мой код станет лучше.


      1. Evgeny42
        17.05.2016 00:18
        +1

        Я тут вашу CMS тестовую скачал, ну чтобы посмотреть код. В принципе, это конечно далеко от идеала, но для коммерческого проекта, наверно такой код можно даже назвать не плохим. Особенно в сравнении с тем же утекшим кодом fl.ru и прочего, типа вордпресса.

        Но одна вещь меня все таки позабавила. Не уверен, что имею право приводить сюда этот кусок кода. Будем считать что я его сам написал.

        	/**
        	 * Кодирует пароль
        	 * ...
        	 */
        	public function encrypt($text)
        	{
        		return md5($text);
        	}
        


        1. izac
          17.05.2016 00:49
          +3

          опять md5, сколько уже было статей на эту тему, что md5 не пригоден для хранения паролей, даже статья была целая, кстати вот и она https://habrahabr.ru/post/210760/


        1. evnuh
          17.05.2016 01:21
          +1

          жду реализации decrypt()


        1. diafan
          17.05.2016 14:30
          -1

          Именно для забавы эта вещь там и стоит. Это заглушка. У нас функция, кодирующая пароль, зашифрована, а эта выведена для прикола, как и

          meta name="GENERATOR" content="Microsoft FrontPage 1.0"
          
          на сайте artlebedev.ru ;)
          Вы в этом убедитесь, если возьмете ХЕШ пароля из БД, его простым md5 не раскрыть.
          наверно такой код можно даже назвать не плохим
          Искреннее спасибо :)
          Еще раз повторюсь, мы — коммерческая система управления сайтами, где максимально выгодно, когда код понимает максимальное количество веб-разработчиков. Нам приходится балансировать.


          1. lair
            17.05.2016 14:42

            У нас функция, кодирующая пароль, зашифрована

            А зачем?


            1. diafan
              17.05.2016 14:52

              Тут, наверное, нужно сначала ответить на вопрос, зачем шифровать пароль?


              1. lair
                17.05.2016 14:54

                Да нет, не обязательно. То есть, в принципе, это тоже любопытно — но не обязательно.


          1. oxidmod
            17.05.2016 14:45

            очень интересно:
            1) как вы собрались раскрыть хеш?
            2) зачем шифровать свои костыли, вместо использвоания Password Hashing API?


            1. diafan
              17.05.2016 14:52
              -2

              1) чем шифруем, тем и расшифровываем.
              2) писал в самой статье:

              … такой подход нас привел еще к одной особенности. Мы очень мало используем внешний код, плагины и библиотеки. Вопреки «велосипедной» ошибке, мы считаем, что лучше написать одну функцию под свои задачи, чем включить огромную стороннюю библиотеку.


              1. oxidmod
                17.05.2016 15:04
                +2

                1) Похоже вы банально некомпетентны. Хеши не расшифровывают. При хорошей хеш функции сделать это за разумное время невозможно в принципе.
                2) Password Hashing API — апи ядра ПХП. это даже не екстеншин, не то что сторонняя библиотека. Что еще раз подтверждает, что вы некомпетентны в этой сфере.


                1. evnuh
                  17.05.2016 18:42
                  +1

                  Конечно некомпетентен, пару сообщений выше автор предлагает нам:

                  Вы в этом убедитесь, если возьмете ХЕШ пароля из БД, его простым md5 не раскрыть.

                  «раскрыть» хеш с помощью md5


    1. Fedcomp
      17.05.2016 01:38
      +1

      composer вышел в 2012. Сколько должно пройти времени прежде чем вы будет использовать нормальную технологию?


      1. M-A-XG
        19.05.2016 10:01

        Он только недавно стал стабильным… :)


    1. M-A-XG
      17.05.2016 10:57
      -2

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


      1. SilverFire
        17.05.2016 12:58

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


        1. M-A-XG
          19.05.2016 09:54

          Говнокод, конечно, писать не стоит.
          Просто нужно понимать, что у старых проектов может быть куча легаси-кода.
          Не переписывать же его ради моды каждый год.
          Работает? Ну так пусть работает.


          1. lair
            19.05.2016 10:37
            +1

            Работает? Ну так пусть работает.

            Но зачем приводить легаси-говнокод как совет?


            1. oxidmod
              19.05.2016 13:08
              +1

              "по приколу" же))


              1. 13i
                24.05.2016 13:20

                Статья явно написана не по приколу.


            1. M-A-XG
              19.05.2016 14:39

              Какой совет?


              1. lair
                19.05.2016 14:50

                "Как сделать быстрое веб-приложение"


                1. M-A-XG
                  19.05.2016 15:09

                  А, я думал, вы о том, что я давал где-то совет писать легаси-говнокод :)


  1. anitspam
    17.05.2016 09:16

    То есть, если для какого-то материала нужно ограничить доступ, достаточно отметить настройку и запросы к БД усложняются. Если нет, то запросы простые.


    Получается, вам для каждого материла нужно запросить систему, есть для для него настройка доступа. Получается +n (количество материалов) запросов?


    1. diafan
      17.05.2016 10:53

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


  1. diafan
    17.05.2016 10:52

    Я рад, что статью не проигнорировали и она вызвала какой-то отклик в сердцах. :) Даже если все только утвердились в мыслях, что они далеко впереди нас в разработке и все делают правильно — и то не зря.

    Я только еще раз попытаюсь напомнить, что все вышенаписанное возникло не на пустом месте в формате «вот какие мы изниоткуда», а по факту https://roem.ru/14-04-2015/192219/cms-highload/

    Победителем безоговорочно оказалась DIAFAN.CMS. Она успешно выдержала нагрузку в 1000 посетителей, причем со скоростью ответа как у html-страницы.
    оставив далеко позади даже всенародно любимые Битрикса и Юми.

    Безусловно, мы вообще не идеальны и работать нам есть куда, и мы будем развиваться. А за очередной стимул очередной раз спасибо сообществу Хабра :)


    1. oxidmod
      17.05.2016 11:30
      +2

      битрикс конечно топ, на который нужно равняться… и 1000 посетителей, это конечно мего трафик
      вот тоже взял и скачал, не поленился…
      includes/database.php


      public static function _query_callback($R64B8E2B8C7ABE18309C106487717187A, $R5875A5AF586E3482AE15888C305D0FDF = false)
      {
          global $R9FE302BDF914868081913A22F58F9E7E;
          if ($R5875A5AF586E3482AE15888C305D0FDF){
              $R9FE302BDF914868081913A22F58F9E7E = $R64B8E2B8C7ABE18309C106487717187A;
              return;
          }
      
          switch ($R64B8E2B8C7ABE18309C106487717187A[1]){
              case '%d':return (int)preg_replace('/[^0-9\-]+/', '', array_shift($R9FE302BDF914868081913A22F58F9E7E));
              case '%s':return self::_escape_string(array_shift($R9FE302BDF914868081913A22F58F9E7E));
              case '%h':return self::_escape_string(htmlspecialchars(stripslashes(strip_tags(array_shift($R9FE302BDF914868081913A22F58F9E7E)))));
              case '%%':return '%';
              case '%f':return (float) str_replace(',', '.', array_shift($R9FE302BDF914868081913A22F58F9E7E));
              case '%b':return self::_encode_blob(array_shift($R9FE302BDF914868081913A22F58F9E7E));
          }
      }

      1) глобалы, хотя врятли комуто придет в голову назвать переменную $R9FE302BDF914868081913A22F58F9E7E
      2) абсолютная невозможность дебага и понимания что здесь происходит
      3) htmlspecialchars, stripslashes, strip_tags… господи, да заюзайте же подготовленные выражения


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


      1. lair
        17.05.2016 11:34
        +1

        … вообще, это выглядит как обфусцированный код.


        1. oxidmod
          17.05.2016 11:43

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


        1. diafan
          17.05.2016 14:32
          -1

          … вообще, это выглядит как обфусцированный код.

          Именно. Небольшая заноза для путаницы и забавы, привлекающая внимание, как псевдообфусцированная функция. Это для любителей нуллить, один из приколов.


          1. 13i
            24.05.2016 13:27

            Там у вас одни приколисты сидят? :)


      1. M-A-XG
        17.05.2016 13:50
        -1

        >htmlspecialchars, stripslashes, strip_tags… господи, да заюзайте же подготовленные выражения

        Шта?


        1. oxidmod
          17.05.2016 14:47
          +1

          код выше гляньте:


          case '%h':return self::_escape_string(htmlspecialchars(stripslashes(strip_tags(array_shift($R9FE302BDF914868081913A22F58F9E7E)))));

          оно конечно "для прикола" как пишет автор, но больно уж смахивает на попытку защититься от sql-injections… фиговою попытку, надо сказать


          1. M-A-XG
            17.05.2016 14:59

            Ну это только гении могут думать, что с помощью htmlspecialchars, stripslashes, strip_tags защищаются от sql-иньекций :)


      1. diafan
        17.05.2016 14:35

        1000 посетителей, это конечно мего трафик
        Единовременных? Т.е., грубо говоря, 1000 в секунду — это по-вашему тьфу? :)

        Тем не менее, из всех коммерческих систем управления сайтами этот «меготраф» выдержали только мы, причем с хорошим запасом.


        1. lair
          17.05.2016 14:45
          +1

          Единовременных? Т.е., грубо говоря, 1000 в секунду — это по-вашему тьфу?

          А в тесте где-то сказано, что это тысяча пользователей в одну секунду?


          (ну и да, в "секунду" обычно меряют не пользователей, а запросы)


          1. diafan
            17.05.2016 14:56

            Ну это уже к loaddy.com — как они нагружают я в деталях не знаю. Может пользователи, может хиты. Мы лишь гордимся тем, что выдержали эту нагрузку заметно лучше других участников теста.


            1. lair
              17.05.2016 17:23

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


    1. oxidmod
      17.05.2016 11:40
      +1

      class DB_mysql
      no comments… хорошо что хоть есть альтернатива mysqli


      includes/model.php


      public function format_date($date, $module_name = '', $site_id = 0)
          {
              $months_array = array(
                  '01' => $this->diafan->_('января'),
                  ...
                  '12' => $this->diafan->_('декабря')
              );
              $week_array = array(
                  '1' => $this->diafan->_('понедельник'),
                  ...
                  '0' => $this->diafan->_('воскресенье')
              );
      ...

      и еще куча другого текстового хардкода...


      modules/admin/admin/admin.admin.php


      /**
           * Редактирование поля "Псевдоссылка"
           * 
           * @return void
           */
          public function edit_variable_rewrite()
          {
              echo '
              <div class="unit">
                  <div class="infofield">'.$this->diafan->variable_name().$this->diafan->help().'</div>
                  <input type="text" name="'. $this->diafan->key.'" value="'
                  .(! $this->diafan->is_new ? str_replace('"', '&quot;', $this->diafan->value) : '')
                  .'">
              </div>';
          }
      
          /**
           * Редактирование поля "Родитель"
           * 
           * @return void
           */
          public function edit_variable_parent_id()
          {
              $rows = DB::query_fetch_all("SELECT id, name FROM {admin} WHERE parent_id=0 AND id<>%d ORDER BY name ASC", $this->diafan->id);
              echo '
              <div class="unit">
                  <div class="infofield">'.$this->diafan->variable_name().$this->diafan->help().'</div>
                  <select name="'. $this->diafan->key.'">
                  <option value="">-</option>';
                  foreach($rows as $row)
                  {
                      echo '<option value="'.$row["id"].'"'.($row["id"] == $this->diafan->value ? ' selected' : '').'>'.$row["name"].'</option>';
                  }
                  echo '</select>
              </div>';
          }


      1. izac
        18.05.2016 22:21

        html + php, я точно в 2016 году?


        1. oxidmod
          18.05.2016 23:03
          +1

          ну это такой "прикол"
          жаль что ТС слился и не пишет больше=(


    1. 13i
      24.05.2016 13:45

      https://roem.ru/14-04-2015/192219/cms-highload — читайте первый комментарий


  1. lair
    17.05.2016 13:21
    +2

    Допустим, Вашему проекту понадобились SMS-уведомления. Код отправки SMS достаточно простой, в несколько строк: это просто валидация телефона и GET-запрос к серверу отправки SMS — прямо так его и интегрируйте. Например, когда в Diafan.CMS потребовались SMS-уведомления администратору на поступление заказов, мы поместили этот простой код прямо в модуле «Магазин», в функции, формирующей заказ.

    Простой вопрос: и как вы это протестировали?


    Тогда мы вынесли этот функционал в виде глобальной фунцкции в отдельный файл includes/sms.php и спокойно использовали в разных модулях в таком виде: Sms::send($message, $to);

    … и тот же самый вопрос.


    1. diafan
      17.05.2016 14:37

      В смысле? Как обычно тестируют, так и мы протестировали. А какие здесь подводные камни? Код из пары простых строк, что там тестировать?


      1. lair
        17.05.2016 14:42
        +1

        Как обычно тестируют, так и мы протестировали

        У всех свое собственное "обычно". Какое у вас?


        А какие здесь подводные камни?

        Лично мне в глаза бросается невозможность юнит-тестирования. Я неправ?


        Код из пары простых строк, что там тестировать?

        Например, факт его наличия (т.е., тот факт, что смс отправляется в нужный момент) и факт передачи корректных параметров (т.е., что смс отправляется тому, кому надо, и с нужным текстом).


        1. Evgeny42
          17.05.2016 16:36
          +1

          А вы скачайте и сами посмотрите. Там забавно, сперва открывают сокет на bytehand.com:3800 и если сокет открылся, то используя file_get_contents (!) уже отправляют сообщение. А забавное то что, результат выполнения file_get_contents не проверяется, не используется и даже не возвращается. Ну и сокет не зарывается.

          Я конечно не удивлюсь, если и это окажется «по приколу» или «заглушка», а не деле где-то в недрах системы лежит отлаженный и «зашифрованный» идеальный код для отправки SMS.


          1. Evgeny42
            17.05.2016 16:43

            Хотя, ради интереса посмотрел, везде этот странный класс и используется. Может это конечно демка такая, лол, но никаких кастомизаций этого «модуля» там и в теории нет. URL захардкожен, условий минимум — только проверки на ввод.

            Вот типичный пример использования.

            Custom::inc("includes/sms.php");
            Sms::send($_POST["text"], $row["phone"]);
            


            1. lair
              17.05.2016 17:24

              Sms::send($_POST["text"]...)

              Эээ, это правда, серьезно?


          1. lair
            17.05.2016 17:26

            Да ладно реализация, фиг с ней, реализацию можно и поправить при правильной декомпозиции. Вопрос декомпозиции...


            1. Evgeny42
              17.05.2016 17:33

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


              1. lair
                17.05.2016 17:34

                Я выше уже написал, какие проблемы могут быть с вызовом статического метода. А если он еще и ошибку возвращает в виде строки — так тем более.


                1. Evgeny42
                  17.05.2016 17:46

                  Я про то, что реализацию поменять дело пяти минут.


  1. 13i
    24.05.2016 12:56

    По пунктам:

    1. Полностью согласен. Разве что некоторые вещи очевидны сразу и можно выделить предположительно популярные функции в отдельные файлы заранее.

    2. А как вы поступаете, если строковоеЧПУ содержит цифры? Например, /пятница13/
    В CMS «Движок» мы используем альтернативный вариант, 3 вида строковых ЧПУ на выбор:
    — /news_page_12 — где, news — адрес раздела, 12 — номер страницы
    — /Nazvanie_novosti/ — транслит
    — /Название новости/ — русский адрес, в частности для русскоязычных доменов.
    А все дополнительные параметры пишутся как в обычном запросе, передаются через GET или куки.

    3. Солидарен. Но если модуль сверхмал и практически равен по времени выполнения проверке — можно его не проверять.

    4. По сути повторяет 3й пункт.

    5 — 8. Капитан Очевидность.


  1. diafan
    24.05.2016 14:56

    жаль что ТС слился и не пишет больше=(

    А что писать? Хабр как сообщество весьма специфичен, всем известно. Если статья не зашла, её минусуют резонансно и всё. Сражаться с этим явлением бесполезно: защищаешь свою точку зрения — плохо, оправдываешься — плохо, соглашаешься со всеми — плохо, что не делай — все плохо, заклюют безусловно и всё. Будут цепляться к словам, к опечаткам, к коду, к отступам — всё, это называется именно «не зашло». Ничего, бывает. :)
    html + php, я точно в 2016 году?
    Вы так говорите, как будто DIAFAN.CMS написана на Basic-е… А на чем написаны другие CMS? Не на PHP? Покажите коммерческую цмс из топа систем на другом языке http://www.cmsmagazine.ru/catalogue/platnye/
    class DB_mysql
    no comments… хорошо что хоть есть альтернатива mysqli

    Наоборот. Приоритет mysqli, но бывает крайне редко, что mysqli не включено на хостинге, тогда используется mysql. И это лучше, чем не давать ставить цмс вообще.
    Ну это только гении могут думать, что с помощью htmlspecialchars, stripslashes, strip_tags защищаются от sql-иньекций :)

    Эти функции не борятся с sql-иньекциями, а борятся с html, что подразумевает маска h. Используется в тех случаях, когда надо html код исключить.
    https://roem.ru/14-04-2015/192219/cms-highload — читайте первый комментарий
    Смотрим автора комментария и смотрим результаты Битрикса. Вы думаете, если бы Битрикс занял первое-третье место, этот комментарий был бы?
    Но зачем приводить легаси-говнокод как совет?
    В очередной раз повторяю, мы советуем алгоритм, а не код. Используйте любой код, к которому привыкли. А у нас какой бы ни был код, легаси или самый современный, он работает и в своей области (среди других коммерческих цмс) весьма результативно. У нас не спортивное программирование, у нас взрослая универсальная система управления, и я еще раз повторю, чем проще и ближе к классике будет код, тем больше веб-мастеров его поймут, это закон коммерческой системы.
    Конечно некомпетентен, пару сообщений выше автор предлагает нам:
    «раскрыть» хеш с помощью md5

    На самом деле да, я не ведущий разработчик системы, я простой руководитель компании, поэтому я не 100% компетентен в каких-то предметных вещах. Здесь не буду ничего доказывать. По поводу «раскрыть хеш» я согласен, это некорректная фраза, но поясню все-таки, что имел ввиду: есть множество сервисов, типа http://raz0r.name/obzory/top-10-luchshix-onlajn-servisov-po-rasshifrovke-xeshej/ где можно простым перебором популярных паролей получить их хеши и сравнить с искомым. Нередко получается выяснить исходный пароль, зашифрованный обычным md5. Так вот если взять наш хеш пароля из таблицы с администратором, стандартными md5 его так же не свернуть, т.е. не подобрать.
    А как вы поступаете, если строковоеЧПУ содержит цифры? Например, /пятница13/
    У нас есть разрешенные переменные для строковых параметров, глобальные системные и модульные. Если разрешенной переменной «пятница» в системе нет, значит «пятница13» — это строка ЧПУ, и мы ее сразу смотрим в базе.


    1. lair
      24.05.2016 15:22

      В очередной раз повторяю, мы советуем алгоритм

      … содержащий те же ошибки, что и ваш код.


      я еще раз повторю, чем проще и ближе к классике будет код

      Вопрос только в вашем определении "классики".


      На самом деле да, я не ведущий разработчик системы, я простой руководитель компании, поэтому я не 100% компетентен в каких-то предметных вещах.

      Тогда зачем вы пишете — да еще и с уверенным апломбом — о вещах, в которых вы не компетентны?


    1. oxidmod
      24.05.2016 15:38

      Вы так говорите, как будто DIAFAN.CMS написана на Basic-е… А на чем написаны другие CMS? Не на PHP? Покажите коммерческую цмс из топа систем на другом языке http://www.cmsmagazine.ru/catalogue/platnye/


      речь о том, что плохо смешивать верстку и пхп код