Pull to refresh

Comments 75

ИМХО на каждый пример как не стоит делать, надо добавлять пример как правильно надо делать.
Поддержу на счет хелперов url(), baseUrl(), а так же на счет локализации — делать нужно сначала! Был неприятный опыт тоже.
А вот Context-хелпер не использую, т.к. стараюсь в одном акшене обрабатывать как ajax-запрос так и обычный.
например в простом случае:
// принять данные и обработать
// а в конце
if($this->_request->isXmlHttpRequest())
exit(Zend_Json::encode(array(success => 1)))
else
$this->_redirect('/content');

не считаю себя преступником за это :)
мой выбор:
return $this->_helper->json(array(success => 1));
context-switching как раз и нужен для того чтобы упростить использование одного и того-же экшена для ajax-запросов и обычных.

$this->_helper->AjaxContext()->addActionContext('ajax-handler', 'json')->initContext('json');

Эта строка приведет к автоматическому переводу всех переданных во view данных в формат json, но только в случае, если запрос был совершен чрез xhttprequest. Иначе будет осуществлен просто обычный рендеринг view.
Видимо я изначально не разобрался с этим. И получается до сих пор делаю велосипед проверкой isXmlHttpRequest().
Спасибо, что просветили. Хорошо что я написал каммент.
Использовать один и тот же экшен для двух разных запросов… а оно надо? ИМХО это логически не очень-то правильно — допустим в одном случае потребуется слегка усовершенстовать логику, в итоге (если программист не знает, что этот экшен используется для обработки 2х типов запросов), модификация для первого случая повлечет автоматичсескую модификацию и для второго… что потенциально может привести к ошибке.
Ну в таких особых случаях делать два акшена. Все таки чаще случается что обработка не отличается, а отличается только ответ, соответственно не нужно плодить addAction() addAsyncAction() etc.
При таком подходе не получится тестировать код екшена. Exit( и die) убивают процес phpunit. Столкнулся с таким недавно.
>> Использование встроенных методов модели, вместо написания своих

Принципиально, требование уместное, но когда запросов вида «выбрать список, отфильтровав и отсортировав» становится много — свой метод начинает превращаться в клон fetchAll. Ну или в огромное множество методов, отличающихся минимально
Поддерживаю. Это тот случай, когда стоит пользоваться не правилами, а здравым смыслом.
Отсутствие проверки корректности данных на стороне сервера — это не столько к теме Zend Framework-а, сколько к теме секьюрности при веб-разработке (и даже не обязательно на PHP) :)
А вообще очень занятная статья. Касательно обработки ajax-запросов и выдачи JSON от себя могу добавить, что использую следующую конструкцию в экшенах:

public function listAction()
{
//....
$resObj = new stdClass();
$resObj->data = $someData;
$resObj->totalCount = $totalCount;
$this->_helper->json($resObj);
}

Инициализация view в данном случае не производится
Я в начале поста уточнил, что не все будет касаться ZF
Я а начале поста уточнил что не всё будет связано с ZF
точно! sorry! как-то пропустил это :)
По-поводу обращения к методам моделей из контроллеров — мне кажется очень спорный впорос.
Не вижу ничего криминального в том, чтобы использовать например:
$model = new Users();
$data = $model->find($id)->toArray();

Ясное дело, когда выборка данных сложна и нетривиально — то её нужно инкапсулировать в метод модели, но писать обертки под уже существующие методы не имеет смысла помоему.
Смысл в инкапсуляции таких выборок внутри модели. Если вам вдруг понадобится добавить что-то вроде:
$data = $model->find($id)->where('is_active = TRUE')->toArray();
То отыскать все вызовы по всему проекту будет не просто
Чаще всего бывает так что одна модель используется в одном модуле, так что искать тут особо нечего.
Контроллеров может быть много.
Ещё одним примером того почему использование своих методов предпочтительнее — это проверка результатов выборки. Т.е. обработка ситуации когда find не вернет никаких результатов. Т.е. у нас получается дублирование кода, что не очень хорошо
Разным контроллерам, как правило, требуются данные разного порядка, у них и фетчи разные. Выносить фетчи в модели конечно бывает нужно, особенно когда они используются в разных местах. Но когда модель оказывается забита методами, делающими элементарные вещи, каждый из которых используется в своём контроллере — выглядит это не очень красиво.
Ну, имхо конечно, но все-таки, логика модели должна быть в модели, а не контроллере, иначе вся прелесть MVC теряется
какие-то у вас простые проекты.
Согласен! Но судя по фразе «отыскать все вызовы» — подразумевается, что выборка используется многократно во многих контроллерах. В таких случаях конечно же имеет смысл переносить в модель. Поэтому я и написал, что это неоднозначный вопрос, т.к. многое зависит от предметной области, и что именно и как часто мы собираемся извлекать из базы.
Автор забывает тут указать вещь. Код в примере 1 совершенно нормален и верен, пока не противоречит принципу DRY. Если таких запросов у него больше чем 2, то да — нужно перемещать в модель, если 1 — не имеет смысла, если 2 — ещё терпимо.

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

От такого, увы, не застрахуешься, но и гемора в обоих вариантах примерно поровну.
Есть несколько вариантов разработки моделей, одна из которых no model, когда в model указывается название таблицы, ключей и связей. Все остальные манипуляции делаются с rowset и row уже из контроллеров.
Говорить что этот подход неправильный — неправильно, т.к. он есть и в некоторых случаях его даже уместно использовать.
Второй вариант это light model, когда используются дополнительные функции в модели, это как раз вариант автора статьи.
И третий вариант, который используется чаще всего в сложных проектах — это heavy model. В данном подходе используются как pre-методы, так и даже переопределение наследованных стандартных методов для более гибкой работы с данными.

Описание этих методов есть в книге Guide to Programming with ZEND FRAMEWORK
Ух, прям настроение поднялось, когда понял, что ни одной из этих ошибок, вроде, не сделал))

И, как уже сказали выше, да, не хватает примеров вида «Неправильно:… Правильно: ...», чтобы люди не допускали таких ошибок и в дальнейшем.
> Использование вместо ACL, иерархии классов контроллеров
Зачем Вы поставили здесь запятую? Не подумайте, я не придираюсь. Просто не в первый раз уже встречаю такую конструкцию и мне интересно, какими соображениями люди руководствуются, ставя здесь запятую.
Большинство неправильно-правильно это условности. Пишу на фреймворках уже пятый год, и знаете что… Фреймворки меняются, концепции меняются, подходы меняются. Рекомендации о том, как правильно писать код, могут менятся до прямо противоположных в новых версиях фреймворка. Потому, прежде всего нужно думать головой, и придерживаться базовых принципов в виде KISS, DRY.
$model = new Model();
$some_data = $model->fetchAll(array('field1 = ?' => 1, 'field2 = ?' => 0));
$all_data = $model->fetchAll();
И где это должно быть если не в контроллере?
имелось в виду то, что SQL-запросы (sic! будто модели оборачивают только БД) должна слать модель, а не контроллер используя напрямую то, что модель имплементирует Zend_Db_Table*
Чем-то мне контекст ContextSwitch не понравился.
Возможно есть способ обойти, но
— Вроде бы он отображает данные в JSON только если это XmlHttpRequests, тоесть если я в файрбаге кликну строку запроса и скажу «открыть в новой вкладке», чтобы удобно отлаживать в новом окне — он не покажет мне там JSON, потому что признак XmlHttpRequests при этом теряется.
— Вроде бы даже если я сказал насильно переключать данные в json — то по прежнему можно делать запросы типа "/news/list/format/xml" или "/news/list?format=xml" что мне не нужно.

Я согласен, что постоянно отключать вручную Layout и ViewRenderer не правильно, и поэтому я бы лучше в данном случае написал бы какой-нибудь плагин, который бы проверял проперти контроллера, в котором было бы указано, какой Action в каком виде возвращать.
— Вроде бы даже если я сказал насильно переключать данные в json — то по прежнему можно делать запросы типа "/news/list/format/xml" или "/news/list?format=xml" что мне не нужно.


В этом и суть, если в метоже указать initContext указать формат по-умолчанию, то указывать формат в данных ajax хапроса не нужно. А для того, чтобы экшн отдавал в ином формате, напрмиер, указанном в данных ajax запроса, нужно добавить этот формат в метод addActionContext. И, да, второй параметр, формат, может быть массивом
> В этом и суть, если в метоже указать initContext указать формат
> по-умолчанию, то указывать формат в данных ajax хапроса не нужно.

Это работает для contextSwitch, но не для AjaxContext, который предложил автор поста.

Если открыть, к примеру, котроллер /ajax/list, в котором в init будет написало следующее:

$this->_helper->getHelper('contextSwitch')->addActionContext('list', 'json')->initContext('json');

То действительно будет показан json. Но если использовать AjaxContext:

$this->_helper->getHelper('AjaxContext')->addActionContext('list', 'json')->initContext('json');

то на странице /ajax/list вы увидите дефолтный view, а чтобы получить json нужно запрашивать эту страницу с хедером XmlHttpRequests

Ну и кроме того возможность открывать страницы с адресами /ajax/list/format/json мне тоже не нравится.
Ну и кроме того возможность открывать страницы с адресами /ajax/list/format/json мне тоже не нравится.


Ага, для этого я отключаю дефолтный роут

Zend_Controller_Front::getInstance()->getRouter()->removeDefaultRoutes();
Вся эта бадяга с отключениями лишних роутеров ни чем не лучше отключения лайаута и вьюРендерера, так что я по прежнему считаю, что лучше написать свой плагин, который сделает именно то, что нужно :)
ИМХО, основной посыл автора, перед тем, как писать на Zend Framework, неплохо бы его изучить. В начале изучения тоже делал многое «по-своему», пока не узнал, что оно уже сделано. Вы бы могли написать статью на тему «практическое использование ZF».
Спасибо, хорошая идея, возможно скоро реализую
Насчет модели все весьма и весьма спорно (во всяком случае у меня). Допустим, у меня есть некоторая структура в базе данных — пусть это будут группы студентов, студенты, тесты, привязки тестов к группам и результаты выполнения тестов студентами — 5 таблиц (то, что по-хорошему это должно быть два модуля — Студенты и Тесты, и Тестам должно быть начхать, в той же БД ли находится информация о студентах, или же это вообще написано на бумажке у бабы Нюры, а при запросе сервер делает ей звонок по телефону, синтезирует голос, и баба Нюра надиктовывает пароли в трубочку, мы пока скромно умолчим). Тесты также имеют еще одну «базу данных», а именно сами файлы с заданиями (вопросами и ответами). Как видите, структура относительно легкая, но нетривиальная. Теперь самое интересное. Из ваших слов я сделал вывод, что у меня есть два варианта:
1. Сделать в качестве модели ORM (т.е. наплодить некоторое число классов (не помню, вроде когда-то считал, и их выходило 16 (т.к. классы-записи могут смешиваться в различных вариантах + классы контейнеров))), сломать голову над нетривиальной логикой внутри них (мало того, что сочетания не самые простые (например, контейнер тестов студента («тесты студента группы», если быть точным), контейнер выполненных тестов студента («результаты тестов студента») и контейнер всех тестов студента с информацией о их выполнении («тесты студента группы + результаты тестов студента»)), так еще и разбор файлов тестов прибавляется в куда-нибудь), а потом вконец загнуться на оптимизации запросов (т.к. сервачок не самый новый и не самый сильный)
2. Сделать один (два, пять) класс, включающий в себя стопицот методов для получения нужной информации. Так я, в основном, и делал, но меня начинают смущать названия «getStudentTestsWithResultsAndMinMarkByStudentId» или «getPagedStudentTestMarksByStudentIdsAndTestIds».
Что посоветуете, кроме как генерировать SQL-запросы прямо в контроллере? Пробовал написать мета-модель, которая после небольшой настройки под конкретную таблицу базы данных с помощью DSL генерировала SQL-запрос и разбирала его результат, но столкнулся с тем, что эта мета-модель превращается в чистый SQL (ладно, умный SQL, знающий типы полей), но с лишним слоем DSL на PHP.
Zend_Db_Table не всегда удобно использовать. При определённом уровне сложности есть смысл задуматься о Data Mapper
Т.е. использовать все тот же ORM (сделать несколько классов), но вынести всю логику запросов в Mapper'ы? Остается вопрос именований методов для выборки данных из БД ($students->findByGroupIdOrderByName($groupId) или $group->getStudentsOrderByName()) и вопрос дублирования кода (кажется мне, что именно в моем случае от дублирования не избавиться (-_-) ).
Я не вижу у вас большого дублирования кода. Не совсем понятно ещё почему у вас получилось аж 16 классов. Достаточно одного класса для каждого объекта предметной области.
Прошу прощения, сейчас голова ну очень плохо варит. Суть была в том, что требовались самые разнообразные комбинации из пяти таблиц group, student, test, group_test, student_test при самых разнообразных условиях выборок + контейнеры элементов (думаю, от них как раз можно избавиться — пока не могу придумать необходимости получения данных именно от списка результатов выполнения тестов, а не от конкретного результата (если только подсчет средней оценки)). Дублирование возникает как минимум при валидации полей (можно, в принципе, намутить наследование и иже с ними, но опять боюсь сделать слишком сложную и запутанную систему).
В посте я рекомендую использовать для валидации Zend_Form+Zend_Validate думаю это поможет вам свести дублирование кода к минимуму
Еще можно использовать Zend_Filter_Input
Т.е. в любом случае нужно идти в направлении увеличения абстракции, а не наоборот
Видимо вы еще с настоящим ГК не сталкивались.
Раз уж речь зашла о Zend_Acl, позволю себе задать вопрос не совсем по теме. Бизнес-модель — следующая: у пользователя есть счёт, к счёту подключено одно из нескольких возможных приложений и несколько дополнительных услуг. В терминах ACL текущий пользователь — это роль, которая наследуется от статуса счёта (например, в работе или заблокирован), типа приложения (приложение А, B или C) и подключённых на счёт сервисов (сервис X, Y и т.д.).

С помощью ACL необходимо описать права доступа следующего характера:

1) Ресурс A доступен только для счёта со статусом в работе (с этим всё понятно);
2) Ресурс B доступен только для приложения B и счёта со статусом в работе (тут, видимо, нужно использовать наследование ролей);
3) Ресурс С доступен только для приложения A и только в случае, если на счёт подключён сервис X (это для меня ещё сложнее).

Ну и так далее. Общий вопрос — в том, как в ACL описать необходимость выполнения нескольких условий для того, чтобы доступ некоторой роли к некоторому ресурсу был разрешён?
Первые 2 случая довольно тривиальны, а вот для 3го необходимо видимо использовать Assert
Хорошо. А для второго случая как будет выглядеть правило?
На самом деле вы сами ответили на свой вопрос, здесь решение достигается за счет наследования, при чем видимо как ресурсов так и ролей. Каким именно образом построить иерархию не скажу, нужно вникать в предметную область
Спасибо. А насчет предметной области — чего именно здесь не хватает? По-моему, из описания всё вполне понятно. Могу описать третий пример более формализованным образом:

$status = true; // boolean
$application = 'a'; // one of a, b, c


Дело не в описании, просто это немного не по теме поста. Если хотите можем обсудить это немного позже в ЛС
$services = array('x', 'y', 'z');

if (true === $status && $application == 'b' && in_array('y', $services)) {
  echo 'access granted';
}
Очень интересно. А как 1 и 2 сделать без assert'ов?

Вот простейший «1) Ресурс A доступен только для счёта со статусом в работе (с этим всё понятно);»

Покажите, пожалуйста, где условие «со статусом в работе» будет храниться и кем выполняться?
Там дан ответ в контексте лимба и комментарий другого человека, что в Zend'е это же делается с помощью утверждений.

Так что мой вопрос всё ещё в силе: Как в зенде 1 и 2 реализовать без assertion'ов?
К сожалению, я уже давно не использую Zend Framework и не могу вам помочь. Когда использовал, в Zend ACL еще наследования ролей не было )
Но решение должно быть аналогичное — объявление интерфейса для участвующих объектов и поддержка этих интерфейсов в ACL.
Да, но поведение " Ресурс A доступен только для счёта со статусом в работе " через наследование и без ассершнов реализовать не получится (я так считаю, по крайней мере) :-)
В Limb, например, есть возможность использовать объекты в качестве ролей и ресурсов. Это очень удобно как в описанном вами случае когда объект должен менять роль в зависимости от своего состояния, так и в зависимости от переданного ему ресурса. Смотрите по ссылке: wiki.limb-project.com/2011.1/doku.php?id=limb3:ru:packages:acl
В Zend Framework можно делать то же самое. Есть интерфейсы для ролей и ресурсов ACL, которые можно реализовать в любом классе. И есть механизм утверждений, в которых можно описывать роли объектов относительно различных ресурсов (http://framework.zend.com/manual/en/zend.acl.advanced.html).
Каждый новый программист думает что предыдущий — козел.
Козел не козел, но иногда логика в коде предыдущего программиста отсутствует практически полностью. А разбираться в этом коде нужно…
А еще зачастую бывает такая логика, которая не соответствует предполагаемой. Например, нахоодишь модель Table_Users() Справедливо полагая, что она работает с таблицей, где храняться данные пользователей. А выясняется, что там практически весь код, который вызывается фронтендом и к чему обращается пользователь.
Иногда логика присутствует, но не очевидна, и с первого взгляда кажется ошибочной. Только через время понимаешь, почему именно так было сделано.
Каждый программист думает, что в его коде присутствует опупенная логика. А новый программист, который не видит эту логику, думает что предыдущий программист — козел :)
UFO just landed and posted this here
Да уж… Работаю над большим проектом, который писали различные люди в течение достаточно долгого времени (скажем так, версия используемой Kohana уже устарела на пару лет). И у меня складывается такое ощущение, что из всех возможностей фремворка используется максимум 10%, и то — самые базовые функции, такие как роутинг, слой работы с БД, кэширование. Большинство хэлперов и библиотек идут лесом (пишутся свои аналоги, причем несколько в разных местах, размазанных по контроллерам и т.д.). Блин, иногда даже вьюхи не используются!

Видимо в свое время не поставили человека, который бы больно-больно по рукам бил за корявый и нелогичный код…
Отсутствие context-switching когда это необходимо


У меня в этом случае перестает работать firephp.
Пришлось все делать вручную (отключать layout а для вывода использовать специальный шаблон, в которым данные конвертируются в json).
Статья хорошая, но весьма спорным оказался первый пункт.
Первое, что сразу бросилось в глаза — выполнение запросов через объект модели прямо в коде контроллера, что напрочь перечеркивает все преимущества MVC…… Такое смешивание логики было в каждом файле контроллера, что очень мешало чтению кода и исправлению ошибок, постоянно возникали какие-то не очевидные зависимости, перезаписывались важные данные. Соответственно так делать не в коем случае нельзя, даже если вы разрабатываете небольшой проект.

Это утверждение мне кажется несколько резким и в некоторых случаях даже голословным. Зачем плодить в модели множество методов, используемых ровно один раз ровно в одном контроллере? А конструктор запросов с помощью Zend_Db_Table_Select и Zend_Db_Select для чего по вашему был создан? Уж не для того ли, чтобы в контроллере, в зависимости от параметров, динамически сформировать нужный запрос? И не надо отделываться общими фразами вроде «перезаписывались важные данные», надо приводить конкретные примеры, именно они суть этой статьи…
Sign up to leave a comment.

Articles