How to validate required association presence in CakePHP?

515 Views Asked by At

I have a working belongsToMany association between my Proyectos and my Medios models. If I add a new Proyecto entity, I can select from among the registered Medios, and everything is adequately saved. The thing is the entity is correctly saved whether I select any Medio or not, and I want it to be a required field in the form.

I tried adding a "required => true" at the form, and a "$validator->notEmpty" at the ProyectoTable, but it doesn't work. (It still saves the form even if no Medio is selected.)

I have been reading the CakePHP 3.x documentation, but for the life of me I haven't found the correct way to validate the associated data's presence. What am I missing? (I suppose I should add some kind of special rule, but I don't get how and where should I put it.)

My Model:

class ProyectosTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        [...]
        
        $this->belongsToMany('Medios', [
            'foreignKey' => 'proyecto_id',
            'targetForeignKey' => 'medio_id',
            'joinTable' => 'medios_proyectos',
        ]);
    }

    public function validationDefault(Validator $validator)
    {
        [...]
        
            $validator
            ->requirePresence('medios', 'create')
            ->notEmpty('medios');
            
        return $validator;
    }
}

My Controller:

public function add()
{
    $proyecto = $this->Proyectos->newEntity();
    if ($this->request->is('post')) {
        $partida = $this->Proyectos->patchEntity($proyecto, $this->request->getData());
        if ($this->Proyectos->save($proyecto)) {
            $this->Flash->success('El proyecto ha sido salvado.');

            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error('El proyecto no pudo ser salvado.');
    }

    $medios = $this->Proyectos->Medios->find('list', ['limit' => 200]);
    $this->set(compact('proyecto','medios'));
}

My View:

<?= $this->Form->create($proyecto) ?>
    [...]
    <?= $this->Form->control('medios._ids', ['options' => $medios,'required' => true]); ?>
    [...]
    <?= $this->Form->button('submit') ?>
<?= $this->Form->end() ?>

I already tried changing it to "$validator->requirePresence('medios._ids', 'create')->notEmpty('medios._ids');" in the model, but then the view throws me the "can't save, required field" error, whether I select one, all or no Medio at all.

1

There are 1 best solutions below

3
On BEST ANSWER

Multi select controls do have a hidden fallback like this:

<input type="hidden" name="medios[_ids]" value=""/>

So when nothing is selected, the field is still present, indicating an empty selection. Using ._ids you are creating a nested structure, so the value that is being submitted for medios will never be interpreted as empty, you'd receive something like this:

[
    'medios' => [
        '_ids' => ''
    ]
]

So the notEmpty rule of course won't apply here. Also validation rule field names do not support the dot syntax, they strictly map to first level fields.

What you can do is for example using a nested validator for the medios field, the rules of that validator would apply to the fields nested under medios. Something along the lines of this:

public function validationDefault(Validator $validator)
{
    // ...

    $validator
        ->requirePresence('medios', 'create')
        ->addNested('medios', $this->getValidator('medios'));

    return $validator;
}

public function validationMedios(Validator $validator)
{
    $validator
        ->requirePresence('_ids')
        ->notEmpty('_ids');

    return $validator;
}

See also