Пример разработки блога на Zend Framework 2. Часть 1. ZendSkeletonApplication
В последние несколько лет моя работа связана с использованием CMS Drupal, но на досуге я изучал и just for fun запускал проекты на питоновских фреймворках Django, Flask и Twisted. Сейчас я решил освоить основы двух-трех популярных PHP-фреймфорков и первыми я решил изучить Zend Framework 2 и Yii.
В процессе ознакомления с Zend Framework 2 я изучил туториал с официального сайта (http://framework.zend.com/manual/2.2/en/user-guide/overview.html), просмотрел документацию фреймворка (http://framework.zend.com/manual/2.2/en/index.html), прочитал книгу Michael Romer “Web development with Zend Framework 2” и собрал собственное тестовое приложение.
Переварив всю эту информацию, я пришел к мысли, что официальный туториал к фреймворку суховат:
- в нем не рассказывается о работе с пользователями, сессиями и правами доступа,
- лишь вскользь рассматривается такая основополагающая часть фреймворка как ServiceManager,
- в качестве интерфейса с базой данных безальтернативно используется паттерн Table Gateway (и соответствующая его реализация в фреймворке),
- используется встроенный в фреймворк шаблонизатор, который после питоновского Jinja 2 кажется совершенно неудобным и примитивным,
- и т.д.
В итоге, более-менее удовлетворительное по функционалу приложение я смог создать после прочтения книги.
В этой статье я хочу привести пример разработки простого блога, в ней будет несколько отличий от официального туториала. В первую очередь я постараюсь заострить внимание на тех вопросах, которые во время изучения показались мне недостаточно раскрытыми в официальном туториале. Кроме того я буду использовать некоторые технологии, альтернативные тем, что используются в Zend фреймворке по умолчанию:
- в качестве шаблонизатора будет использоваться Twig,
- для работы с БД — Doctrine ORM,
- для авторизации/аутентификации и распределения прав доступа я буду использовать существующие модули ZfcUser (https://github.com/ZF-Commons/ZfcUser) и BjyAuthorize (https://github.com/bjyoungblood/BjyAuthorize),
- также я рассмотрю вопросы разработки собственных валидаторов форм, View плагинов и другие.
Статья написана новичком (в Zend Framework) для новичков, по этому приветствуется любая критика по делу и советы по усовершенствованию приложения.
Найти код проекта можно на Гитхабе: https://github.com/romka/zend-blog-example.
Статья будет разбита на 3 части:
- в первой (текущей) части я рассмотрю структуру ZendSkeletonApplication,
- во второй части разберу разработку собственного модуля (формы, работа с БД при помощи Doctrine ORM, разработка View-плагина),
- третья часть будет посвящена работе с пользователями.
Итак, приступим.
Окружение
Я предполагаю, что у вас уже есть настроенный веб-сервер (мой доступен по адресу http://zblog.kece.ru) и в MySQL создана пустая база данных под этот проект. Ввиду того, что для работы с БД я планирую использовать Doctrine, база данных может быть любой, поддерживаемой этой ORM.
Я предполагаю, что у вас под рукой есть исходный код ZendSkeletonApllication или моего туториала. Взять их можно тут: https://github.com/zendframework/ZendSkeletonApplication и тут: https://github.com/romka/zend-blog-example. Кроме того, я предполагаю, что вы понимаете паттерн MVC, имеете опыт работы с каким-либо шаблонизатором и валидатором форм.
Zend Framework использует замечательный менеджер зависимостей Composer (http://getcomposer.org/), который также должен быть установлен в вашей системе. Подробнее о Композере можно прочитать вот в этой статье: http://habrahabr.ru/post/145946/. Если говорить в двух словах, то Composer — это утилита командной строки, которая позволяет быстро и удобно скачать и установить внешние библиотеки, от которых зависит ваш PHP-проект. На входе утилита принимает JSON-файл в интуитивно понятном формате содержащий список имен и версий зависимостей, на выходе она скачивает и устанавливает нужные библиотеки и их зависимости освобождая вас от рутинной работы.
В тексте статьи иногда будут отсылки к исходникам внешних приложений или файлам, автоматически сгенерированным Композером. В репозитории этих файлов нет, по этому для более глубокого изучения разрабатываемого в этом туториале приложения вам нужно будет установить необходимый софт самостоятельно.
ZendSkeletonApplication
Используя Zend Framework вы можете с нуля спроектировать и создать структуру вашего приложения, но для изучения работы системы лучше воспользоваться заготовкой ZendSkeletonApplication (https://github.com/zendframework/ZendSkeletonApplication). В случае если у вас настроен Composer достаточно выполнить команду:
php composer.phar create-project --repository-url="http:// packages.zendframework.com" -s dev zendframework/skeleton-application path/to/install
(при должной настройке, команду php composer.phar
можно заменить просто на composer
, но далее в статье я буду приводить первый вариант как более универсальный)
После ее выполнения вы получите приложение со следующей структурой (я оставил только самые интересные директории и файлы):
config/
autoload/
global.php
application.config.php
module/
Application/
<Исходный код модуля Application будет рассмотрен далее>
public/
css/
img/
js/
index.php
vendor/
<Внешние библиотеки, в том числе исходный код Zend Framework>
composer.json
init_autoloader.php
Теперь вы можете открыть созданный проект. Моя версия доступна по адресу http://zblog.kece.ru.
Давайте детально разберем созданные директории и файлы.
Структура проекта
composer.json
Начнем с файла composer.json
в корне проекта. Он имеет следующий вид:
{
"name": "zendframework/skeleton-application",
"description": "Skeleton Application for ZF2",
"license": "BSD-3-Clause",
"keywords": [
"framework",
"zf2"
],
"homepage": "http:// framework.zend.com/",
"require": {
"php": ">=5.3.3",
"zendframework/zendframework": ">2.2.0rc1"
}
}
В этом файле задаются параметры приложения, которые будут использоваться Композером. Самой интересной частью этого конфига является секция require, которая содержит список внешних библиотек, от которых зависит приложение. Сейчас в списке зависимостей есть только Zend Framework 2, но в будущем, мы добавим сюда еще несколько зависимостей: Доктрину, Твиг и другие. После добавления новой зависимости достаточно будет в консоли исполнить команду:
php composer.phar update
и Композер скачает необходимые библиотеки. Все внешние зависимости складываются в директорию vendor
, сейчас мы в ней можем увидеть директории zendframework и composer.
Document root
Document_root нашего приложения находится не в корне проекта, а в директории public
, именно здесь находится файл index.php
— точка входа в наш проект и именно на работу с этой директорией должен быть настроен веб-сервер.
Index.php выполняет несколько важных задач, две самых интересных из них это подключение внешних библиотек, загрузка конфигурации приложения и его запуск.
Для подключения внешних библиотек исполняется файл init_autoloader.php
из корня проекта, который в свою очередь вызывает vendor/autoload.php
и вслед за ним автоматически сгенерированный Композером файл vendor/composer/autoaload_real.php
. В этом файле определены автолоад-методы для загруженных Композером внешних библиотек. Таким образом, когда мы в коде наших модулей будем подключать неймспейсы вида Zend\View\Model\ViewModel
PHP будет знать в каких файлах искать указанные неймспейсы.
Строчка кода:
Zend\Mvc\Application::init(require 'config/application.config.php')->run();
Загружает конфигурационные файлы приложения и запускает его. В процессе инициализации приложения (вызов метода Zend\Mvc\Application::init()
) создается ServiceManager — ключевой объект, использующийся во многих частях приложения. По умолчанию СервисМенеджер создает еще один ключевой объект — EventManager. В дальнейшем, когда мы будем создавать свои модули в их настройках мы будем сообщать СервисМенеджеру какие еще объекты необходимо создать для работы нашего модуля.
О СервисМенеджере мы еще поговорим позже, а сейчас давайте подробнее рассмотрим директорию config
. В ней находится файл application.config.php
, возвращающий массив с конфигурацией приложения, который может рассказать много интересного.
Массив modules содержит список включенных модулей, сейчас у нас включен только один модуль Application, но в будущем их будет больше.
Массив module_listener_options
содержит два интересных элемента: массивы module_paths
и config_glob_paths
.
Массив module_paths
подсказывает где искать подключаемые модули, по умолчанию в директориях vendor — внешние зависимости, и module — здесь будут находиться модули разработанные нами для нашего проекта.
Config_glob_paths
содержит маски путей к конфигурационным файлам модулей. Регулярное выражение config/autoload/{,*.}{global,local}.php
означает, что будут загружены все файлы вида module_name.{global или local}.php
, global.php
, local.php
из директории config/autoload
.
Таким образом, первым делом загружаются настройки из файла config/application.config.php
, затем загружаются настройки из файлов config/autoload/{,*.}{global,local}.php
и в последнюю очередь загружаются настройки заданные на уровне модулей (об этом мы поговорим позже).
Если в config/autoload
есть два файла настроек для одного и того же модуля: глобальный и локальный (module_name.global.php
и module_name.local.php
), то первым делом будет загружен глобальный файл, а за ним локальный, то есть локальные настройки имеют приоритет перед глобальными.
Файлы local.php
и *.local.php
по умолчанию включены в файл .gitignore
(если вы используете другую систему контроля версий, то их нужно добавить в исключения вручную) и они должны использоваться только для настроек, соответствующих только текущему окружению. То есть, если у вас проект в том или ином виде запускается на нескольких разных площадках: production, тестовой и девелоперских площадках, то в глобальных настройках приложения нужно хранить данные, актуальные для всех перечисленных площадок, а в локальных — уникальные для каждой площадки, например, данные для доступа к БД.
Кроме глобальных для всего приложения конфигурационных файлов каждый из модулей может иметь свой файл настроек. Такие конфигурационные файлы модулей загружаются в том порядке, в котором определен список активных модулей в файле application.config.php
. Таким образом, если вы хотите в своем модуле переопределить настройки другого модуля, то ваш модуль должен быть ниже в этом списке.
Последней важной и пока еще не рассмотренной директорией является директория modules. В этой директории будут находиться модули, разработанные нами для нашего приложения. Вместе с ZendSkeletonApplication поставляется один модуль Application, но прежде чем изучать его структуру, давайте разберемся с тем, что такое ServiceManager и зачем он нужен.
ServiceManager
В официальной документации (http://framework.zend.com/manual/2.2/en/modules/zend.service-manager.intro.html) сказано, что ServiceManager — это компонент, реализующий паттерн Service Locator, предназначенный для извлечения других объектов. Другими словами, это некоторая точка входа, которая позволяет из любого места приложения получить доступ к любым объектам, зарегистрированным в СервисМенеджере.
Регистрируются объекты в СервисМенеджере либо в кофигурационном файле module.config.php
в секции service_manager, либо в Module.php
в методе getServiceConfig()
, выглядит это примерно так (пример скопирован из документации):
<?php
// a module configuration, "module/SomeModule/config/module.config.php"
return array(
'service_manager' => array(
'aliases' => array(
// Здесь можно задать алиасы для зарегистрированных сервисов или для других алиасов
'SomeModule\Model\User' => 'User',
),
'factories' => array(
// Ключ — имя сервиса,
// Значение — либо имя класса, реализующего интерфейс FactoryInterface,
// либо экземпляр класса, реализующего FactoryInterface,
// либо любой PHP коллбэк
'User' => 'SomeModule\Service\UserFactory',
'UserForm' => function ($serviceManager) {
$form = new SomeModule\Form\User();
// Retrieve a dependency from the service manager and inject it!
$form->setInputFilter($serviceManager->get('UserInputFilter'));
return $form;
},
),
'invokables' => array(
// Ключ — имя сервиса,
// значение — имя класса, экземляр которого должен быть создан.
'UserInputFiler' => 'SomeModule\InputFilter\User',
),
'services' => array(
// Ключ — имя сервиса,
// значение — объект.
'Auth' => new SomeModule\Authentication\AuthenticationService(),
),
),
);
Имея приведенную выше конфигурацию СервисМенеджера в любом нашем контроллере теперь мы можем вызвать код вида:
$user_form = $this->getServiceLocator()->get('UserForm');
и объект $user_form будет содержать соответствующую форму.
Теперь настало время вернуться к модулю Application, входящему в ZendSkeletonApplication.
Модуль Application
Дерево каталогов этого модуля выглядит так:
config/
module.config.php
language/
<много *.po файлов с переводами>
src/
Application/
Controller/
IndexController.php
view/
application/
index/
index.phtml
error/
404.phtml
index.phtml
layout/
layout.phtml
Module.php
Начнем с Module.php. Этот файл объявляет класс Module с 3 методами:
onBootstrap()
;getConfig()
;getAutoloaderConfig()
.
Код метода onBootstrap()
в этом модуле отвечает за обслуживание маршрутов типа Segment (о типах маршрутов чуть ниже).
Метод getAutoloaderConfig()
сообщает ядру фреймворка где искать исходные коды модуля:
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
В текущем случае это директория modules/Application/src/Application
.
В методе getConfig() возвращается путь к файлу настроек модуля:
return include __DIR__ . '/config/module.config.php';
Этим файлом возвращается массив с настройками, содержащий следующие данные:
- элемент массива router — это массив содержащий список маршрутов, обслуживаемых модулем и список контроллеров, для каждого из маршрутов.
- view_manager содержит пути к шаблонам, которые будут использоваться приложением и ряд дополнительных настроек шаблонизатора.
- service_manager — список объектов, которые должны быть инициализированы СервисМенеджером.
- ряд других настроек.
Как и в других MVC-фреймворках в Zend Framework используются понятия: маршрут (route), контроллер и действие (action). Маршрут определяет адрес страницы, контроллер и экшен — действия, которые будут выполнены для отображения страницы. Каждый контроллер — это класс, а действия — это методы с именем вида nameAction() в нём. Как следствие, каждый контроллер может содержать несколько действий, например, контроллер BlogPostController(), который мы создадим далее, будет содержать действия addAction()
, editAction()
, deleteAction()
, viewAction()
и indexAction()
.
В настройках модуля Application определяется два маршрута:
return array(
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'index',
),
),
),
'application' => array(
'type' => 'Literal',
'options' => array(
'route' => '/application',
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
),
),
),
),
),
),
),
);
- За маршрут
/
(корень сайта, тип Literal) отвечает контроллерApplication\Controller\Index
. - за маршруты вида
/application/[:controller[/:action]]
(тип Segment) — контроллер и экшен переданные в качестве аргументов, то есть внутри модуля достаточно определить новые контроллеры и экшены и они тут же будут доступны по описанному адресу. Благодаря этому, маршрут/
здесь идентичен маршруту/application/index/index
.
Существует несколько типов маршрутов, их описание можно найти в документации: http://framework.zend.com/manual/2.2/en/modules/zend.mvc.routing.html.
Модуль Application содержит один единственный контроллер IndexController()
с одним единственным экшеном indexAction()
, который возвращает страничку с приветственным сообщением. Для отображения этой странички используются шаблоны, находящиеся в директории view модуля.
На этом я бы хотел завершить первую часть статьи. Во второй и третьей мы займёмся разработкой своего небольшого модуля и подключим к системе несколько внешних.