Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Конфигурация PHP: Объектно-ориентированный дизайн

Markdown — упрощённый язык разметки, который удобен при работе с текстом (в отличие от HTML). Браузеры не умеют отображать Markdown напрямую, поэтому он транслируется в HTML и уже затем показывается. Трансляция Markdown в HTML описывается чистой функцией. Она не зависит от внешнего окружения, детерминирована и не порождает побочных эффектов.

<?php

$html = markdownToHtml($markdown);

На входе текст (в формате Markdown), на выходе — тоже текст (в формате HTML). Если нужно изменить поведение трансляции, то достаточно передать вторым параметром массив опций.

<?php

$html = markdownToHtml($markdown, ['sanitize' => false]);

Теперь давайте вообразим объектно-ориентированную версию этого кода. Перед тем, как двигаться дальше, попробуйте отвлечься от чтения и подумайте над следующими вопросами:

  • Что мы вообще хотим получить такого от ООП, чего не даёт нам чистая функция?
  • Как будет выглядеть получившийся интерфейс?

Как вы помните, классы позволяют реализовать абстракцию. Можно ли сказать, что в процессе преобразования Markdown в HTML есть абстракция? Нет. Абстракция подразумевает наличие некоторого понятия (типа), значения которого обладают временем жизни. Это значит, что она создается и затем многократно и по-разному используется. Например, невозможно представить работу с пользователем в виде одной функции. Если говорить о Markdown, то конкретный текст этого формата не интересует нас сам по себе, мы не определяем над ним некоторый набор операций и не собираемся им активно пользоваться. Все, что мы хотим, прямо здесь и сейчас (в том коде) - получить HTML и забыть про Markdown.

Если бы мы хотели построить вокруг текста абстракцию, то код выглядел бы так:

<?php

// Объект md описывает собой переданный текст $markdown и позволяет им манипулировать
$md = new Markdown($markdown);
$html = $md->render();

В примере выше тип Markdown представляет собой абстракцию над текстом в формате Markdown. Смысла в таком коде мало, а вот проблем он доставит. Эти две строчки начнут неразрывно встречаться в каждом месте, в котором требуется получить HTML. Объект $md становится сразу не нужен, как только получен HTML, у него нет времени жизни. Такой антипаттерн особенно часто встречается у новичков. Загвоздка здесь именно в том, чтобы разобраться, где у нас абстракция данных, а где нет.

<?php

// Типичный избыточный код в том месте, где абстракцию сделали, но она не нужна
$md1 = new Markdown($markdown1);
$html1 = $md1->render();

// Еще раз для закрепления
$md2 = new Markdown($markdown2);
$html2 = $md2->render();

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

<?php

$md = new Markdown();
// очень важно, чтобы render оставался чистой функцией и не сохранял $markdown внутри объекта
$html1 = $md->render($markdown1);
$html2 = $md->render($markdown2);

В этом коде класс Markdown — тип, относящийся к транслятору, а не к тексту. У такого объекта жизненный цикл шире, чем ожидание однократного вызова функции render (как в предыдущем случае). Он может (и должен) переиспользоваться столько раз, сколько потребуется. Для этого важно оставить функцию render чистой и не менять состояние объекта между вызовами.

Тогда становится непонятно, зачем здесь вообще объект. И на это есть 3 причины.

  1. Идиоматика. В PHP, как и в Java, принято практически всё оформлять в виде классов. К тому же для них работает автозагрузка.
  2. Полиморфизм подтипов. Разберём в последующих курсах.
  3. Третья и главная причина (для данного случая) — Конфигурация.

Разберём последний пункт подробнее. Представьте что Markdown на проекте используется повсеместно (на Хекслете очень часто) и код генерации HTML выглядит так:

<?php

// В одном месте
$html1 = markdownToHtml($markdown1, ['sanitize' => true]);

// Где-то в другом месте
$html2 = markdownToHtml($markdown2, ['sanitize' => true]);

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

<?php

// В одном месте
$html1 = markdownToHtml($markdown1, $options);

// Где-то в другом месте
$html2 = markdownToHtml($markdown2, $options);

Использование объекта позволяет убрать явную передачу (про которую легко забыть). Суть этого паттерна заключается в конфигурировании. То есть объект в данном случае выступает в роли контейнера, содержащего опции для Markdown, которые применяются при рендеринге, что позволяет их не передавать каждый раз.

<?php

$md = new Markdown(['sanitize' => true]);
$html1 = $md->render($markdown1);
$html2 = $md->render($markdown2);

Под конфигурированием всегда понимается передача опций (различных настроек, необходимых данной библиотеке) в конструктор во время создания объекта. Особенно полезной такая конфигурация становится тогда, когда объект создаётся в одном месте программы (на этапе инициализации приложения), а используется в других местах. Возможность конфигурации не навязывает саму конфигурацию, как правило, подобные объекты можно создавать и без указания чего-либо, тогда поведение остаётся "дефолтным", но смысл от этого не меняется.

<?php

$md = new Markdown();
$html = $md->render($markdown);

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

Попробуйте проверить себя. Выполнение HTTP-запроса это абстракция данных или нет?

<?php

$client = new \GuzzleHttp\Client();
$res = $client->get('https://api.github.com/repos/guzzle/guzzle');
echo $res->getStatusCode();

Данный приём не является прерогативой классов и объектов. В функциональных языках (и в JS) он крайне просто реализуется через замыкание


Дополнительные материалы

  1. Библиотека CommonMark

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

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

Получить доступ
900
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы PHP-разработчик
Профессия
Разработка веб-приложений на Laravel
29 сентября 8 месяцев

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»