2
votes

I have baked this standard add class which works fine.

class ClientsController extends AppController
{
 /**
 * Add method
 *
 * @return \Cake\Network\Response|void Redirects on successful add, renders view otherwise.
 */
 public function add()
 {
    $client = $this->Clients->newEntity();
    if ($this->request->is('post')) {
        $client = $this->Clients->patchEntity($client, $this->request->data);
        if ($this->Clients->save($client)) {
            $this->Flash->success(__('The client has been saved.'));
            return $this->redirect(['action' => 'index']);
        } else {
            $this->Flash->error(__('The client could not be saved. Please, try again.'));
        }
    }
    $this->set(compact('client'));
    $this->set('_serialize', ['client']);
}

With this add.ctp file - the output is as it should be:

<div class="clients form large-9 medium-8 columns content">
<?= $this->Form->create($client) ?>
<fieldset>
    <legend><?= __('Register Customer') ?></legend>
    <?php
        echo $this->Form->input('company_indicator');
        echo $this->Form->input('name');
        echo $this->Form->input('name2');
        echo $this->Form->input('address_id');
        echo $this->Form->input('address_street');
        echo $this->Form->input('address_street_number');
        echo $this->Form->input('address_postal_code');
        echo $this->Form->input('address_city');
        echo $this->Form->input('address_country');
        echo $this->Form->input('address_lat');
        echo $this->Form->input('address_lng');
        echo $this->Form->input('phone1');
        echo $this->Form->input('phone2');
        echo $this->Form->input('fax');
        echo $this->Form->input('website');
        echo $this->Form->input('logo');
        echo $this->Form->input('vat_number');
        echo $this->Form->input('taxId');
        echo $this->Form->input('tax_office');
    ?>
 </fieldset>
 <?= $this->Form->button(__('Submit')) ?>
 <?= $this->Form->end() ?>
</div>

I try to do the same with wizard steps

class ClientsController extends AppController
{
/**
 * use beforeRender to send session parameters to the layout view
 */
 public function beforeRender(Event $event) {
    parent::beforeRender($event);
    $params = $this->request->session()->read('Form.params');
    $this->set(compact('params'));
}

/**
 * delete session values when going back to index
 * you may want to keep the session alive instead
 */
public function regIndex() {
    $this->request->session()->delete('Form');
}

/**
 * this method is executed before starting 
 * the form and retrieves one important parameter:
 * the form steps number by counting the number 
 * of files that start with reg_step
 */
public function regSetup() {
        $clientsViewFolder = new Folder(APP.'Template'.DS.'Clients');
        $steps = count($clientsViewFolder->find('reg_step.*\.ctp'));
        $this->request->session()->write('Form.params.steps', $steps);
        $this->request->session()->write('Form.params.maxProgress', 0);
        $this->redirect(array('action' => 'regStep', 1));
}

    /**
     * this is the core step handling method
     * it gets passed the desired step number, performs some checks to prevent smart users skipping steps
     * checks fields validation, and when succeding, it saves the array in a session, merging with previous results
     * if we are at last step, data is saved
     * when no form data is submitted (not a POST request) it sets this->request->data to the values stored in session
     */
    public function regStep($stepNumber) {

        $this->log('regStep($stepNumber)= ' . $stepNumber , 'debug');

        /**
         * check if a view file for this step exists, otherwise redirect to index
         */
        if (!file_exists(APP.'View'.DS.'Clients'.DS.'reg_step'.$stepNumber.'.ctp')) {
                        $this->redirect('/clients/reg_index');
        }

      $session = $this->request->session();

        /**
         * determines the max allowed step (the last completed + 1)
         * if choosen step is not allowed (URL manually changed) the user gets redirected
         * otherwise we store the current step value in the session
         */
        $maxAllowed = $this->request->session()->read('Form.params.maxProgress') + 1;
        if ($stepNumber > $maxAllowed) {
                        $this->redirect('/clients/reg_step/'.$maxAllowed);
        } else {
                        $session->write('Form.params.currentStep', $stepNumber);
        }

        $client = $this->Clients->newEntity();
        $client->name = 'Tiknas';

        /**
         * check if some data has been submitted via POST
         * if not, sets the current data to the session data, to automatically populate previously saved fields
         */
        if ($this->request->is('post')) {
                /**
                 * set passed data to the model, so we can validate against it without saving
                 */
                 $client = $this->Clients->patchEntity($client, $this->request->data);

                /**
                 * if data validates we merge previous session data with submitted data, using CakePHP powerful Hash class (previously called Set)
                 */
                if ($this->Client->validates($client)) {
                        $prevSessionData = $session->read('Form.data');
                        $currentSessionData = Hash::merge( (array) $prevSessionData, $this->request->data);

                        /**
                         * if this is not the last step we replace session data with the new merged array
                         * update the max progress value and redirect to the next step
                         */
                        if ($stepNumber < $session->read('Form.params.steps')) {
                                $session->write('Form.data', $currentSessionData);
                                $session->write('Form.params.maxProgress', $stepNumber);
                                $this->redirect(array('action' => 'reg_step', $stepNumber+1));
                        } else {
                                /**
                                 * otherwise, this is the final step, so we have to save the data to the database
                                 */
                                if ($this->Client->save($currentSessionData))
                                {
                                        $this->Flash->success(__('Customer created!'));
                                        return $this->redirect('/clients/reg_index');
                              } else {
                                        $this->Flash->error(__('The client could not be saved. Please, try again.'));
                                }
                        }
                }
        } else {
                $this->request->data = $session->read('Form.data');
        }

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

        /**
         * here we load the proper view file, depending on the stepNumber variable passed via GET
         */
        $this->render('reg_step'.$stepNumber);
    }

}

With this reg_step1.ctp file which results in an error:

<div class="clients form large-9 medium-8 columns content">
<?= $this->Form->create($client) ?>
<fieldset>
    <legend><?= __('Register Customer') ?></legend>
    <?php
        echo $this->Form->input('company_indicator');
        echo $this->Form->input('name');
        echo $this->Form->input('name2');
        echo $this->Form->input('address_id');
        echo $this->Form->input('address_street');
        echo $this->Form->input('address_street_number');
        echo $this->Form->input('address_postal_code');
        echo $this->Form->input('address_city');
        echo $this->Form->input('address_country');
        echo $this->Form->input('address_lat');
        echo $this->Form->input('address_lng');
        echo $this->Form->input('phone1');
        echo $this->Form->input('phone2');
        echo $this->Form->input('fax');
        echo $this->Form->input('website');
        echo $this->Form->input('logo');
        echo $this->Form->input('vat_number');
        echo $this->Form->input('taxId');
        echo $this->Form->input('tax_office');
    ?>
</fieldset>
<?= $this->Html->link('Next step',
    array('action' => 'reg_step', $params['currentStep'] + 1),
    array('class' => 'button')); ?>
<?= $this->Form->end(); ?>
</div>

The error details

==> Invalid data type, must be an array or \ArrayAccess instance. ==> InvalidArgumentException

⟩ Cake\View\Helper\FormHelper->input APP/Template\Clients\reg_step1.ctp, line 9 which corresponds to this line echo $this->Form->input('company_indicator');

I tried the same with commenting this line but i get the same error.

I checked the $client which is empty in both examples. (YES $client is an Entity)

I searched for this error but found only HASH errors which have nothing to do with this case. There must be an error in the controller but i could not find it. Why is this error raised in this context? Where do i have to search to find the real issue? Any ideas?

StackTrace :

2016-02-15 21:50:50 Error: [InvalidArgumentException] Invalid data type, must be an array or \ArrayAccess instance.
Request URL: /pam/clients/reg-step/1
Referer URL: http://localhost/pam/clients/regIndex
Stack Trace:
#0  C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Network\Request.php(1158): Cake\Utility\Hash::get(NULL, 'company_indicat...')
#1 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\Form\EntityContext.php(208): Cake\Network\Request->data('company_indicat...')
#2 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\Helper\FormHelper.php(2385): Cake\View\Form\EntityContext->val('company_indicat...')
#3 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\Helper\FormHelper.php(1387): Cake\View\Helper\FormHelper->_initInputField('company_indicat...', Array)
#4 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\Helper\FormHelper.php(1115): Cake\View\Helper\FormHelper->checkbox('company_indicat...', Array)
#5 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\Helper\FormHelper.php(1023): Cake\View\Helper\FormHelper->_getInput('company_indicat...', Array)
#6 C:\Users\D052192\OneDrive\xampp\htdocs\pam\src\Template\Clients\reg_step1.ctp(9): Cake\View\Helper\FormHelper->input('company_indicat...', Array)
#7 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\View.php(992): include('C:\\Users\\D05219...')
#8 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\View.php(952): Cake\View\View->_evaluate('C:\\Users\\D05219...', Array)
#9 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\View\View.php(587): Cake\View\View->_render('C:\\Users\\D05219...')
#10 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Controller\Controller.php(611): Cake\View\View->render('reg_step1', NULL)
#11 C:\Users\D052192\OneDrive\xampp\htdocs\pam\src\Controller\ClientsController.php(250): Cake\Controller\Controller->render('reg_step1')
#12 [internal function]: App\Controller\ClientsController->regStep('1')
#13 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\friendsofcake\crud\src\Controller\ControllerTrait.php(51): call_user_func_array(Array, Array)
#14 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(114): App\Controller\AppController->invokeAction()
#15 C:\Users\D052192\OneDrive\xampp\htdocs\pam\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(87): Cake\Routing\Dispatcher->_invoke(Object(App\Controller\ClientsController))
#16 C:\Users\D052192\OneDrive\xampp\htdocs\pam\webroot\index.php(37): Cake\Routing\Dispatcher->dispatch(Object(Cake\Network\Request), Object(Cake\Network\Response))
#17 {main}
1
Whenever receiving errors, please always post the complete, exact error message including the full stacktrace (ideally the one from the logs which is readibly formatted)! Also note that you can navigate through the stacktrace on the left, and see possible contextual values for each step, so you may want to investigate that. And why is $client an array btw, according to your code it should be an entity?ndm

1 Answers

4
votes

Looking at the stacktrace, the request data (Request::$data) is null, and Hash::get() doesn't like that.

The problem stems from your second $session->read('Form.data') call, at some point the value doesn't exist yet/anymore, so this the method will return null, and you are overwriting the request data with it.

Depending on what fits your wizards logic, ensure that you are either retrieving an empty array (like you did with the first call), or do not overwrite the request data in these cases.