26 сент. 2014 г., 1:12
Запрет на сохранение xPDO-объекта, если не заполнены необходимые данные
Материал для экспертов. Многие сложные моменты не будут подробно объясняться, требуется хорошее знание xPDO.
Небольшое вступление. Заморочился я тут на сайте реализовать теги для топиков (для SEO отличная штука). Создал новый класс SocietyTopicTag со следующими колонками: id, topic_id, tag, active. Какие тут главные моменты, побудившие написать топик?
- Поле tag не может быть пустым.
- Поле topic_id так же не может быть пустым.
И вот далее мы разберемся с тем, как это предусмотреть, и какие тут есть подводные камни...
Начнем с простого - с проверки поля tag и запрета сохранения. Сразу посмотрим конечный код класса SocietyTopicTag:
<php class SocietyTopicTag extends xPDOSimpleObject { public function save($cacheFlag= null) { $this->tag = trim($this->tag); if(!$this->tag){ $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "[- ". __CLASS__ ." -]. Column tag can not be empty"); return false; } if(!$this->getOne('Topic')){ $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "[- ". __CLASS__ ." -]. Topic required"); return false; } return parent::save($cacheFlag); } }
1. Перегружаем оригинальный метод save(). Теперь, если что-то не так, мы можем вернуть false, что будет означать, что объект не был сохранен.
2. Проверяем наличие значения у поля tag.
if(!$this->tag){ $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "[- ". __CLASS__ ." -]. Column tag can not be empty"); return false; }
Теперь вот такой код не создаст нового объекта:
<php $tag = $modx->newObject('SocietyTopicTag', array( "tag" => "", )); if(!$tag->save()){ print "Error"; } else{ print "Saved"; }
Так как tag не заполнен, то сохранение объекта прервется. Это исключит сохранение объекта с незаполненым полем.
А вот с topic_id несколько сложнее... Почему вот мы не стали проверять поле topic_id так же, как и tag, просто на наличие значения?
Во-первых, топик для этого тега может быть еще новый и не сохранен в БД, и соответственно у него и не будет id. А если у топика нет id, то мы просто не можем записать его в качестве topic_id. К примеру, в таком случае бы вот этот код не сработал:
<php $tag = $modx->newObject('SocietyTopicTag', [ "tag" => "DSfdsfdsF", ]); $r = $modx->newObject('modResource'); $r->addMany($tag); $r->save();
Во-вторых, можно было бы указать id несуществующего топика. В случае проверки $this->topic_id у нас просто выполняется проверка значения на наличие, но нет проверки наличия документа. А вот в случае проверки объекта $this->Topic, даже если мы просто передали значение topic_id, xPDO попытается выполнить запрос к БД и получить объект топика, и если его не получит, то тег не будет сохранен.
В итоге, представленная проверка сработает во всех случаях попытки сохранения как нового тега, так и обновления существующего.
P.S. Понятное дело, что в данном случае важную роль играет описание composite/aggregates-связей классов modResource и SocietyTopicTag.
В SocietyTopicTag map-файл дописываем:
'aggregates' => array ( 'Topic' => array ( 'class' => 'modResource', 'local' => 'topic_id', 'foreign' => 'id', 'cardinality' => 'one', 'owner' => 'foreign', ), ),
А вот в modResource мы просто так не вклинимся, так как это ядро MODX-а, и мы его не можем трогать. Но есть лазейка - meta-файл пакета (metadata.mysql.php). Вот туда мы и дописываем связь для modResource:
$this->map['modResource']['composites']['TopicTags'] = array( 'class' => 'SocietyTopicTag', 'local' => 'id', 'foreign' => 'topic_id', 'cardinality' => 'many', 'owner' => 'local', );
Чтобы этот момент был чуть более понятен, я специально перенес свой старый топик.