Делимся секретами создания сайтов

mvc (некоторые сложности с контроллерами)

Все знают как работает mvc - паттерн. Кто-то хорошо, ну а кто-то приблизительно.
Введение:
Если максимально упростить, то имеем нечто следующее.
1. Имеется шаблон, общий для всех страниц.
2. Где-то внутри имеется переменная, ну например {body_content_tpl}
3. После того, как мы узнали имя контроллера и метода (myController). Мы выполняем их myController->myAction(). Потом бла-бла неважно что именно, но в итоге мы получаем готовый кусок хтмл-я который подставляется в тот самый {body_content_tpl}

Собственно, в итоге контроллер отвечает за кусокдинамичного наполнения сайта.
Вопрос: Допустим помимо основого {body_content_tpl} , нам нужнореализовать кучу всяких меню, блоков, шапок, футеров и т.п . То есть кучу всякого иногда статичного, иногда не статичного побочного материала. И хочется знать - как правильно это сделать ?
Раньше я всегда делал класс, который обзывал preController -который запускался перед самим контроллероми выполнял все эти бяки. Однако не совсем устраивает такой подход.
Как решается эта проблема __правильно__. Или где почитать ?

Ответы: 17 → “mvc (некоторые сложности с контроллерами)”

  1. Stepanov Evgeny Ответить

    Ну, к примеру одним шаблоном сложно обойтись для всего сайта, если нужно реализовать "кучу всяких меню, блоков, шапок, футеров и т.п".

    Из первого что пришло на ум это с помощью идеи "Активный шаблон" (в mzz framework кажись такая)

    Но а вообще концепция MVC продразумевает следующее:
    Model<->Controller<->View
    Контроллер запрашивает данные у модели и после все возможных преобразований и проверок передает их в соответствующий шаблон на подстановку.
    Следовательно подбор шаблона осуществляется контроллером.
    К примеру используя "смарти" шаблон получив данные может сам выбирать погрузку различных блоков шаблона.
    А вообще вариантов много.. :)

  2. Антон Шурашов Ответить

    После того, как начал использовать cake, тоже задался этим вопросом. Там любое действие контроллера может использовать собственный шаблон. А вот с блоками никак не разберусь… Вернее не пойму, как их делать динамическими, т.к. блоки можно вызывать только из шаблона (назначая содержимое из контроллера)… А если вдруг мне не нужен сейчас этот блок? Что, создавать и вызывать новый шаблон? Или писать в шаблоне "если нет данных – блок опускать" [текстовый псевдокод =)]????

  3. Андрей Бусаев Ответить

    А почему бы не оставить определение блоков контроллеру? Присвоить каждой страничке набор блокв и пусть контроллер подключает необходимые.

  4. Stepanov Evgeny Ответить

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

    Но нагружать один контроллер всем, неправильно.

    делать нужно так чтоб при любом изменении – изменений было минимум!! (<=2)

  5. Плиско Вячеслав Ответить

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

  6. Антон Шурашов Ответить

    [А почему бы не оставить определение блоков контроллеру? Присвоить каждой страничке набор блокв и пусть контроллер подключает необходимые.]

    Кажись, надо переходить к этому варианту…

  7. Андрей Codeator Ответить

    У мну работает так… есть контроллеры активные (из урла вызываются UrlMapper-ом), и пассивные блоковые (меню динамическое например)

    Активный вызывает Layout страницы, а в лэйауте уже коды пассивных блоков… например страница news активный контроллер news вызывает layout фронтенда по дефолту, а там уже {block controller=news action=get5last}

    Эта проблема шикарно решена в фрэймворке на котором написан движок для соц. сетей PhpFOX (погуглите, шикарная штука, но есть и свои очевидные минусы, движок хороший, по-моему разработка нашего Aitoc)

  8. Александр Белковец Ответить

    в symfony есть использование slot'ов и включаемых блоков в общий layout, очень удобно

  9. Вячеслав Шиндин Ответить

    #1

    Я себе представляю MVC как-то так:

    * Составляющая M – упорядоченный склад сущностей, будь то персистентные классы, драйвера различных вебсервисов или же полиморфные рендеры, генерирующие pdf, zip, email, или просто html контент, и т.п.

    * Составляющая V – набор конечных txt/html/и т.п. шаблонов используемый только одним определённым контроллером.

    * И самое интересное C – может содержать различные actions, у меня они обычно в виде методов showDefaultPage, processLogin, getRemoteContent и т.п.

    Так вот, из django мне понравилась идея наследования шаблонов, у себя я её реализовал как наследование контроллеров.

    И получилось так:
    IndexController наследуется от BaseController, тем самым наследует прикреплённые к базовому котроллеру шаблоны.

    PHP code:
    >class IndexController extends BaseController {
    >
    >>>public function showDefaultPage() {
    >>>>>$indexPage = $this->createRender(/*'html'*/); // instanceof HtmlRender
    >>>>>$completePage = $this->widgetCombiner($indexPage);
    >>>>>$completePage->render('index'); // … 'index' . '_template.tpl'
    >>>}
    >
    >}

    Параметры передаются до вызова ->render(), выглядит всё так:
    >$indexPage->myVar = "Hello, World!"; // в index template
    >…
    >$completePage->title = 'Redefined title.';
    >$compeltePage->getChildView()->anotherVar = 'Text text text.';

    Упустил один важный момент, на счёт приклеивания менюшек, сайдбаров и прочих блоков. У меня сделано так: менюшка – это отдельный контроллер (вернее виджет), у менюшки есть свой View (рендер+ шаблоны), параметры, всё как в обычном MVC:

    PHP code:
    >class MenuController extends Controller {
    >
    >>>public function __construct(Render $widgetView, Render $currentView) {
    >>>>>$widgetView->setRenderProperty('view', $currentView);
    >>>>>$widgetView->setRenderProperty('template', 'menu');
    >>>>>$widgetView->menu = $this->getMenu();
    >>>>>parent::__construct($widgetView);
    >>>}
    >
    >>>protected function getMenu() {
    >>>>>return array(
    >>>>>>>'main' => array(
    >>>>>>>>>'Первый' => 'first',
    >>>>>>>>>'Второй' => 'second'
    >>>>>>>)
    >>>>>);
    >>>}
    >
    >}

    Метод getMenu вынесен специально отдельно, чтобы его можно было переопределять в других контроллерах. Так же в базовых контроллерах есть списки по которым производятся подключения контроллеров таких как MenuController, SidebarController и т.п.:

    PHP code:
    >class BaseController extends Controller {
    >
    >>>protected $controllersQueue = array(
    >>>>>'sidebar',
    >>>>>'menu',
    >>>>>'header',
    >>>>>'footer',
    >>>>>'frontend'
    >>>);
    >
    >}

  10. Вячеслав Шиндин Ответить

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

    PHP code:
    >class BackendHeaderController extends HeaderController {
    >
    >>>protected function getTabs() {
    >>>>>return array(
    >>>>>>>'Tab 1' => $this->tab('url1'),
    >>>>>>>'Tab 2' => $this->tab('url2'),
    >>>>>>>'Tab 3' => $this->tab('url3'),
    >>>>>);
    >>>}
    >
    >}

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

    PS:
    В итоге у меня в серверной части всё собирается как лего, легко и просто из таких вот мелких кусочков, каждый кусочек легко заменим, или дополняем.

    PPS:
    В шаблонах, кстати, тоже хочется навести порядок. Уже напоявлялось много корявых яваскриптовых MVC фреймворков. Кто-нибудь порекомендует какой-нибудь хороший новый, не из старых динозавров, типа jQuery, прототайпа, куксду, ExtJSа ?
    Кстати, о Qooxdoo коллеги хорошо отзываются.

  11. Плиско Вячеслав Ответить

    jQuery – это как раз новичок ;)
    фреймворки хороши своими плагинами, потому я перешёл на jquery, хотя prototype мне больше нравится

  12. Вячеслав Шиндин Ответить

    Я имел в виду не "из старых", а "из популярных", оговорился..
    Сам часто использую jQuery и Prototype.

    А идея такая, разделить html и jaavscript настолько, насколько это возможно, определиться со структурой js-контроллеров, вынести js-модели в сторону от js-контроллеров, js-транспорты(AJAX/Comet/Flash и всякие обёртки работающие как будь-то бы с сокетами из javas?1?ript, но через сервер посредник).

    В чистом виде MVC для клиентской части применить не получится, т.к. тут две стороны составляющей View. Одна – это генерация контента для отправки серверу AJAX/JSON/т.п. запросов, и вторая – генерация контента для отрисовки в браузер, для изменений в DOM-модели.

    Javas?1?ript код представляю себе примерно таким:
    >Controller.ShopController = {
    >>>showPaymentInfo: function(…) {…},
    >>>buyNow: function(…) {…},
    >>>updateCart: function(…) {
    >>>>>// factory method for getting model
    >>>>>this.createObject('AJAXRequest').send(url, callback, params);
    >>>}
    >}

    >Model.AJAXTransport = {
    >}

    Где-нибудь, например, в Smarty добавить свой плагин для того, чтобы вызовы js-контроллеров, передача им параметров и т.п. стала проще.

    Как-то так:
    // в PHP контроллере
    $view = $this->createRender('smarty');
    $view->mySimpleArray = range(1,5);
    $view->cartID = $cart->getId();
    $view->render('my_tpl_name');

    // в Smarty шаблоне, после некоторых доработок самого Smarty
    <script>{$mySimpleArray|jsController:"Shop"}</script>
    <button onclick="{jsController('Shop.updateCart', $cartID)}" value="Update" />

    // а примерно такое бы наблюдалось в браузере:
    <script>Controller.ShopController.prototype.props.mySimpleArray = [1,2,3,4,5];</script>
    <button onclick="Controller.ShopController.updateCart(137)" value="Update" />

  13. Плиско Вячеслав Ответить

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

  14. Вячеслав Шиндин Ответить

    Код во вьюхе не смешан с PHPшным MVC, никак.
    Я всего-лишь добавил в смарти плагин с названием jsController, но его можно назвать как-нибудь по-другому, а можно вообще обойтись без него:

    // вьюха:
    <script>Controller.ShopController.prototype.props.mySimpleArray = [
    {foreach from=$mySimpleArray item=i}
    {if не первый}, {/if}
    {$i}
    {/foreach}
    ];</script>
    <button onclick="Controller.ShopController.updateCart({$cartID})" value="Update" />

    Но вариант в моём предыдущем посте мне нравится больше, немного подкорректировал свой нейминг:
    <script>{$mySimpleArray|assign_js_controller:"Shop"}</script>
    <button onclick="{run_js_controller('Shop', 'updateCart', $cartID)}" value="Update" />

    Во вьюхах модели джаваскриптовые, для навороченных js-приложений, когда на одной странице используется много различных активных элементов, аджакс команд, валидаций и прочего хлама.

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

    Для этого логично создать в папочке javas?1?ripts/models/shoppingcart.js
    Аналогично – окошко предварительного просмотра, которое в себе может отображать различные виды элементов, детали, thumbnails, и прочее: javas?1?ript/models/preview_window.js
    Аналогично – javas?1?ript/controllers/pricelist_controller.js

    А в js-контроллеры инкапсулировать методы, например, такие как метод отвечающий за добавление элемента в корзину: при вызове метода, в блок где располагается превью окошко включится индикатор типа [loading... please wait], затем аджакс запросом получим detailInfo для выбранного товара, выведем это в превью окошке, обновим цифру кол-ва товаров напротив корзинки, уберём из списка продаваемых только что зарезирвированный товар (либо аджаксом, либо уменьшим счётчик количества данного товара на складе…), тут же синхронизируем мини-окошко показывающее мини-иконки товаров, находящихся в корзинке, и в таком духе.

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

    PS: все названия в примерах кода выдумывал на ходу, не опираясь на какой-то определённый существующий проект, но с подобрыми заморочками в толстых "джаваскрипт-хтмльных" клиентах сталкивался не один раз…

  15. Вячеслав Шиндин Ответить

    Вот ещё пример, когда хорошо было бы разделять и влавствовать:
    1. передо мной страничка
    2. жму на кнопку обновления thumbnail, через аджакс генерится новая картинка и отображается вместо старой (например, это нужно, когда я сменил рамку, или подпись, или выбрал другой рекламный банер, прикручимываемый обязательно к аватару, генерируемому системой)
    3. жму кнопку редактировать подпись, она тоже меняется аджаксом
    4. на этой же странице вижу небольшой блок со списком активных юзеров, хочу его обновить не обновляя всей страницы, и там как раз есть кнопочка (или же список юзеров обновляется автоматически, допустим, раз в 5 секунд)
    5. Ниже, вижу, на моём аккаунте перечислены товары, которые я выложил на аукцион.
    6. Жму на маленькую кнопочку краткой инфы по одному из товаров. Справа в маленьком блоке отображаются детали полученные аджаксом.
    7. В этих деталях жму обновить thumbnail, тыкаю на мелкую иконку карандаша рядом с названием, чтобы название превратилось в текстовый input с названием, и вместо карандаша кнопка [update].

    И в таком духе…
    Это я всё к тому, что во вьюхах тоже есть логика(не путать с серверной логикой), и тоже должен быть порядок в джаваскриптах, вёрстке и стилях.

    PS: После всего высказанного, меня навело на мысли о протоколах взаимодействия серверной логики с клиентской, на основе AJAX и прочих транспортов.

  16. Вячеслав Шиндин Ответить

    Кстати, о MVC. Есть и другие родственные с ним паттерны, MVT (в django),
    ещё в абревеатурах встречал буковку P (от Presentation). Но всё это сводится к наведению порядка только в серверной части.
    А как же быть с клиентской?
    Ведь сейчас некоторые сайты называют себя веб-приложениями, т.е. клиент-серверными веб-приложениями, а до сих пор проектируются толькочастично.
    Грубо говоря _клиент-серверное_ веб-приложение:
    - на проектирование, утверждение начальной спецификации, оценку серверной части уходит X времени
    - на кодирование Y
    - на проектирование клиентской части приложения обычно уходит 0 времени
    - на кодирование Z

    Так вот меня волнует этот 0.

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

  17. Плиско Вячеслав Ответить

    MVC как раз и предполагает то, что описал ты, но почему-то у нас предпочитают пользоваться только шаблонами. кстати, в том же смарти изначально была встроена поддержка вот таких js виджетов, типо popup с билиотекой оверлиб.
    Если бы не сумбурность изложения, то статья достойная хабра, а не вконтакта. Но уж сильно закостенело виью == шаблон, хотя реально вьюе !== шаблон

Ответить