phpvalidationcakephphas-and-belongs-to-manycakephp-2.7

HABTM form validation with CakePHP 2.x


I have a HABTM relation like : Post <-> Tag (a Post can have multiple Tag, and same the other way).

This work with the multiple checkbox selection generated by Cakephp. But I want to have at least one Tag for every Post and throw an error if someone try to insert an orphan.

I'm looking for the cleanest/most CakePHP alike way to do this.


This is more or less an update of this HABTM form validation in CakePHP question, as I get the same problem on my cakephp 2.7 (last cakephp 2.x for now with php 5.3 support at the date of 2016) and can't find a good way to do it.


Solution

  • Here are what I think is the best for now. It use the cakephp 3.x behaviour for HABTM validation.

    I choose to only work in model, with the most generic code.

    In your AppModel.php, set this beforeValidate() and afterValidate()

    class AppModel extends Model {
       /** @var array set the behaviour to `Containable` */
     public $actsAs = array('Containable');
    
       /**
        * copy the HABTM post value in the data validation scope
        * from data[distantModel][distantModel] to data[model][distantModel]
        * @return bool true
        */
     public function beforeValidate($options = array()){
       foreach (array_keys($this->hasAndBelongsToMany) as $model){
         if(isset($this->data[$model][$model]))
           $this->data[$this->name][$model] = $this->data[$model][$model];
       }
    
       return true;
     }
    
       /**
        * delete the HABTM value of the data validation scope (undo beforeValidate())
        * and add the error returned by main model in the distant HABTM model scope
        * @return bool true
        */
     public function afterValidate($options = array()){
       foreach (array_keys($this->hasAndBelongsToMany) as $model){
         unset($this->data[$this->name][$model]);
         if(isset($this->validationErrors[$model]))
           $this->$model->validationErrors[$model] = $this->validationErrors[$model];
       }
    
       return true;
     }
    }
    

    After this, you can use your validation in you model like this :

    class Post extends AppModel {
    
        public $validate = array(
            // [...]
            'Tag' => array(
                  // here we ask for min 1 tag
                'rule' => array('multiple', array('min' => 1)),
                'required' => true,
                'message' => 'Please select at least one Tag for this Post.'
                )
            );
    
            /** @var array many Post belong to many Tag */
        public $hasAndBelongsToMany = array(
            'Tag' => array(
                // [...]
                )
            );
    }
    

    This answer use :