Zend Framework: идеальное веб-приложение (ч. 1 из 2)

Часть вторая

Задача: написать веб-приложение с помощью Zend Framework 1.10.x, которое идеально использовало бы возможности фреймворка для определенного функционала.

Лирическое отступление: конечно, по определению невозможно создать что-то идеальное, поскольку идеальное – это совокупность субъективных образов объективной реальности, а раз программистов много и у всех свои мнения по разным вопросам, то и договориться до идеального видиния веб-приложения не получится. Однако, ведь, всё-равно хочется сделать что-то по-настоящему хорошее и масимально близко к идеалу, правда?) И в действительности к стандартизации, идеализации кода приложений всё дело и идет. Яркий пример тому относительно недавно появившиеся тенденции к использованию паттернов и фреймворков.

Так к чему это всё? В n-ый раз перечитывая нередко меняющийся Zend Framework Quickstart, я заметил, что у них постепенно получается четкая и абсолютно прозрачная схема разработки веб-приложения. Для каждого случая, будь-то работа с формами или выемка данных из БД, есть свой паттерн, общепринятое решение (ну ладно, не всеобщее, а принятое умными дядьками из Zend). Значит, теоретически вполне возможно привести культуру написания кода к такому уровню, что программисты будут писать одинаковый, абсолютно правильный и понятный каждому код, свободный от уязвимостей и ошибок. Увидев постановку задачи, два абсолютно разных программиста в уме уже разметят будущую архитектуру веб-приложения, наборы классов и интерфейсов, и эти архитектуры будут абсолютно идентичны. Вроде Java Beans именно для этого и придумали, да? Для мира php я пока такого не встречал.

Скажете, это будет слишком грустно и неинтересно? Достигнув определенного уровня знания, программирование превратиться в рутину, однообразное складывание одних и тех же кирпичиков, в банальное ремесло? Как знать! Из простых каменных блоков строят и пирамиды в Египте, и великие китайские стены. В конце концов, важнее всего качественный и надежный результат. Впрочем, хватит лирики.)

Что же будет в этой статье? В ней приводится пример веб-приложения, в котором я попробую максимально верно использовать ZF и небольшой набор паттернов для решения простых и рутинных проблем, типа выемки данных из БД, запихивания их в формы, отображения списков и прочее. Конечно, это только моё видиние решения, и чувствуется, что некоторые паттерны я использую не совсем так, и вообще, логика обработки запросов должна быть не в том маппере, а вот в той модели и пр. пр. пр…)

Что будет в приложении:

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

Что будет «под капотом»:

  • Модели и мапперы для основных сущностей (статей и авторов);
  • Отдельные классы-шлюзы для работы с таблицами в БД;
  • Связка классов-шлюзов для выполнения агрегирующих запросов (в случае связей один-ко-многим, многие-ко-многим).

Ну поехали.)

Создание БД, структуры файлов, папок и классов приложения

  1. Создаем БД в MySQL (я предпочитаю phpMyAdmin, но можно проделать и трюк, используемый в QuickStart):
    -- --------------------------------------------------------
    
    --
    -- Структура таблицы `authors`
    --
    
    CREATE TABLE IF NOT EXISTS `authors` (
      `id` int(11) NOT NULL auto_increment,
      `f_name` tinytext NOT NULL,
      `m_name` tinytext NOT NULL,
      `l_name` tinytext NOT NULL,
      `organization_id` int(11) default NULL,
      PRIMARY KEY  (`id`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Структура таблицы `organizations`
    --
    
    CREATE TABLE IF NOT EXISTS `organizations` (
      `id` int(11) NOT NULL auto_increment,
      `name` mediumtext NOT NULL,
      PRIMARY KEY  (`id`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
    
    -- --------------------------------------------------------
    
    --
    -- Структура таблицы `papers`
    --
    
    CREATE TABLE IF NOT EXISTS `papers` (
      `id` int(11) NOT NULL auto_increment,
      `file` mediumtext,
      `title` mediumtext NOT NULL,
      `title_en` mediumtext,
      `source` mediumtext,
      `publisher` mediumtext,
      `date` date default NULL,
      PRIMARY KEY  (`id`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;
    
    -- --------------------------------------------------------
    
    --
    -- Структура таблицы `papers2authors`
    --
    
    CREATE TABLE IF NOT EXISTS `papers2authors` (
      `paper_id` int(11) NOT NULL,
      `author_id` int(11) NOT NULL,
      UNIQUE KEY `paper_id_2` (`paper_id`,`author_id`),
      KEY `paper_id` (`paper_id`),
      KEY `author_id` (`author_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    

    Другими словами, у нас будут статьи, у статей будут авторы, авторы будут состоять в организациях.

  2. Создаем приложение с помощью Zend_Tool и добавляем модуль papers.  Сей нехитрый процесс я описывал в одной из предыдущих записей.
  3. Конфигурируем приложение для работы с БД (далее пойдут команды для ZF Tool для консоли):
    zf configure db-adapter "adapter=mysqli&username=test&password=test&dbname=text&charset=utf8"
    
  4. Создаем классы-«шлюзы» (Table Data Gateway) для работы с таблицами в БД.
    zf create db-table.from-database papers
    

    Эта команда пройдется по всем таблицам в указанной в конфиге БД и создаст для каждой примерно такой класс:

    <?php
    
    class Papers_Model_DbTable_Organizations extends Zend_Db_Table_Abstract
    {
        protected $_name = 'organizations';
    }
    

    Позже они нам пригодятся для связки данных в таблице и автоматизации процессов выборки данных из разных таблиц (ага, организуем мини-ORM).

  5. Делаем необходимые actions, модели и формы:
    zf create action addPaper index 1 papers
    zf create action editPaper index 1 papers
    zf create action deletePaper index 1 papers
    zf create model paper papers
    zf create model author papers
    zf create form EditPaper papers
    zf create form AddPaper papers
    

Как видите, имея в своем арсенале такой мощный инструмент как Zend_Tool, можно буквально за минуты построить фундамент приложения.

Модели, мапперы, врапперы – что лишнее?

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

Однако, не всё так плохо. Zend Framework просто напичкан этими паттернами, и местами подсказывает, какие решения стоит использовать. Сейчас мы попробуем освоить три паттерна: модель-вид-контроллер (Model-View-Controller, MVC), маппер (Mapper, распределитель) и шлюз к данным таблицы (Table Data Gateway).

MVC

Паттерн Model-View-Controller

Один из самый распространненых паттернов. В двух словах, позволяет разделить логику работы с данными от логики их представления.  ZF реализует этот паттерн в полной мере. На иллюстрации выше показан путь прохождения запроса к веб-приложению, использующего архитектуру MVC. Подробнее.

Mapper

mapper

Паттерн, который позволяет разделить представление данных в ZF и в БД. Например, у нас есть объект «ученый», который имеет свойства ФИО, возраст, место работы и др. В БД такой объект может быть «размазан» по множеству таблиц и должен будет каждый раз из них собираться с помощью какого-нибудь  SQL-запроса с  JOIN’ами. Однако, в самом приложении этот объект будет цельной сущностью с рядом свойств и методов.  Маппер как раз и является «переходником» между представлением сущности в БД и в приложении. Подробнее.

Table Data Gateway (шлюз)

В нашем случае класс, построенные по этому паттерну, будет проводником запросов на выемку данных к самой БД. Казалось бы, эту функцию берет на себя маппер? Но маппер занимается вопросами связи сущностей (типа «ученый», «организация») с местом их хранения (в нашем случае это СУБД MySQL), в то время как шлюз является универсальным объектом доступа к таблице в БД. Другими словами, для маппера не важно, используем ли мы MySQL, MsSQL, SQLite или любую другую СУБД, потому что маппер для выемки данных посылает запросы шлюзу. Шлюз же имеет в своем распоряжении унифицированный интерфейс (набор методов) для выемки данных и набор адаптеров, которые позволяют проводить запросы к БД различного типа. Получается, если вы вдруг захотите использовать вместо MySQL SQLite, то всё, что придется сделать, — это просто изменить конфигурацию (обычно она в файле «/application/configs/application.ini»), выбрав адаптер sqlite.

От теории к практике

В следующей и последней части статьи, вооружившись знаниями о паттернах, реализуем их в нашем приложении.