Немного переписал ETaggableBehavior

Выкладываем свои наработки
Ответить
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Немного переписал ETaggableBehavior

Сообщение mitallast »

Не дождавшись нужного функционала в поведении, а заодно насмотревшись на CDbCommandBuilder , немного переписал код поведения для получения новых возможностей:

1) Добавлена так недостающая поддержка CDbCommand в двух возможных вариантах : установка дефолтного CDbCriteria в виде параметра scope и передача как параметра в функции типа getTagsWithModelCount(CDbCriteria $criteria). Первый вариант действует во всех случаях подгрузки тегов к модели, по сути - тот же defaultScope в модели, но оформленный как переменная в поведении для удобства конфига, например :

Код: Выделить всё

'tags' => array(
    'class' => 'application.extensions.yiiext.behaviors.model.taggable.ETaggableBehavior',
    'scope' => array(
        'condition' => ' t.user_id = :user_id ',
        'params' => array( ':user_id' => Yii::app()->user->id ),
    ),
),
 
2) Как логичное продолжение первого, добавлен атрибут insertValues, который содержит массив значений, выставляемых текущим добавляемым тегам, пример:

Код: Выделить всё

'tags' => array(
    'class' => 'application.extensions.yiiext.behaviors.model.taggable.ETaggableBehavior',
    'insertValues' => array(
        'user_id' => Yii::app()->user->id,
    ),
),
 

Все это делалось для возможности реализовать пользовательские теги, которые видны только конкретному участнику, но могут быть и совершенно другие потребности, под себя не затачивал.
Пришлось переписать достаточно много кода с жестко забитых SQL на CDbCommandBuilder, чтобы обеспечить работу с CDbCriteria.

Замечу, что я пока не вкурил PHPUnit и оттестировать толком не умею :oops:
Вложения
ETaggableBehavior.zip
ETaggableBehavior
(3.95 КБ) 222 скачивания
Последний раз редактировалось mitallast 2010.05.03, 18:14, всего редактировалось 1 раз.
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение mitallast »

и тут же уперся в ограничение:

Код: Выделить всё

         public function createDeleteCommand($table,$criteria)
    {
        $this->ensureTable($table);
        $sql="DELETE FROM {$table->rawName}";
        $sql=$this->applyJoin($sql,$criteria->join);
        $sql=$this->applyCondition($sql,$criteria->condition);
        $sql=$this->applyGroup($sql,$criteria->group);
        $sql=$this->applyHaving($sql,$criteria->having);
        $sql=$this->applyOrder($sql,$criteria->order);
        $sql=$this->applyLimit($sql,$criteria->limit,$criteria->offset);
        $command=$this->_connection->createCommand($sql);
        $this->bindValues($command,$criteria->params);
        return $command;
    }
 
А как же запрос типа

Код: Выделить всё

DELETE a FROM foo a LEFT JOIN b on b.foo_id = a.id where a.id = 1
?
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение mitallast »

Немного поразмыслив, написал альтернативный конструктор запроса на удаление, с поддержкой select :

Код: Выделить всё

$builder = $this->getConnection()->getCommandBuilder();

if($criteria->alias!='')
    $alias=$criteria->alias;
$alias=$builder->getSchema()->quoteTableName($alias);

$table = $this->tagBindingTable;
$table=$builder->getSchema()->getTable($tableName=$table);

$select=is_array($criteria->select) ? implode(', ',$criteria->select) : $criteria->select;
$sql= "DELETE {$select} FROM {$table->rawName} $alias";

$sql=$builder->applyJoin($sql,$criteria->join);
$sql=$builder->applyCondition($sql,$criteria->condition);
$sql=$builder->applyGroup($sql,$criteria->group);
$sql=$builder->applyHaving($sql,$criteria->having);
$sql=$builder->applyOrder($sql,$criteria->order);
$sql=$builder->applyLimit($sql,$criteria->limit,$criteria->offset);

$command=$this->getConnection()->createCommand($sql);
$builder->bindValues($command,$criteria->params);
 
Вложения
ETaggableBehavior.zip
исправленная версия
(4.22 КБ) 216 скачиваний
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

Интересные изменения. Попробую рассмотреть подробнее и влить в SVN.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

__toString включать в YiiExt не буду точно (неспроста я его выкинул) и пользоваться им в данном случае не советую.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

Зачем в toTagsArray is_numeric?
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

Все изменения, за исключением deleteTags, существующие тесты проходят. Чего хотелось достичь в deleteTags?
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение mitallast »

__toString включать в YiiExt не буду точно (неспроста я его выкинул) и пользоваться им в данном случае не советую.
Уже столкнулся с этой проблемой при дебаге, согласен.
Зачем в toTagsArray is_numeric?
Если передается не строка, но то, что может без проблемы преобразовать тип - приведения типа не происходит. Соответственно, либо проверять тип - либо делать преобразование типа. Дурная привычка дебажить при помощи time() :)
Все изменения, за исключением deleteTags, существующие тесты проходят. Чего хотелось достичь в deleteTags?
исходный функционал сбрасывания связи в кросс-таблице убивал просто все. Мне нужно было учитывать правила scope, чтобы сохранить связи, не удовлетворяющие scope. Пример :

Имеем посты и теги, у тегов есть привязка к пользователю, и в scope соответствующее правило, что пользователь может работать только со своими тегами. Принимаем, что deleteTags работает как в исходном варианте.
Пользователь user1 выставил к посту №1 тег foo. При этом все связи тегов через кросс-таблицу удалились, пускай их изначально небыло, и ошибка не может быть обнаружена.
Пользователь user2 выставил к посту №1 тег bar. При этом все связи опять удалились, в том числе и от user1, хотя в scope явно указано, что этого делать он права не имеет. Пропадает привязка к тегу foo для пользователя user1, но выставляется тег bar для пользователя user2. Ситуация ошибочна, так как связь для user1 теряться не должна.

Исходя из проблемы, нужно удалять только те связи, что удовлетворяют в scope - а scope как раз применяются к таблице, содержащей теги. Для этого требуется конструкция sql

Код: Выделить всё

DELETE f FROM foo f INNER JOIN bar b on b.foo_id = f.id
, которая удаляет данные только из выбранной таблицы, а не всех обьединенных - СDbCommandBuilder::createDeleteCommand() этого не позволял сделать, поскольку жестко было забито "DELETE FROM".
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

По deleteTags ясно. Попробую написать, чтобы прошло тесты.
Дурная привычка дебажить при помощи time()
То есть? is_numeric реально понадобился или перестраховка?
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение mitallast »

То есть? is_numeric реально понадобился или перестраховка?
Иногда нужно добавить тег из объетка являющегося не только строкой, а например числом или обьектом с интерфейсом __toString. На мой взгляд, это полезная возможность. Если это не так, то нужно было бы ввести жесткую проверку типа , иначе можно нарваться на нативное исключение типа "аргумент 1 в array_walk не является массивом ". Пример :

Код: Выделить всё

protected function toTagsArray($tags){
  if(is_string($tags)) $tags = explode(',', $tags);
  array_walk($tags, array($this, 'trim'));
  return $tags;
} 
вызываем как $this->toTagsArray( 123 );
поскольку 123 не строка, а число, explode не будет, и в следующей строке получим : array_walk(123, array($this, 'trim'));
В документации явно указано, что передать можно только массив. В итоге получаем ошибку.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

Большая часть кода ушла в SVN. deleteTags пока остался прежним. Новый генерит нерабочий запрос (MySQL).
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение mitallast »

Новый генерит нерабочий запрос (MySQL).
А можно увидеть примерны нерабочего запроса ? В чем проблема возникает ?
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

Во-первых DELETE [что-то] FROM — запрос некорректный. Во-вторых алиас давать тому, из чего удаляем не выходит. В третьих, с JOIN тоже при удалении проблемы.
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение mitallast »

1), 2) По документации и delete [что-то] from вполне валиден, если [что-то] это имя или алиас таблицы http://phpclub.ru/mysql/doc/delete.html
Правда, я пока не могу учесть базы данных типа mssql, oracle или postgresql.

3) Тут согласен, не прочел про использование USING && JOIN .

Как вариант можно попробовать запрос типа

Код: Выделить всё

DELETE FROM post_tags WHERE post_tags.tag_id IN (  SELECT t.id FROM tags WHERE ....  ) AND post_tags.post_id = :post_id
Потери производительности сильной быть не должно , так как запросы друг от друга не зависят.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение samdark »

Подзапросы, насколько помню, в DELETE также не поддерживаются. По идее можно сделать сначала select, а потом delete where id in.

Ещё немного накоммитил в taggable.
mitallast
Сообщения: 207
Зарегистрирован: 2010.02.21, 20:40
Откуда: Голицыно
Контактная информация:

Re: Немного переписал ETaggableBehavior

Сообщение mitallast »

Подзапрос с delete входит в SQL-92 и работает как минимум в :
  • mysql
  • postgresql
  • sqlite
  • mssql
  • oracle
в живую проверил только mysql и sqlite, остальные по документации посмотрел официальной.

ps. Скачал свое обновление. Последний раз так радовался, только скачав обновление jQuery со своими правками :)
Ответить