Pull to refresh

Zend Framework первой свежести, ч1: зендируем MVC

Reading time8 min
Views3.6K
Меня тут разбанили по просьбе Дина, и я решил принести пользу обществу. Поскольку я дурак и ничего не умею, дай, думаю, напишу о ZF — офигенной штуке, которую все почему-то искренне ненавидят. Надо успеть, правда, пока по НТВ не стали показывать Пелевина (жаль, ненастоящего).

Главная беда всевозможных QS в том, что они действительно quick и действительно start, но если делать все как там, получится не особенно «масштабируемое» приложение, с которым не очень понятно, что делать. По сравнению с официальным quickstart'ом прошлой версии Zend Framework'a, новый просто великолепен, но не лишен недостатков. Я пойду с другого конца: вместо того, чтобы обьяснять, как сделать что-то бессмысленное и типовое, попробую (!) рассказать, как писать приложение вообще, используя всевозможными способами Zend Framework. А, пора хабракат делать.

Zend Framework — волшебный и счастливый фреймворк на языке PHP для настоящих мужиков ( ;-) ). Для меня ZF — единственная возможность написать легкодописываемое, масштабирумое и вполне сопроводимое веб-приложение за очень короткий срок (часов за 6 легко делается без особого напряга набор авторизация+регистрация+посты+лента+древовидные комменты+чай+кофе+потанцуем), выгодно отличающееся от решений, где вообще почти ничего делать не придется (Wordpress, Drupal, ...) тем, что программисту не навязывается ничегошеньки вообще: ZF, в принципе, набор связанных между собой классов: использовать можно только те, которые хочется, сохраняя тот стиль разработки, который вам больше нравится. Вам не хочется использовать Zend_Auth для авторизации? А кто вам запретит написать своё что-нибудь? ;) При этом, он красиво и очень логично построен (классы всякие, паренты, интерфейсы, вся фигня): его удобно переписывать, например, просто унаследовав какой-нибудь свой класс от стандартного зендовского и дописав то, чего так хочется дописать (только не устраивайте в комментах холиваров «ZF против чего-нибудь». Пожалуйста. Вообще не устраивайте).

Ага! Итак, то, что у нас будет получаться, будет (надеюсь) сложновато, но логично и более-менее красиво: будет легко доработать и легко понять, где у нас что. Я — идиот и дилетант, а потому никак не могу претендовать на то, что все мои ходы действительно разумны, правильны и что я использую все возможности: за ценные советы, добавления, опровержения буду говорить «спасибо» и так далее. Ах да, и я думаю, что одной статьей тут дело не ограничится. Более того, первая может показаться достаточно банальной, в таком случае подождите вторую 8) Конкретно в этой я буду рассказывать о работе с Zend'овской реализацией MVC, позже буду придерживаться ее же, но надеюсь не забыть о труженниках, которым она не нравится.

Решил повториться: «Я не хакер, я только учусь». Знаете лучше? Давайте перепишем эту/напишем следующую статью вместе, так, чтобы было еще круче ;-)

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

Поехали!

Первым делом стоит скачать ZF, а потом пробежаться по квикстарту, хотя бы до «Create a Configuration and Registry». Потом идет какая-то фигня с SQLite (я никогда не пойму смысла скьюлайта), а дальше вообще используется Zend_Form (я противник такого рода развлечений, сорьке — весь html будем писать ручками или просить об этом верстальщиков, если хотите, разбирайтесь с ним сами, ничего сложного нету). От того костяка, что у вас получится, мы и будем плясать.

Итак, у нас есть примерно следующее (чего нет — досоздайте, если чего лишнее — ну сами придумайте, что с этим делать):
+ application/
  + config/
    * app.ini
  + controllers/
    * IndexController.php
	* ErrorController.php
  + layouts/
	+ scripts/
	  * layout.html
	+ helpers/
  + models/
	+ dbtable/
	* SuperModel.php
  + views/
    + scripts/
	  + index/
	    * index.phtml
      + error/
	    * error.phtml
	+ helpers/
  * routes.php [1]
  * bootstrap.php
+ library/ // [2]
  + zend/
	// тру-ля-ля...
+ public/ (он же htdocs/ у меня, но это неважно)
  + static/
    // [3]
  * index.php


[1] — в этом фаиле находятся все роуты. Он инклудится потом в bootstrap.php (после qs роуты должны идти в самом бутстрапе, но обычно у меня их многовато выходит)
[2] — я бы вынес эту папку на уровень выше, то есть, скажем, если у вас это все в /home/va1en0k/coolsite.ru/, то почему бы не держать библиотеки в /home/va1en0k/library/ вместо /home/va1en0k/coolsite.ru/library/: остальным прогам будет легче до них добраться
[3] — тут будут разные крутые фаилы CSS, JS, картинки — вся байда. Их можно разнести по styles/, images/, можно вынести на какой-нибудь другой сервер, никто не заставляет держать их здесь: HTML-то весь мы сами пишем

Все достаточно просто, обычный MVC. Алгоритм написания программы такой (он несколько не похож на «разработку через тестирование», которую так все любят, но, в принципе, можно устраивать тесты вида, контроллера и модели до/после/во время их разработки):
  1. Для начала, нам нужно нечто, происходящее при определенном действии юзера: нас об этом дизайнер попросил или там еще что-нибудь;
  2. Действие — всегда вызов урла какого-нибудь. Придумываем урл (он может быть любой, но мы все очень радуемся урлам вида /«controller_name»/«action_name». Но, скажем, для получения поста лучше какой-нибудь такой: /post/12321, не правда ли?), решаем, каким он будет обрабатываться контроллером (никакой не подходит? Создаем новый в /application/controllers/), добавляем к этому контроллеру действие;
  3. Создаем /views/scripts/назване_контроллера/название_действия.phtml, рисуем там нужный нам html;
    • Там где надо вывести что-то, что не выводится на других страницах, так и пишем: <?=$this->shachlo?>, записываем или запоминаем, что нам нужно;
    • Там где надо вывести что-то, что выводится много где, например, имя какого-нибудь пользователя, пишем что-то вроде <?=$this->printUser($this->user)?>;

  4. Теперь идем и создаем все хелперы, которые нам нужны (хелперы — это функции, которые вызываются так: <?=$this->printUser($this->user)?>). Их пишем в /views/helpers/. Лучше выносить их отдельно для того, чтобы можно было в любой момент к именам юзеров добавить какую-нибудь иконку или применить какой-нибудь еще стиль. Или заменить их все на слово ЩАЧЛО, чтобы все юзеры просто охренели от удивления! %)
  5. Подпрыгивая от счастья, пишем действие. Весь код действия можно (почему бы, собственно, не) делить на 3 части:
    • Разбираемся с аргументами: получаем данные из форм или там из урла (если урл был /user/12345);
    • Делаем то, что от действия требуется: запускаем в космос ракету или отправляем сообщение таксисту на пейджер. Если нам нужны данные из БД, или если нам нужны данные с какого-нибудь Last.fm или твиттера, или еще какие-нибудь данные — любые вообще — просим о них модель. Если модель [пока] не умеет выдавать, просим так, будто умеет: $this->m->twitter->getUser(«va1ka»); или там $this->m->getUsersTable()->getUser(«va1en0k»); — как хотите, так и запрашивайте;
    • Выдаем view все, что он просил в шаге 3: $this->view->shachlo = «popyachtsa»; и т.п.;
  6. Дописываем в модель все, что мы у нее просили в предыдущем шаге.


Этот алгоритм почти универсален (если нам нужно то, что происходит при любом действии юзера, надо действовать чуть по другому), и я рекомендую придерживаться его максимально четко. Почему сначала вид, а потом остальное? Ну, по двум причинам, во-первых, так советуют 37signals, а во-вторых, это самый разумный способ понять, что нам нужно и в каком виде получать. Важно следить, чтобы все запросы к данным шли только инкапсулированно через модель: тогда, о cчастье, мы сможем в случае чего махнуть не глядя базу данных на другую или добавить симпатичный кеш, ускоряющий нашу программу, как сброс ступенчатого модуля. Важно следить, чтобы весь HTML, ну, то есть, совсем-совсем весь, шел через находящееся в папках views или layouts, в хелперах или просто так.

А теперь конкретнее по трём китам MVC.

Вид

Как я уже красноречиво намекнул, в Видах должен быть весь-весь HTML. Любые ошибки, что угодно, вот всё, что происходит в контроллере и — тем более — в модели должно быть лишено тегов и являться голой строкой. Любые константы вывода (я имею в виду, например, теглайны или «Ошибка! Имя юзера должно быть до 25 символов! Убей себя!») стоит тоже хранить во view.

Как это cделать? На помощь спешат хелперы! Создаем хелпер (дальше идет удивительный код, который не факт, что стоит использовать as is, но, в принципе, в нем нет ничего совсем ужасного. Просто думайте сами, как хотите организовать происходящее):
class Zend_View_Helper_Errors extends Zend_View_Helper_Abstract {
	public function errors($err) {
		if (!sizeof($err)) return '';
		
		$html = '<div class="errors">';
		
		foreach ($err as $er)
			$html .= '<p>'. AppErrors::getErrorString($er) .'</p>';
		
		$html .= '</div>';
		
		return $html;
	}
}

А также класс со статическим методом:
class AppErrors {
	static function getErrorString($er) {
	  // возвращаем строку. Берем ее откуда-нибудь: массива там или БД или еще откуда-то, переводим, если надо, через Zend_Translate и возвращаем
	}
}


Сам $err — аргумент хелпера — это массив «ошибок». Ошибка может кодироваться просто числом каким-нибудь, а может быть, и массивом, скажем, array( 'err_id' => 666, 'err_arg' => 'satan' ), если у ошибки есть аргументы %) Ну или вообще обьектом, у кого насколько фантазия извращена.

Контроллер

Контроллер состоит из действий. Действия — костяк (прям как в структурно-функциональной социологии на основе интеграционного подхода) приложения. Так вообще выглядит идеальный запрос от дизайнера программы к программисту: «Давай, если жать вот на эту кнопуську, будет происходить то-то». Вот, «то-то» это и есть действия (кому я это обьясняю?).

Прежде чем плодить контроллеры пачками, я предлагаю сделать хитрый ход: создать наши собственный front и action контроллеры и наследовать уже от них. Тогда, в случае чего, мы сможем, например, запрещать посещение любых страниц без входа на сайт (как в лепрозории, например), выставлять разные нужные для экшн-контроллеров переменные и так далее:
class SchachloFrontController extends Zend_Front_Controller {
	// 
тут не забываем перегрузить getInstance(), как доктор нам прописал
} // не забудьте поменять bootstrap.php!
class SchachloActionController extends Zend_Action_Controller {
	// по-моему, ниче не обязательно перегружать %)
} // и наследуем все экшн-контроллеры от него (это те, которые в /application/controllers/


Ну, а дальше все более-менее понятно. Советую максимально расчленять код на мелких функции, пихая их в модель или оставляя на долю хелперов (как view helpers, так и action helpers). И не забывайте: любые данные стоит получать в model, даже отчество вашей бабушки %) Хотя не, его лучше в конфиг, если нужна только ваша бабушка.

Модель

С моделью, на самом деле, у меня больше всего сомнений по неопытности. Ну, я вот лично обычно делаю так:
Для каждого типа данных создаю таблички в БД. Там для поста, для коммента, все дела. Если у типа данных есть переменная-массив (например, теги для поста), для него, ясен пень, отдельную табличку. Потом для каждой таблички создаю в /application/models/DbTable/ свой файлик. Ну и главный файлик — SuperModel.php — в котором наша прелестная Анжелина Джоли, возвращающая своими методами все таблички (которые у нее хранятся в статичных переменных). Аналогично, рядом пихаю нужные классы для работы с файловой системой или внешними источниками данных (недавно вот с last.fm работал, при помощи Zend_Rest_Client вообще без напряга), а в супермодель пихаю методы для их получения.

Итак, если мне вдруг нужно что-нибудь узнать, я смотрю, скольких источников данных это касается. Если одной таблички: добавляю метод в ее собственный класс. Если одной таблички и еще нескольких для нее (типа, получаем пост из таблички posts и теги к нему из tags и т.п.) — то тоже в нее. А если нам нужны 2 и больше более-менее равноправных таблиц — кладу метод в модель, но работу с каждой из участвующих таблиц организую в ней самой.

Че еще сказать? Ах, да. Не забывайте про кеш. Можно добавлять в каждый метод слежку за кешем, а можно взять и сделать сразу 2 модели: одна будет такая, как описано выше, а другая будет сквозь кеш вызывать первую, заодно отключая его, когда он вообще не нужен. Я про кеш, наверное, еще напишу, если меня снова не забанят. :D

Уф. Вроде 13 килобайт, а не особо много и рассказал. Любитель, чего уж тут! Жду ваших отзывов.
Tags:
Hubs:
+65
Comments135

Articles

Change theme settings