phpvalidationcakephpcakephp-3.0

CakePHP 3.0 throwing 'field required' error on non-empty field


I'm having difficulty with my CakePHP 3.0 site throwing an error when it shouldn't. I'm building an "Add Page" form, shown below:

echo $this->Form->create($newpage);
echo $this->Form->control('title');
echo $this->Form->control('content');
echo $this->Form->button('Save');
echo $this->Form->end();

The title and content are required. Once the form is submitted, 'title' is used to generate a page 'name', which is currently just the title in lowercase and with spaces removed (so "About Us" would have the name "aboutus"). This name is also saved, but must be unique (eg if you had pages with titles "No Space" and "NOSpace" they would both end up with the name "nospace" even though the titles are unique).

I have the following validation rules in PagesTable.php:

public function validationDefault(Validator $validator)
{
    $validator = new Validator();

    $validator
        ->requirePresence('title','content')
        ->lengthBetween('title', [0, 50])
        ->add(
            'name',
            ['unique' => [
                'rule' => 'validateUnique',
                'provider' => 'table',
                'message' => 'Not unique']
            ]
            );

    return $validator;

}

The form is submitted to the controller, which contains this:

public function add($mainpage_id = null) {
    $newpage = $this->Pages->newEntity();

    if ($this->request->is('post')) {
        $newpage = $this->Pages->patchEntity($newpage, $this->request->data);
        // Get page name by removing spaces from title
        $name = strtolower(str_replace(' \'\"', '', $newpage->title));
        // Get navigation order by getting number of current siblings, and adding 1
        $siblings = $this->Pages->find('all')
                                ->where(['parent_id' => $newpage->parent_id])
                                ->count();
        $nav_order = $siblings + 1;
        $newpage = $this->Pages->patchEntity($newpage, ['name' => $name, 'nav_order' => $nav_order]);
        if ($newpage->errors()) {
            $errors = $newpage->errors();
            if(isset($errors['name'])) {
                $this->Flash->error('The title you have entered is too similar to one that already exists. To avoid confusion, please choose a different title.');
            }else {
                $this->Flash->error('Please correct errors below');
            }
        }else {
            $this->Pages->save($newpage);
            $page_id = $newpage->id;
            $this->Flash->success('Page created');
            return $this->redirect('/admin/pages/index');
            exit;
        }
    }

    $this->set(compact('newpage'));
    $this->set('_serialize', ['newpage']);
}

However, when I try to submit a page, I get "This field is required" on the title field even if I've entered a title. In fact, it won't let me submit the form without entering a title (a message pops up saying "Fill out this field"), but then it throws the error.

Can anyone see what I'm doing wrong?


Solution

  • Codebase looks good. However, this might be not obvious behavior: calling patch entity method validate the passed patch data. So the first time your patch is validated here:

    $newpage = $this->Pages->patchEntity($newpage, $this->request->data);

    and second time it's validated here:

    $newpage = $this->Pages->patchEntity($newpage, ['name' => $name, 'nav_order' => $nav_order]);

    and as second patch have no title, then validator said that it missed. To fix this you need to run patch once with all the data, i.e.:

        $data = $this->request->data();
        // Get page name by removing spaces from title
        $data['name'] = strtolower(str_replace(' \'\"', '', $data['title']));
        // Get navigation order by getting number of current siblings, and adding 1
        $siblings = $this->Pages->find('all')
                                ->where(['parent_id' => $data['parent_id']])
                                ->count();
        $data['nav_order'] = $siblings + 1;
        $newpage = $this->Pages->patchEntity($newpage, $data);
    

    Hope you understand the idea, and it'll help.

    UPDATE: just noticed that there might be no parent_id in request... You can move this logic into afterSave callback in PagesTabe:

    public function afterSave(Event $event, EntityInterface $entity) {
        $siblings = $this->count()
            ->where(['parend_id '=> $entity->parent_id]);
        // updateAll won't trigger the same hook again
        $this->updateAll(
                ['nav_order' => ++$siblings],
                ['id' => $entity->id]
        );
    }