I’d like to implement some SluggableBehavior functionality, so I’ve decided to use behaviors as many times before, but I’ve got some troubles.
When massively assigning attributes in controller, model verifies that they are safe, so my behavior class should add one new field, that can be safely assigned — ‘slug’ in my case.
I’ve implemented it in a veeery ugly way and I definetly don’t think, that this is good solution, but it works:
class SluggableBehavior extends CActiveRecordBehavior {
public $title;
private function _extendModel(SluggableModel $model) {
foreach ($model->getValidators() as $validator) {
/** @var $validator CValidator */
if ($validator instanceof CSafeValidator) {
$validator->attributes[] = 'slug';
}
}
}
public function afterFind(CEvent $event) {
$this->_extendModel($this->getOwner());
}
public function afterConstruct(CEvent $event) {
$this->_extendModel($this->getOwner());
}
public function beforeValidate(CEvent $event) {
/** @var $owner SluggableModel */
$owner = $this->getOwner();
/** @var $existing SluggableModel */
$existing = $owner->model()->findByAttributes(array(
'slug' => $owner->slug,
));
if ($existing !== null && $existing->id != $owner->id) {
$owner->addError('slug', 'This field should be unique (now assigned to «' . $existing->{$this->title} . '», ID=' . $existing->id . ').');
}
return true;
}
}
It’s a bit unfair to use it this way:) even more so if it’s really just for reading. This is cheating, and there is no guarantee that after the upcoming update of this will not break. Thus, if there is a good solution, I’d better use it.
I agree, but I can attach multiple behaviours to a single model. Multiple inheritance is not implemented in PHP, so it can’t be done using just pure OOP way.
I plan to implement TaggableBehavior also, which will add relations to Tags to the model it’s attaching to. Basic usage will be via the MultiAutoComplete field in view, through which tags will be passed to model, so it will need validation too.
class SluggableBehavior extends CActiveRecordBehavior {
public function attach($owner) {
parent::attach($owner);
if (!($owner instanceof CActiveRecord)) throw new Exception('Owner must be a CActiveRecord class');
/** @var $owner CActiveRecord */
$validators = $owner->getValidatorList();
$validators->add(CValidator::createValidator('unique', $owner, 'slug', array(
'allowEmpty' => false,
'caseSensitive' => false,
)));
$validators->add(CValidator::createValidator('safe', $owner, 'slug', array()));
}
}
getValidatorList() returns extendable CList of validators, so I’m free to add there anything. attach() is the replacement for both afterFind() and afterConstruct().