Aug 25

В Zend Framework всё замечательно! …пока не начинаешь его использовать на полную катушку…

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

В Zend_Db везде можно использовать array в качестве where, только нигде не сказано, как этот array должен выглядеть, а аналогий не прослеживается…

$productsTable = new Products();

Обычная вставка нового продукта:

$productsTable->insert(array(
    'name'  => 'Fucked Product!',
    'price' => 999.99,
  ));

Всё OK.

Поиск продукта по аналогии со вставкой:

$productsTable->fetchRow(array(
    'name'  => 'Fucked Product!',
    'price' => 999.99,
  ));

Выдаст SQL типа такого:

SELECT products.* FROM products WHERE (name) AND (price)

Что приведет к выдаче первого попавшегося продукта (что, естественно, нам не подходит).

Правильный вариант поиска продукта:

$productsTable->fetchRow(array(
    'name = ?'  => 'Fucked Product!',
    'price = ?' => 999.99,
  ));

Удаление продукта по аналогии с поиском:

$productsTable->delete(array(
    'product_id = ?' => 1000,
  ));

Выдаваемый SQL:

DELETE FROM products WHERE 1000

Приведет к удалению всех продуктов (sic!).

Причем самое интересное, что в случае со строкой - у вас будет ошибка, а вот в случае с числом, которое escape’ить не нужно - будут проблемы. Я с ними на днях столкнулся, и… получил пустую таблицу, в которой были важные данные. :( Благо, бэкап был.

Удаление продукта по аналогии со вставкой:

$productsTable->delete(array(
    'product_id' => 1000,
  ));

Полностью аналогично предыдущему случаю.

Правильный вариант удаления продукта:

$productsTable->delete(
    $productsTable->getAdapter()->quoteInto('product_id = ?', 1000)
  );

Вот вам и офигенный ZF. Если использовать его не постоянно, а изредка для отдельных задач - натыкаешься на подобные грабли.

written by FX Poster \\ tags: ,

Jul 20

На работе пришлось столкнуться с очень не нравившейся мне ORM’кой собственного производства. Стал делать свою (ну не дурак ли, а? :)), наваял за 3 дня простенькую ORM, отображающую структуру таблиц на обьекты, не контроллируя типов. Итог получился примерно такой:

  • класс базы данных (относледовался от mysqli, pdo использовать было нельзя)
  • класс таблицы, хранящий в себе бд, и отвечающий за CRUD записей
  • класс записи, перенаправляющий методы CUD классу таблицы

Примерное использование:

$table = new ArticleTable();
$record = $table->fetchOneWhere("slug = 'hello'"); // получаем существующую запись
$record->name = 'Fucking Article!';
$record->save(); // вызывает insert/update в зависимости от того, новая ли это запись
$record = $table->create(); // создаем новую запись
$record->name = 'Fucking Article2!';
$record->slug = 'fucking_article';
// ...
$record->save();

И что-то мне это очень сильно напомнило, а именно - Zend_Db: Zend_Db_Table / Zend_Db_Table_Row. Недолго думая - выкинул нафиг свою систему и залил в проект кусок Zend Framework’а (если нужно - потом скажу, какие именно файлы нужны для полноценной работы всего компонента Zend_Db), а также решил почитать, что сейчас вообще есть в этой Zend_Db, а есть, как оказалось - довольно много:

  • Хорошая абстракция работы с бд
  • Классы записи/таблицы
  • Поддержка fetch’инга связанных обьектов
  • Поддержка many-to-many связей (этого даже в Propel нет)

На самом деле - еще есть вещи, коотрые бы можно было добавить, чтобы они работали автоматически:

  • Валидаторы в зависимости от типов полей таблицы
  • Возможность сразу fetch’ить данные из нескольких таблиц (точнее - получить такие данные довольно легко, но вот разбрасывать их по разным обьектам и связывать эти обьекты сейчас нужно ручками, если я не ошибаюсь, но опять же - это проблем не составляет)

Вроде всё. Общее впечатление - просто замечательная система. Использовать легко и приятно. :)

Пример:
Сначала пойдут мои супертипы слоя (кто читал Patters of EAA - поймет):

class Db_Table extends Zend_Db_Table_Abstract {
  /**
   * @return Zend_Db_Table_Rowset_Abstract
   */
  public function fetchAllBy($key, $value) {
    $where = $this->getAdapter()->quoteInto("$key = ?", $value);
    return $this->fetchAll($where);
  }

  /**
   * @return Zend_Db_Table_Row_Abstract
   */
  public function fetchRowBy($key, $value) {
    $where = $this->getAdapter()->quoteInto("$key = ?", $value);
    return $this->fetchRow($where);
  }

  public function __call($name, $arguments) {
    if(strpos($name, 'fetchRowBy') === 0) {
      array_unshift($arguments, substr($name, 10));
      return call_user_func_array(array($this, 'fetchRowBy'), $arguments);
    }

    if(strpos($name, 'fetchAllBy') === 0) {
      array_unshift($arguments, substr($name, 10));
      return call_user_func_array(array($this, 'fetchAllBy'), $arguments);
    }

    throw new Exception("Undefined method $name");
  }
}

class Db_Record extends Zend_Db_Table_Row_Abstract {
}

А теперь - пример использования:

class Item extends Db_Table {
  protected $_name = 'items';
  protected $_rowClass = 'ItemRecord';
  protected $_referenceMap = array(
      'Group' => array(
        'columns'       => 'groupid',
        'refTableClass' => 'Group',
        'refColumns'    => 'groupid',
      )
    );
}

class ItemRecord extends Db_Record {
}

class Group extends Db_Table {
  protected $_name = 'groups';
  protected $_rowClass = 'GroupRecord';
  protected $_dependentTables = array('Item');
}

class GroupRecord extends Db_Record {
}

$itemTable = new Item();
$item = $itemTable->fetchRowBySlug('hello');
$group = $item->findParentGroup();

Согласитель - всё просто и удобно, не так ли?

Для заинтересовавшихся - очень советую проштудировать полностью главу о Zend_Db из документации Zend Framework’а. А также - мой пост про Zend_Db_Table, посвященный его улучшению (правда, я не знаю, насколько он сейчас актуален, проверять нет времени :( ).

written by FX Poster \\ tags: , ,