Comments 77
Круто. Пойду внедрять.
Посоветуйте хорошую книгу по ZF?
Посоветуйте хорошую книгу по ZF?
Где бы собрать воедино все эти полезные финты.
можно купить книгу по шаблонам GoF
это не какой-то специфический паттерн ведь, м?
нет, но само решение можно было свести к «Объекту запроса», которое является более гибким, чем приведенное
Угу, я уже сходил martinfowler.com/eaaCatalog/queryObject.html
мерси
мерси
Вот очень подробно и интересно www.rsdn.ru/article/patterns/gotopatterns.xml#EULAC
С одной стороны в Django всё тоже самое выглядит более красиво, с другой именно на Zend-е более ООП-но смотрится чтоль )))
Спасибо за пост.
Спасибо за пост.
Зачем плодить столько кода?
Сделали бы один продуманный метод, независящий от данных.
Пример из своего кода:
$Object->Query('Sex=Female')->Intersect('Avatar=True');
Или так $Object->Query('Sex=Female;Avatar=True');
$Object->Query('Surname=Иванов')->Merge('Surname=Петров')
Плюс, удобно абстрагировать выборки вроде Top, Last, Random.
$Object->Query('All')->Substract('Random') — все, минус один лузер.
Сделали бы один продуманный метод, независящий от данных.
Пример из своего кода:
$Object->Query('Sex=Female')->Intersect('Avatar=True');
Или так $Object->Query('Sex=Female;Avatar=True');
$Object->Query('Surname=Иванов')->Merge('Surname=Петров')
Плюс, удобно абстрагировать выборки вроде Top, Last, Random.
$Object->Query('All')->Substract('Random') — все, минус один лузер.
Для простых запросов это хорошо. А для чего-то посложнее уже не очень.
Приведите пример.
Например, выбрать только посты, помеченные тегами «PHP» и «Zend Framework» с кодом, приведённом в посте:
$post_table->select()->withTags('PHP', 'Zend Framework'))->fetchAll();
В Yii:
Post::model()->withTags('PHP', 'Yii')->findAll();
т.е. смысл как раз в куче разных методов, зависящих от данных и скрывающих сложную логику.
$post_table->select()->withTags('PHP', 'Zend Framework'))->fetchAll();
В Yii:
Post::model()->withTags('PHP', 'Yii')->findAll();
т.е. смысл как раз в куче разных методов, зависящих от данных и скрывающих сложную логику.
Чем это хуже к примеру
with ('Tags: PHP,Yii')
(псевдокод)
with ('Tags: PHP,Yii')
(псевдокод)
Плюс в том, что можно в with подставить другую модель, минус — немного более сложный API. Если попытаться сделать сверхуниверсальные методы, тяжко будет разбираться в том, какие к ним нужны параметры.
потому что тогда мне придется эту строку составлять. типа
->with('Tags: '. join(',', $tagsList));
вместо
->withTags($tagsList)
мне редко приходится в исходном коде писать названия, например, тегов
->with('Tags: '. join(',', $tagsList));
вместо
->withTags($tagsList)
мне редко приходится в исходном коде писать названия, например, тегов
Фаулер. Объект запросов
Можно использовать Zend_Db_Table_Select и не заморачиваться,
будет что то типа $select->where('sex = ?', 'female')->order('id DESC') и т.д.
Такой код больше подвержен ошибкам, потому что его нельзя атомарно протестировать. Так же он поощряет писать запросы прямо в контроллерах, что в итоге ведет к каше и при рефакторинге базы данных Вам придется переписывать все места, а не один select.
будет что то типа $select->where('sex = ?', 'female')->order('id DESC') и т.д.
Такой код больше подвержен ошибкам, потому что его нельзя атомарно протестировать. Так же он поощряет писать запросы прямо в контроллерах, что в итоге ведет к каше и при рефакторинге базы данных Вам придется переписывать все места, а не один select.
Неудобно.
Например, если вы собираете условия «по пути», то придется конкатенировать какую-то переменную ($conditions). А если потом внезапно выяснится, что какое-то условие уже не нужно (опять же, во время прогона скрипта)? Намного удобнее использовать различные методы, вроде того же hasAvatar($hasAvatar = true).
А если нужно передать объект Select какому-то методу, чтобы он его по желанию изменил, тогда ссылку на строковую переменную придется передавать, что вообще бред, как я считаю.
Наглядно:
$conditions .= 'Avatar: true'; /* Ан нет, аватар не нужен */ $conditions = str_replace('Avatar: true', 'Avatar: false'); // Ппц
$select->hasAvatar()->hasAvatar(false);
Например, если вы собираете условия «по пути», то придется конкатенировать какую-то переменную ($conditions). А если потом внезапно выяснится, что какое-то условие уже не нужно (опять же, во время прогона скрипта)? Намного удобнее использовать различные методы, вроде того же hasAvatar($hasAvatar = true).
А если нужно передать объект Select какому-то методу, чтобы он его по желанию изменил, тогда ссылку на строковую переменную придется передавать, что вообще бред, как я считаю.
Наглядно:
$conditions .= 'Avatar: true'; /* Ан нет, аватар не нужен */ $conditions = str_replace('Avatar: true', 'Avatar: false'); // Ппц
$select->hasAvatar()->hasAvatar(false);
А если без переопределения селекта? Код не проверял, просто для примера.
Users extends Zend_Db_Table
{
const STATUS_OFF = 0;
const STATUS_ON = 1;
protected $select;
public function __construct ()
{
$this->select = $this->select();
}
public function getSelect ()
{
if (null === $this->select) {
throw new Zend_Exception('Select object is not set.');
}
return $this->select;
}
public funtion setSelect (Zend_Db_Table_Select $select)
{
$this->select = $select;
}
public funtion status ($status = self::STATUS_ON)
{
$this->select->where('status = ?', $status);
return $this->select;
}
public funtion hasAvatar ()
{
$this->select->where('avatar IS NOT NULL');
return $this->select;
}
}
$users = new Users();
$rows = $users->fetchAll($users->status()->hasAvatar());
* This source code was highlighted with Source Code Highlighter.
Напрашивается вариант с написанием обработчика __call для такого типа функций
Правда нужно учесть что в Zend_Db_Select он уже есть и корректно скармливать ему если это не столбец
public function status($status)
{
return $this->where('status = ?', $status);
}
public function sex($sex)
{
return $this->where('sex = ?', $sex);
}
* This source code was highlighted with Source Code Highlighter.
Правда нужно учесть что в Zend_Db_Select он уже есть и корректно скармливать ему если это не столбец
Да такой метод habrahabr.ru/blogs/zend_framework/86353/ можно комбинировать с тем что описано в статье, но он никак его не заменяет. Тут habrahabr.ru/blogs/zend_framework/98877/#comment_3048351 я написал почему. Добавьте сюда еще автокомплит.
А зачем под каждое условие писать свой метод?
Так ведь универсальнее?:
Так ведь универсальнее?:
public function call($method, $params)
{
return $this->where($method." = ".$params);
}
* This source code was highlighted with Source Code Highlighter.
Потому что для каждого условия могут быть свои операторы. Например для возраста может быть диапазон, для категории скажем IN (...)
Да и код более очевидный.
Да и код более очевидный.
Ну если в таблице пара десятков столбцов — тогда, имхо, для многих можно использовать вызов через __call
А для некоторых условий выборки уже сделать свои методы.
_call можно расширить, например так:
А для некоторых условий выборки уже сделать свои методы.
_call можно расширить, например так:
public function call($method, $params)
{
if(is_array($params) {
return $this->where($method." IN (".implode(',',$params).")");
} else {
return $this->where($method." = ".$params);
}
}
* This source code was highlighted with Source Code Highlighter.
Мне кажется, что дальше будет только сложнее.
Как быть с ">", "<", «IS NULL» и др.?
Как быть с ">", "<", «IS NULL» и др.?
Я чуть ниже написал
habrahabr.ru/blogs/zend_framework/98877/#comment_3048451
_call сделать для основных условий выборки, а для более сложных уже свои методы.
habrahabr.ru/blogs/zend_framework/98877/#comment_3048451
_call сделать для основных условий выборки, а для более сложных уже свои методы.
А вот это другой разговор). У меня реализован метод selectBy($field, $value), который делает то что вы говорите. И это действительно удобно.
Примерно к этому я и клоню
Вот пример реализации:
code.google.com/p/zfcore/wiki/Core_Model тут в разделе «Магический поиск»
Вот пример реализации:
code.google.com/p/zfcore/wiki/Core_Model тут в разделе «Магический поиск»
Это уже больше на функциональный код похоже. Но вообще да, такой подход используется скажем в magento для фильтрации коллекций. Метод addFieldToFilter(), в который подаются поле и возможные значения.
А еще Вы забыли учесть IS NULL, >, <, BETWEEN и много чего другого).
Я написал не готовый метод, а лишь дал понять, как можно было бы сделать.
Ведь не лучше написать 1 универсальный метод, в котором учесть многие условия выборки и им пользоваться?
А для более серьезных условий выборки уже написать отдельные методы.
Ведь не лучше написать 1 универсальный метод, в котором учесть многие условия выборки и им пользоваться?
А для более серьезных условий выборки уже написать отдельные методы.
Не лучше. Сложнее в тестировании и сопровождении, запутанный код, завязка на конкретную базу, невозможность учесть всех вариантов, тот кто будет пользоваться вообще не поймет что может этот код, отсутствие автокомплита.
В принципе, может Вы и правы. Хотя я видел подобные решения и, надо признать, лично мне такой вариант больше нравится.
Только не совсем понял, о какой завязке на конкретную базу идет речь. Ибо любой проект так или иначе завязывается на конкретную базу. Ваш пример тоже завязан.
Только не совсем понял, о какой завязке на конкретную базу идет речь. Ибо любой проект так или иначе завязывается на конкретную базу. Ваш пример тоже завязан.
100 процентной абстракции от базы никогда не получится, но в моем случае проще разобраться и поменять что то. Однажды мне пришлось переезжать с mysql на postgresql, когда уже было создано больше сотни таблиц и моделей. Благодаря использованию Zend_Db_Table_Select, многое не пришлось переписывать, хотя бы из-за автоматического квотирования селектом под каждую базу.
Это же очевидное решение. Добавление фильтров к объекту по мере необходимости.
Кстати метод
public function fetchRowIfExists($message = 'Данной страницы нет на сайте')
ужасен. Ужасен по нескольким причинам:
1) метод в зависимости от наличия строки возвращает разные типы данных (массив или строку ошибки)
2) логика обработки отсуствия строки в БД перенесена из контроллера в модель
По хорошему достаточно метода fetchRow(), который возвращает либо запись из БД, либо пустой массив (объект?), которй уже в контроллере проверяется на пустоту и выбрасывается соответствующее исключение, которое обработается центральным обработчиком исключений и покажет 404, либо вручную перенаправит на 404
public function fetchRowIfExists($message = 'Данной страницы нет на сайте')
ужасен. Ужасен по нескольким причинам:
1) метод в зависимости от наличия строки возвращает разные типы данных (массив или строку ошибки)
2) логика обработки отсуствия строки в БД перенесена из контроллера в модель
По хорошему достаточно метода fetchRow(), который возвращает либо запись из БД, либо пустой массив (объект?), которй уже в контроллере проверяется на пустоту и выбрасывается соответствующее исключение, которое обработается центральным обработчиком исключений и покажет 404, либо вручную перенаправит на 404
Он не возвращает строку ошибки, а выкидывает исключение. Очень, кстати, удобный метод, который имеет аналоги во всех фреймворках (правда реализация может отличаться). После написания в тысячный раз
$row = где то взяли
if (!$row) throw exception <класс для 404 ошибки> // а еще все забывают это делать
мозг начинает искать способы упростить задачу.
$row = где то взяли
if (!$row) throw exception <класс для 404 ошибки> // а еще все забывают это делать
мозг начинает искать способы упростить задачу.
выбрасывать исключения каждый раз, когда нет записи в БД — не кошерно. Иногда нужен действительно пустой массив, если нет данных.
Зачем каждый раз? Этот метод используется только в тех случаях когда нужно показать страницу с ошибкой 404. Во всех остальных случаях используется простой fetchRow.
да просто потом прийдется объяснять, почему метод называется именно так и почему в одних случаях случается магия (отображается 404), а в других — обычный поток выполнения
В общем то не вижу никакой магии. Этот метод давно и успешно применяется не вызывая ни у кого никаких противоречий. Если Вам нужно что то более очевидное то можно его переназвать так: fetchRowAndThrowExceptionIfNotExists(), ну или по короче).
В Django, аналогичная функция называется, если не ошибаюсь, get_object_or_404 — по-моему, название говорит само за себя. Есть также аналогичная функция для «набора записей» get_list_or_404
_or_404 уже лучше, но опять же смешиваеются слои приложения. Но это дело вкуса, конечно…
Не смешиваются, это не метод модели, а глобальная функция. Модель и параметры запроса передаются в неё в качестве параметров, из модели обычным путём делается выборка и, если выборка пуста, генерится исключение, к модели никак не относящееся — контроллер вызвал глобальную функцию, она сгенерировала исключение.
При этом нужно учитывать, что в python глобальная функция это не тоже самое что в php из-за пространств имен. В zf это можно оформить в виде хелпера. В разных проектах я использовал оба подхода.
Это да (по крайней мере пока популярные фреймворки не переписаны под 5.3 с его неймспейсами и прочими вкусностями), но главное, что использование подобных хелперов (а можно описать методом контроллера) не нарушает концепций MVC — модель ничего не знает о 404, а контроллеру не надо заботиться о пустом объекте или списке.
у меня контроллеры выглядят приблизительно так(суть).
$topic = $this->getTopic(); // Topic or Null if ($topic) { return $this->render($topic); } else { return $this->http(404); }
Вот, а представьте что у вас таких мест пара тройка десятков. Сделайте хелпер который можно использовать так
тьфу, случайно нажал.
$topic = $this->getTopic(); // Topic or Null
return $this->renderOrHttp($topic, 404)
А внутри уже ваш иф.
$topic = $this->getTopic(); // Topic or Null
return $this->renderOrHttp($topic, 404)
А внутри уже ваш иф.
function port ($argv) { if (count($argv) != 1 || !$this->isPortCode($argv[0])) { return $this->http(404); } $port = $this->getPort($argv[0]); if (!$port) { return $this->http(404); } return $this->renderPort($port); } function portsList ($argv) { if ($argv) { return $this->http(404); } return $this->renderPortsList(); }
Ну это грубо. Каждый раз могут быть другие причины появления 404
Если Вы так хотите этого нововведения, то почему это запостили на хабр, а не в maling list Zend. Если в таблице уже так сильно нужно что бы был доступ к селекту, попросите и в следующем релизе это добавят, а если нет — то Вам объяснят почему это делать нельзя и чем плох Ваш подход.
Из всей статьи вы заметили только то что нужно переопределить метод select? Это полезная плюшка сама по себе на которую уже есть давно есть тикет в трекере zf. Только статья о другом…
Тикет тикетом, а предложить патч. И что бы эта функция была по умолчанию в Zend_Db_Table?
Я просто похожий механизм использую в своём классе. И не пользуюсь Zend_Db_Table.
Я просто похожий механизм использую в своём классе. И не пользуюсь Zend_Db_Table.
Я давно туда не заглядывал, но насколько помню там все было как надо и патч и объяснение. Сейчас в любом случае этого никто делать не будет т.к. в zf1 только багфиксы, а все силы на zf2. Во втором zf возможно это и не понадобится.
Кстати, не обязательно переопределять стандартный метод, можно написать свой и дергать через него. В любом случае это мелочь и каждый сам сможет это реализовать как ему удобнее.
Кстати, не обязательно переопределять стандартный метод, можно написать свой и дергать через него. В любом случае это мелочь и каждый сам сможет это реализовать как ему удобнее.
Если в Rails мы можем писать так
User.active
то аналогичный код в PHP/Zend будет выглядеть в 2 строки
$u = User.new
$u->select()->active();
User.active
то аналогичный код в PHP/Zend будет выглядеть в 2 строки
$u = User.new
$u->select()->active();
Кто-то ещё использует Zend_Db_Table_*? )))
Я сделал так:
public function __call($name, $arguments) {
$actionName = '';
foreach (array('fetchRowBy', 'fetchAllBy', 'deleteBy', 'countBy') as $_a) {
if (strpos($name, $_a) === 0) {
$actionName = $_a;
break;
}
}
if (empty($actionName)) throw new Model_DbTable_Exception(«Undefined method $name»);
$fetchField = substr($name, strlen($actionName));
$arguments = $this->_splitCallArguments($fetchField, $arguments);
return call_user_func_array(array($this, $actionName), $arguments);
}
protected function _splitCallArguments($fetchField, $arguments) {
$fetchFields = explode('And', $fetchField);
if (count($fetchFields) == 1) {
array_unshift($arguments, strtolower(Zend_Filter::filterStatic($fetchField, 'Word_CamelCaseToUnderscore')));
}
else {
$args = array_values($arguments);
$arguments = array();
for ($i=0;$i
public function __call($name, $arguments) {
$actionName = '';
foreach (array('fetchRowBy', 'fetchAllBy', 'deleteBy', 'countBy') as $_a) {
if (strpos($name, $_a) === 0) {
$actionName = $_a;
break;
}
}
if (empty($actionName)) throw new Model_DbTable_Exception(«Undefined method $name»);
$fetchField = substr($name, strlen($actionName));
$arguments = $this->_splitCallArguments($fetchField, $arguments);
return call_user_func_array(array($this, $actionName), $arguments);
}
protected function _splitCallArguments($fetchField, $arguments) {
$fetchFields = explode('And', $fetchField);
if (count($fetchFields) == 1) {
array_unshift($arguments, strtolower(Zend_Filter::filterStatic($fetchField, 'Word_CamelCaseToUnderscore')));
}
else {
$args = array_values($arguments);
$arguments = array();
for ($i=0;$i
Почему-то кусок кода обрезало:
protected function _splitCallArguments($fetchField, $arguments) {
$fetchFields = explode('And', $fetchField);
if (count($fetchFields) == 1) {
array_unshift($arguments, strtolower(Zend_Filter::filterStatic($fetchField, 'Word_CamelCaseToUnderscore')));
}
else {
$args = array_values($arguments);
$arguments = array();
for ($i=0;$i < count($fetchFields);$i++) {
if (array_key_exists($i, $args)) array_push($arguments, array(
strtolower(Zend_Filter::filterStatic($fetchFields[$i], 'Word_CamelCaseToUnderscore')),
$args[$i]
));
}
$arguments = array($arguments);
}
return $arguments;
}
Далее возможны вызовы типа: fetchAllByGenderAndStatus('female', 'active')
Но ваш метод тоже интересен. Нужно будет к нему присмотреться.
protected function _splitCallArguments($fetchField, $arguments) {
$fetchFields = explode('And', $fetchField);
if (count($fetchFields) == 1) {
array_unshift($arguments, strtolower(Zend_Filter::filterStatic($fetchField, 'Word_CamelCaseToUnderscore')));
}
else {
$args = array_values($arguments);
$arguments = array();
for ($i=0;$i < count($fetchFields);$i++) {
if (array_key_exists($i, $args)) array_push($arguments, array(
strtolower(Zend_Filter::filterStatic($fetchFields[$i], 'Word_CamelCaseToUnderscore')),
$args[$i]
));
}
$arguments = array($arguments);
}
return $arguments;
}
Далее возможны вызовы типа: fetchAllByGenderAndStatus('female', 'active')
Но ваш метод тоже интересен. Нужно будет к нему присмотреться.
Возможно, вам будет интересна моя статья (Zend_Db_Table_Select Dynamic Finder) — habrahabr.ru/blogs/zend_framework/86353/. Ну а что будет в ZF2 — поживем-посмотрим.
Sign up to leave a comment.
Named scope для Zend Framework