Материал для экспертов. Многие сложные моменты не будут подробно объясняться, требуется хорошее знание 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',
);
Чтобы этот момент был чуть более понятен, я специально перенес свой старый топик.