monsur.hoq
monsur.hoq

Reputation: 1131

CakePHP 3.x unique validation not working for saving multiple records

I have a Questions table which has a validation like :

$validator
    ->notEmpty('title')
    ->add('title', [
        'unique' => [
            'rule' => [
                    'validateUnique', 
                     ['scope' => ['subject_id', 'chapter_id']]
            ],
            'provider' => 'table'
        ]
    ]);

I want to save following records into my table at a time.

Array
(
    [0] => Array
        (
            [subject_id] => 1
            [chapter_id] => 4
            [title] => What is a .ctp file used for in CakePHP?
        )
    [1] => Array
        (
            [subject_id] => 1
            [chapter_id] => 4
            [title] => What is a .ctp file used for in CakePHP?
        )
)

I try to save it using saveMany() method. It save both records i.e. validation not working. I also try following code for transactional() method instead of saveMany() method, but validation also not working.

$entities = $this->Questions->newEntities($records);
$this->Questions->connection()->transactional(function () use ($entities) {
    foreach ($entities as $entity) {
       $this->Questions->save($entity);
    }
});

My validation works fine if I save the records one by one using save() method or my records already saved in database. Why my unique validation not working for saveMany() and also for transactional() for duplicate new entities?

Upvotes: 2

Views: 1230

Answers (1)

ndm
ndm

Reputation: 60453

Validation happens before saving

Validation happens before saving, so that behavior is to be expected, given that the rule looks up the database, and not the request data (it would only have access to one set of data at the time anyways), ie, no matter how many datasets are being tested, none of the submitted ones will be saved yet, and therefore validation will pass unless a matching record already exists in the database.

So either create/patch and save all entities one by one in a custom transaction (and don't forget to add some proper failure checks),

$this->Questions->connection()->transactional(function () {
    foreach ($this->request->data() as $set) {
        $entity = $this->Questions->newEntity($set); // < validaton is being applied there
        if (!$this->Questions->save($entity)) { // < not there
            return false;
        }
    }
    return true;
});

or use application rules instead.

Application rules are being applied in the saving process

Application rules are being applied in the actual saving process, ie upon calling Table::save(), so to avoid the hassle of using custom transactions, and generally to have a last line of defense, use them instead of/additionally to validation.

// QuestionsTable class

public function buildRules(\Cake\ORM\RulesChecker $rules)
{
    $rules->add($rules->isUnique(['title', 'subject_id', 'chapter_id']));

    // ...

    return $rules;
}

See also

Upvotes: 2

Related Questions