CakePHP 2 Validating Multiple Forms on One Page while Posting to same Controller Action

Written by James McDonald

December 27, 2020

I have a CakePHP 2.x view that creates multiple forms, one for each production line. By default each form is validated by the LineSetup Model and saves to the ‘line_setup’ table in the database

The Controller Action the forms share is LineSetupsController/configure()

View Code

This is the view code I have modified to allow 3 forms to the same endpoint

You need to logically separate them so they can receive back their unique validation errors. Note the $formName is unique for each form e.g. LineSetup1, LineSetup2, LineSetup3

// View/LineSetup/configure.ctp
<?php foreach ($productionLines as $productionLine) : ?>
    <?php $formName = 'LineSetup' . $productionLine['ProductionLine']['id']; ?>
    <div class="panel-body">
        <?= $this->Form->create($formName, [
            'id' => $formName,
        ]); ?>
        <?php echo $this->Form->input('item_id', [
            'value' => $productionLine['LineSetup']['item_id'],
            'label' => "Change to",
            'empty' => '(none)'
        ]); ?>
        <!-- other hidden form fields here  -->
        <?php echo $this->Form->input('user_id', [
            'type' => 'hidden',
            'value' => $user['id']
        ]); ?>
        <?php echo $this->Form->button('Save'); ?>
    </div>
    <?php echo $this->Form->end(); ?>
<?php endforeach; ?>

If you create the forms with the default Form->create('LineSetup') when the forms post back to the controller action and fails validation any errors are copied to the help block for each input in all three forms because each control on each form input has the same name: data[LineSetup][item_id]

This is an example of a control with the standard Model name in the name= param

<select name="data[LineSetup][item_id]" class="form-control" id="LineSetup1ItemId">
<option value="">(none)</option>
<option value="358">69998 - Description 1</option>
<option value="350">69999 - Toggen Test Product</option>
</select>

For each form you need to avoid repeating field names so the above becomes different for each form

<!-- inside form one -->
<select name="data[LineSetup1][item_id]" class="form-control" id="LineSetup1ItemId">
<option value="">(none)</option>
<option value="358">69998 - Description 1</option>
<option value="350">69999 - Toggen Test Product</option>
</select>

<!-- inside form two -->
<select name="data[LineSetup2][item_id]" class="form-control" id="LineSetup1ItemId">
<option value="">(none)</option>
<option value="358">69998 - Description 1</option>
<option value="350">69999 - Toggen Test Product</option>
</select>

<!-- inside form one -->
<select name="data[LineSetup3][item_id]" class="form-control" id="LineSetup1ItemId">
<option value="">(none)</option>
<option value="358">69998 - Description 1</option>
<option value="350">69999 - Toggen Test Product</option>
</select>

Model Code

To fix the validation errors showing on all forms you need to create a dummy validation model so inside my Model/LineSetup.php file I create a function that creates and returns $model with a custom name but copies the parent LineSetup classes validation rules to itself.

// Model/LineSetup.php

    public function createCustomValidationModel($formName)
    {
        $model = ClassRegistry::init(
            [
                'class' => $formName,
                'table' => true,
                'type' => 'Model',
            ]
        );

        $model->useTable = 'line_setup';

        $model->validate = $this->validate;

        return $model;
    }

    // these are the normal Model validation rules 
    // which you copy into the customValidation model
    public $validate = [
        'item_id' => [
            'notBlank' => [
                'rule' => 'notBlank',
                'message' => 'Please choose an item'
            ]
        ]
    ];

Controller Code

In the Controller actions post section you call the createCustomValidationModel function passing it the custom $formName. So when the view Posts from LineSetup1 form the validation and any resulting validationErrors only applies to the one form

<?php
// Controller/LineSetupsController.php
// inside configure() action
if ($this->request->is('post')) {
    // grab the top level LineSetup1, LineSetup2, LineSetup3 key

    $formName = array_keys($this->request->data)[0];

    // create a custom validation model with a name that is 
    // not "LineSetup"
    // i.e. "LineSetup1"
    $model = $this->LineSetup->createCustomValidationModel($formName);


    $this->LineSetup->updateAll(
        array('LineSetup.active' => false),
        array('LineSetup.production_line_id' => $this->request->data[$formName]['production_line_id'])
    );

    // with the custom model you can then
    // set and validate the data
    // and any error will automatically go to the
    // correct form
    $model->set($this->request->data);

    if ($model->validates()) {

        $this->LineSetup->create();

        // swap it back to the default model name
        $save['LineSetup'] = $this->request->data[$formName];

        if ($this->LineSetup->save($save)) {
            $this->Flash->success(__('The line setup has been saved.'));
            return $this->redirect([
                'action' => 'configure'
            ]);
        }
    } else {
        $this->Flash->error(__('The line setup could not be saved. Please, try again.'));
    }
}

The End Result – Duplicate Forms Displaying Individual Validation Errors Correctly

This picture shows what should be the result. A form with identical fields which posts back to a shared controller action but returns the validationErrors to the correct form.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

You May Also Like…

PC Build

I recently built myself a PC using the SilverStone SG13 Case. I am planning to use it as a Hot Spare. So any client...