/php-app-step-by-step

Sample PHP application step by step

Primary LanguagePHPMIT LicenseMIT

PHP-фреймворк. Шаг за шагом

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

Каждый шаг рефакторинга - отдельная ветка.

  • Запуск локального сервера: php -S localhost:8000
  • Проект в браузере: http://localhost:8000

000-init-app

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

  • Администратор имеет возможность управлять постами. Файлы, относящиеся к администрированию, находятся в директории admin.
  • Реализована аутентификация с помощью сессии. Учетные данные лежат в конфиге. Пароль для доступа мы храним в открытом виде исключительно для демо-целей (не делайте так!) и трогать его не будем.
  • Хранение постов осуществляется в json-файле. Доступ к хранилищу даже! инкапусулирован с помощью класса репозитория.
  • Посетитель может читать посты. Для удобства даже! есть шаблоны, которые подключаются директивой require к каждому PHP-файлу, используемому для вывода.

001-public

Проблема

  • Все файлы в публичном доступе. Часто файлы в представленном виде выкладываются на сервер в публичную директорию. В этом случае все они находятся в публичном (свободном) доступе через http-запросы.

Решение

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

002-render

Проблема

  • Ручное подключение и хардкод параметров шаблонов. Шаблоны внешнего вида подключаются через require с указанием пути и расширения файла. При изменениях потребуются правки во всех подключающих файлах.
  • Преждевременная отправка заголовков. При выводе контента происходит отправка заголовков ответа, что может приводить к проблемам (headers already sent) с сессией, редиректом и т.д.

Решение

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

003-data

Проблема

  • Нет возможности работы с динамическими шаблонами. Шаблонизатор способен работать только со статическими шаблонами без возможности подстановки данных.
  • Смешение кода разметки (HTML) и логики (PHP) в файлах

Решение

  • Добавляем в шаблонизатор возможность передачи данных, необходимых для отрисовки шаблона.
  • В PHP-файлах оставляем только логику (PHP-код), весь код разметки (HTML) переносим в шаблоны.

004-autoload

Проблема

  • Ручное подключение классов. В каждом PHP-файле необходимо вручную подключать все необходимые классы с указанием полных путей к ним.

Решение

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

005-ctrl

Проблема

  • Смешение технического кода (подключение, инициализация и т.д.) и кода, ответственного за обработку запроса.

Решение

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

006-entry

Проблема

  • Множество точек входа в приложение. Каждый публичный файл является точкой входа в приложение, где выполняется одинаковый технический код - подключение загрузчика, конфига, старт сессии и прочее.

Решение

  • Приводим приложение к единой точке входа, которая перенаправляет запрошенное действие нужному методу контроллера (роутинг). Используем замыкания для отложенного выполнения кода.

007-router

Проблема

  • Смешение кода конфигурации с кодом запуска приложения в index.

Решение

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

008-container

Проблема

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

Решение

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

009-config

Проблема

  • Разброс настроек по коду приложения.

Решение

  • Пишем и используем конфиг - класс, ответственный за управление конфигурацией. Конфигурирование параметров осуществляем в отдельном файле.

010-final-app

Проблема

  • Много технического кода в индексном файле.

Решение

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

empty-app

Проблема

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

Решение

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

master

  • Дубль ветки empty-app

Спасибо за внимание!

Лицензия

Этот проект распространяется под MIT license.