Комментарии 77
Круто. Пойду внедрять.
Посоветуйте хорошую книгу по ZF?
Посоветуйте хорошую книгу по ZF?
+1
Где бы собрать воедино все эти полезные финты.
0
можно купить книгу по шаблонам GoF
0
это не какой-то специфический паттерн ведь, м?
0
нет, но само решение можно было свести к «Объекту запроса», которое является более гибким, чем приведенное
0
Угу, я уже сходил martinfowler.com/eaaCatalog/queryObject.html
мерси
мерси
0
Вот очень подробно и интересно www.rsdn.ru/article/patterns/gotopatterns.xml#EULAC
0
С одной стороны в Django всё тоже самое выглядит более красиво, с другой именно на Zend-е более ООП-но смотрится чтоль )))
Спасибо за пост.
Спасибо за пост.
0
Зачем плодить столько кода?
Сделали бы один продуманный метод, независящий от данных.
Пример из своего кода:
$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') — все, минус один лузер.
-4
Для простых запросов это хорошо. А для чего-то посложнее уже не очень.
0
Приведите пример.
-2
Например, выбрать только посты, помеченные тегами «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();
т.е. смысл как раз в куче разных методов, зависящих от данных и скрывающих сложную логику.
0
Чем это хуже к примеру
with ('Tags: PHP,Yii')
(псевдокод)
with ('Tags: PHP,Yii')
(псевдокод)
0
Плюс в том, что можно в with подставить другую модель, минус — немного более сложный API. Если попытаться сделать сверхуниверсальные методы, тяжко будет разбираться в том, какие к ним нужны параметры.
0
потому что тогда мне придется эту строку составлять. типа
->with('Tags: '. join(',', $tagsList));
вместо
->withTags($tagsList)
мне редко приходится в исходном коде писать названия, например, тегов
->with('Tags: '. join(',', $tagsList));
вместо
->withTags($tagsList)
мне редко приходится в исходном коде писать названия, например, тегов
-1
Фаулер. Объект запросов
0
Можно использовать Zend_Db_Table_Select и не заморачиваться,
будет что то типа $select->where('sex = ?', 'female')->order('id DESC') и т.д.
Такой код больше подвержен ошибкам, потому что его нельзя атомарно протестировать. Так же он поощряет писать запросы прямо в контроллерах, что в итоге ведет к каше и при рефакторинге базы данных Вам придется переписывать все места, а не один select.
будет что то типа $select->where('sex = ?', 'female')->order('id DESC') и т.д.
Такой код больше подвержен ошибкам, потому что его нельзя атомарно протестировать. Так же он поощряет писать запросы прямо в контроллерах, что в итоге ведет к каше и при рефакторинге базы данных Вам придется переписывать все места, а не один select.
+2
Неудобно.
Например, если вы собираете условия «по пути», то придется конкатенировать какую-то переменную ($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);
-1
А если без переопределения селекта? Код не проверял, просто для примера.
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.
0
Напрашивается вариант с написанием обработчика __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 он уже есть и корректно скармливать ему если это не столбец
0
Да такой метод habrahabr.ru/blogs/zend_framework/86353/ можно комбинировать с тем что описано в статье, но он никак его не заменяет. Тут habrahabr.ru/blogs/zend_framework/98877/#comment_3048351 я написал почему. Добавьте сюда еще автокомплит.
+1
А зачем под каждое условие писать свой метод?
Так ведь универсальнее?:
Так ведь универсальнее?:
public function call($method, $params)
{
return $this->where($method." = ".$params);
}
* This source code was highlighted with Source Code Highlighter.
+1
Потому что для каждого условия могут быть свои операторы. Например для возраста может быть диапазон, для категории скажем IN (...)
Да и код более очевидный.
Да и код более очевидный.
-1
Ну если в таблице пара десятков столбцов — тогда, имхо, для многих можно использовать вызов через __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.
0
Мне кажется, что дальше будет только сложнее.
Как быть с ">", "<", «IS NULL» и др.?
Как быть с ">", "<", «IS NULL» и др.?
+1
Я чуть ниже написал
habrahabr.ru/blogs/zend_framework/98877/#comment_3048451
_call сделать для основных условий выборки, а для более сложных уже свои методы.
habrahabr.ru/blogs/zend_framework/98877/#comment_3048451
_call сделать для основных условий выборки, а для более сложных уже свои методы.
0
А вот это другой разговор). У меня реализован метод selectBy($field, $value), который делает то что вы говорите. И это действительно удобно.
0
Примерно к этому я и клоню
Вот пример реализации:
code.google.com/p/zfcore/wiki/Core_Model тут в разделе «Магический поиск»
Вот пример реализации:
code.google.com/p/zfcore/wiki/Core_Model тут в разделе «Магический поиск»
0
Это уже больше на функциональный код похоже. Но вообще да, такой подход используется скажем в magento для фильтрации коллекций. Метод addFieldToFilter(), в который подаются поле и возможные значения.
0
А еще Вы забыли учесть IS NULL, >, <, BETWEEN и много чего другого).
0
Я написал не готовый метод, а лишь дал понять, как можно было бы сделать.
Ведь не лучше написать 1 универсальный метод, в котором учесть многие условия выборки и им пользоваться?
А для более серьезных условий выборки уже написать отдельные методы.
Ведь не лучше написать 1 универсальный метод, в котором учесть многие условия выборки и им пользоваться?
А для более серьезных условий выборки уже написать отдельные методы.
0
Не лучше. Сложнее в тестировании и сопровождении, запутанный код, завязка на конкретную базу, невозможность учесть всех вариантов, тот кто будет пользоваться вообще не поймет что может этот код, отсутствие автокомплита.
0
В принципе, может Вы и правы. Хотя я видел подобные решения и, надо признать, лично мне такой вариант больше нравится.
Только не совсем понял, о какой завязке на конкретную базу идет речь. Ибо любой проект так или иначе завязывается на конкретную базу. Ваш пример тоже завязан.
Только не совсем понял, о какой завязке на конкретную базу идет речь. Ибо любой проект так или иначе завязывается на конкретную базу. Ваш пример тоже завязан.
+1
100 процентной абстракции от базы никогда не получится, но в моем случае проще разобраться и поменять что то. Однажды мне пришлось переезжать с mysql на postgresql, когда уже было создано больше сотни таблиц и моделей. Благодаря использованию Zend_Db_Table_Select, многое не пришлось переписывать, хотя бы из-за автоматического квотирования селектом под каждую базу.
0
Это же очевидное решение. Добавление фильтров к объекту по мере необходимости.
+1
Кстати метод
public function fetchRowIfExists($message = 'Данной страницы нет на сайте')
ужасен. Ужасен по нескольким причинам:
1) метод в зависимости от наличия строки возвращает разные типы данных (массив или строку ошибки)
2) логика обработки отсуствия строки в БД перенесена из контроллера в модель
По хорошему достаточно метода fetchRow(), который возвращает либо запись из БД, либо пустой массив (объект?), которй уже в контроллере проверяется на пустоту и выбрасывается соответствующее исключение, которое обработается центральным обработчиком исключений и покажет 404, либо вручную перенаправит на 404
public function fetchRowIfExists($message = 'Данной страницы нет на сайте')
ужасен. Ужасен по нескольким причинам:
1) метод в зависимости от наличия строки возвращает разные типы данных (массив или строку ошибки)
2) логика обработки отсуствия строки в БД перенесена из контроллера в модель
По хорошему достаточно метода fetchRow(), который возвращает либо запись из БД, либо пустой массив (объект?), которй уже в контроллере проверяется на пустоту и выбрасывается соответствующее исключение, которое обработается центральным обработчиком исключений и покажет 404, либо вручную перенаправит на 404
-1
Он не возвращает строку ошибки, а выкидывает исключение. Очень, кстати, удобный метод, который имеет аналоги во всех фреймворках (правда реализация может отличаться). После написания в тысячный раз
$row = где то взяли
if (!$row) throw exception <класс для 404 ошибки> // а еще все забывают это делать
мозг начинает искать способы упростить задачу.
$row = где то взяли
if (!$row) throw exception <класс для 404 ошибки> // а еще все забывают это делать
мозг начинает искать способы упростить задачу.
0
выбрасывать исключения каждый раз, когда нет записи в БД — не кошерно. Иногда нужен действительно пустой массив, если нет данных.
0
Зачем каждый раз? Этот метод используется только в тех случаях когда нужно показать страницу с ошибкой 404. Во всех остальных случаях используется простой fetchRow.
0
да просто потом прийдется объяснять, почему метод называется именно так и почему в одних случаях случается магия (отображается 404), а в других — обычный поток выполнения
0
В общем то не вижу никакой магии. Этот метод давно и успешно применяется не вызывая ни у кого никаких противоречий. Если Вам нужно что то более очевидное то можно его переназвать так: fetchRowAndThrowExceptionIfNotExists(), ну или по короче).
0
В Django, аналогичная функция называется, если не ошибаюсь, get_object_or_404 — по-моему, название говорит само за себя. Есть также аналогичная функция для «набора записей» get_list_or_404
0
_or_404 уже лучше, но опять же смешиваеются слои приложения. Но это дело вкуса, конечно…
0
Не смешиваются, это не метод модели, а глобальная функция. Модель и параметры запроса передаются в неё в качестве параметров, из модели обычным путём делается выборка и, если выборка пуста, генерится исключение, к модели никак не относящееся — контроллер вызвал глобальную функцию, она сгенерировала исключение.
0
При этом нужно учитывать, что в python глобальная функция это не тоже самое что в php из-за пространств имен. В zf это можно оформить в виде хелпера. В разных проектах я использовал оба подхода.
0
Это да (по крайней мере пока популярные фреймворки не переписаны под 5.3 с его неймспейсами и прочими вкусностями), но главное, что использование подобных хелперов (а можно описать методом контроллера) не нарушает концепций MVC — модель ничего не знает о 404, а контроллеру не надо заботиться о пустом объекте или списке.
0
у меня контроллеры выглядят приблизительно так(суть).
$topic = $this->getTopic(); // Topic or Null if ($topic) { return $this->render($topic); } else { return $this->http(404); }
0
Вот, а представьте что у вас таких мест пара тройка десятков. Сделайте хелпер который можно использовать так
0
тьфу, случайно нажал.
$topic = $this->getTopic(); // Topic or Null
return $this->renderOrHttp($topic, 404)
А внутри уже ваш иф.
$topic = $this->getTopic(); // Topic or Null
return $this->renderOrHttp($topic, 404)
А внутри уже ваш иф.
-1
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
0
Если Вы так хотите этого нововведения, то почему это запостили на хабр, а не в maling list Zend. Если в таблице уже так сильно нужно что бы был доступ к селекту, попросите и в следующем релизе это добавят, а если нет — то Вам объяснят почему это делать нельзя и чем плох Ваш подход.
0
Из всей статьи вы заметили только то что нужно переопределить метод select? Это полезная плюшка сама по себе на которую уже есть давно есть тикет в трекере zf. Только статья о другом…
0
Тикет тикетом, а предложить патч. И что бы эта функция была по умолчанию в Zend_Db_Table?
Я просто похожий механизм использую в своём классе. И не пользуюсь Zend_Db_Table.
Я просто похожий механизм использую в своём классе. И не пользуюсь Zend_Db_Table.
0
Я давно туда не заглядывал, но насколько помню там все было как надо и патч и объяснение. Сейчас в любом случае этого никто делать не будет т.к. в zf1 только багфиксы, а все силы на zf2. Во втором zf возможно это и не понадобится.
Кстати, не обязательно переопределять стандартный метод, можно написать свой и дергать через него. В любом случае это мелочь и каждый сам сможет это реализовать как ему удобнее.
Кстати, не обязательно переопределять стандартный метод, можно написать свой и дергать через него. В любом случае это мелочь и каждый сам сможет это реализовать как ему удобнее.
0
Если в Rails мы можем писать так
User.active
то аналогичный код в PHP/Zend будет выглядеть в 2 строки
$u = User.new
$u->select()->active();
User.active
то аналогичный код в PHP/Zend будет выглядеть в 2 строки
$u = User.new
$u->select()->active();
0
Кто-то ещё использует Zend_Db_Table_*? )))
+2
Я сделал так:
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
0
Почему-то кусок кода обрезало:
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')
Но ваш метод тоже интересен. Нужно будет к нему присмотреться.
0
Возможно, вам будет интересна моя статья (Zend_Db_Table_Select Dynamic Finder) — habrahabr.ru/blogs/zend_framework/86353/. Ну а что будет в ZF2 — поживем-посмотрим.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Named scope для Zend Framework