Search code examples
mysqlcakephpassociationscakephp-3.0saving-data

CakePHP 3.x - Saving associated data across multiple tables


I'm having an issue with trying to get data saved in associated tables (currently when I save it only saves in the initial table, but not the others).

So my first table (Users):

-id
-username
-password
-email

Second table (Artists):

-name
-cp_id (foreign key)
-user_id (foreign key)
-genre

Third table (Contactpersons):

-cp_firstname
-cp_lastname
-cp_email
-cp_phoneno

Relationships:

A User hasOne Artist. An Artist belongsTo a User. (1-1) 
An Artist belongsTo Contactperson. A Contactperson hasMany Artists (1 to many)

user_id in Artists references id in Users
cp_id in Artists references id in Contactpersons

In my UsersController:

public function artistregister()
    {
        $user = $this->Users->newEntity();
        if($this->request->is('post')){
          $data = $this->request->data;
          $data['role_id'] = "2"; //role_id 2 makes the user register as role = artist
          $user = $this->Users->patchEntity($user, $data);
          $save = $this->Users->save($user);
          if ($save) {
            $user_id = $save->id;
            $data['user_id'] = $user_id;
            $ContactPersonsController = new ContactPersonsController();
            $ContactPersonsController->artistregister($data);
            $ArtistController = new ArtistsController();
            $ArtistController->artistregister($data);
            $this->Flash->success(__('Registration completed'));
            return $this->redirect(['action' => 'login']);
          } else {
            $this->Flash->error(__('Registration failed. Please try again.'));
          }
        }
        $artists = $this->Users->Artists->find('all');
        $persons = $this->Users->Artists->ContactPersons->find('all')->toArray();
        $this->set(compact('user', 'artists', 'roles', 'contactpersons','persons'));
          $this->set('_serialize', ['user']);
    }

Then in the Contactpersons controller, the same function:

public function artistregister($data)
    {
    if ($this->Contactpersons)
        $contactperson = $this->Contactpersons->newEntity();
        $contactperson = $this->Contactpersons->patchEntity($contactperson, $data);
        if ($this->Contactpersons->save($contactperson)) {
            $this->Flash->error(__('Contact person details could not be saved. Please try again.'));
        }
        $this->set(compact('contactpersons'));
        $this->set('_serialize', ['contactperson']);
    }

And the ArtistsController, the same function again:

public function artistregister($data)
    {
        $artist = $this->Artists->newEntity();
    $user = $this->request->session()->read('Auth.User');
    $data['user_id'] = $user['id'];
    $contactperson = $this->request->session()->read('Auth.Contactpersons') //I put in Auth.Contactpersons as a placeholder, but I'm not really sure what to put there.
    $data['cp_id'] = $contactperson['id'];
        $artist = $this->Artists->patchEntity($artist, $data);
        if ($this->Artists->save($artist)) {
            $this->Flash->error(__('Artist profile details could not be saved. Please try again.'));
        }
        $contactpersons = $this->Artists->Contactpersons->find('list', ['limit' => 200]);
        $this->set(compact('artist', 'contactpersons'));
        $this->set('_serialize', ['artist']);
    }

Oh and for good measure, here's the Artistregister.ctp file:

<div class="container" style="margin-top: 70px;">
    <?= $this->Flash->render('auth') ?>
    <?php $this->Form->templates([
        'inputContainer' => '<div class="form-group">{{content}}</div>',
        'inputContainerError' => '<div class="error" style="color:red;">{{content}}{{error}}</div>'
    ]);?>
    <div class="col-sm-2">
    </div>
    <div class="col-sm-8">
        <div class="container col-sm-12" style="border-radius: 10px; border: 1px solid;" >
            <?= $this->Form->create($user, ['role'=>'form']) ?>
            <header><?= __('Artist Registration') ?></header>
            <legend><?= __('User Account Details') ?></legend>
            <div class="col-sm-6">
                <?= $this->Form->input('username', ['class'=>'form-control',
                'placeholder'=>'Please enter a username.']);?>
            </div>
            <div class="col-sm-6">
                <?= $this->Form->input('email', ['class'=>'form-control', 'type'=>'email',
                'placeholder'=>'Please enter an email address.']);?>
            </div>
            <div class="col-sm-6">
                <?= $this->Form->input('password', ['class'=>'form-control', 'type'=>'password',
              'placeholder'=>'Please enter a password.']);?>
            </div>
            <div class="col-sm-6">
                <?= $this->Form->input('confirm_password', array('type'=>'password',
                    'label'=>'Confirm Password', 'value'=>'', 'autocomplete'=>'off','class'=>'form-control',
                  'placeholder'=>'Please re-enter your password.'))?>
            </div>
            <legend><?= __('Artist Profile Details') ?></legend>
            <div class="col-sm-12">
                <?= $this->Form->input('name', ['class'=>'form-control', 'label'=>'Artist Name',
              'placeholder'=>'Please enter your artist name.']);?>
            </div>
            <div class="col-sm-12">
                <?= $this->Form->input('genre', ['class'=>'form-control', 'type'=>'textarea', 'label'=>'Genres',
              'placeholder'=>'Please enter a description of the genres you play.']);?>
            </div>
            <legend><?= __('Contact Person Details') ?></legend>
            <div class="col-sm-6">
                <?= $this->Form->input('cp_firstname', ['class'=>'form-control', 'label'=>'First Name',
              'placeholder'=>'Please enter the first name of your contact.']);?>
            </div>
            <div class="col-sm-6">
                <?= $this->Form->input('cp_lastname', ['class'=>'form-control', 'label'=>'Last Name',
              'placeholder'=>'Please enter the last name of your contact.']);?>
            </div>
            <div class="col-sm-6">
                <?= $this->Form->input('cp_phoneno', ['class'=>'form-control','type'=>'tel', 'label'=>'Phone Number',
              'placeholder'=>'Please enter the phone number of your contact.']);?>
            </div>
            <div class="col-sm-6">
                <?= $this->Form->input('cp_email', ['class'=>'form-control','type'=>'email', 'label'=>'Email', 
              'placeholder'=>'Please enter the email of your contact.']);?>
            </div>
            <legend></legend>
            <div class="col-sm-12" >
                <?= $this->Form->button('<strong>'.__('Register').'</strong>',
                    ['class'=>'btn btn-primary', 'style'=>'width:100%;']); ?>
            </div>
            <div class="col-sm-12" align="center" style="margin-bottom: 20px;">
                <?= $this->Html->link(__("Already have an account? Log in"), ['action'=>'logout'])?>
            </div>
            <?= $this->Form->end() ?>
        </div>
    </div>
    <div class="col-sm-2">
    </div>
</div>

So when I go to the Artistregister page, fill in the fields and click submit, all the data relevant for the Users table is submitted, but the Contactpersons and Artists table remain blank. Currently the error I get is:

Call to undefined method App\Controller\ContactpersonsController::artistregister()

with the line referenced being:

$ContactPersonsController->artistregister($data);

in the UsersController.

Meanwhile, if I swap the order of the functions being called (i.e. call ArtistsController before ContactpersonsController), I get this error:

syntax error, unexpected '$data' (T_VARIABLE)

with the line referenced being:

$data['cp_id'] = $contactperson['id'];

This line is found in the ArtistsController function.

I'm fairly sure I get this error because no contactperson exists, therefore it can't find an id to put as cp_id - this is why in the UsersController function, I called the ContactpersonsController first to establish some data in the Contactpersons table.

Adding in the models for each:

Contactpersons:

class ContactpersonsTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('contactpersons');
        $this->displayField('id');
        $this->primaryKey('id');
    }

    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->requirePresence('cp_firstname', 'create')
            ->notEmpty('cp_firstname');

        $validator
            ->requirePresence('cp_lastname', 'create')
            ->notEmpty('cp_lastname');

        $validator
            ->requirePresence('cp_email', 'create')
            ->notEmpty('cp_email');

        $validator
            ->integer('cp_phoneno')
            ->requirePresence('cp_phoneno', 'create')
            ->notEmpty('cp_phoneno');

        return $validator;
    }
}

Artists:

class ArtistsTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('artists');
        $this->displayField('name');
        $this->primaryKey('id');

        $this->addBehavior('Timestamp');

        $this->belongsTo('Contactpersons', [
            'foreignKey' => 'cp_id',
            'joinType' => 'INNER'
        ]);
        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER'
        ]);
        $this->hasMany('Bookings', [
            'foreignKey' => 'artist_id'
        ]);
    }

    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->requirePresence('name', 'create')
            ->notEmpty('name');

        $validator
            ->allowEmpty('genre');

        return $validator;
    }

    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['cp_id'], 'Contactpersons'));
        $rules->add($rules->existsIn(['user_id'], 'Users'));

        return $rules;
    }
}

Users:

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

        $this->table('users');
        $this->displayField('id');
        $this->primaryKey('id');

        $this->addBehavior('Timestamp');

        $this->belongsTo('Roles', [
            'foreignKey' => 'role_id',
            'joinType' => 'INNER'
        ]);

        $this->hasOne('Artists', [
                'foreignKey' => 'user_id'
        ]);

            $this->hasOne('Engineers', [
                'foreignKey' => 'user_id'
        ]);
    }

    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->requirePresence('username', 'create')
            ->notEmpty('username');

        $validator
            ->requirePresence('password', 'create')
            ->notEmpty('password');

        $validator
            ->email('email')
            ->requirePresence('email', 'create')
            ->notEmpty('email');

        return $validator;
    }

    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->isUnique(['username']));
        $rules->add($rules->isUnique(['email']));
        $rules->add($rules->existsIn(['role_id'], 'Roles'));

        return $rules;
    }
}

Solution

  • Got the answer - slightly modified from what @Derek provided and with help from the CakePHP Cookbook.

    public function artistregister()
        {
            $user = $this->Users->newEntity($this->request->data(),[
              'associated'=>[
                'Artists' => ['associated' => ['Contactpersons']]
              ]
            ]);
            if($this->request->is('post')){
              $data = $this->request->data;
              $data['role_id'] = "2";
              $user = $this->Users->patchEntity($user, $data, [
                'associated'=>[
                  'Artists' => ['associated' => ['Contactpersons']]
                ]
              ]);
              $save = $this->Users->save($user);
              $user_id = $save->id;
              $data['user_id'] = $user_id;
              $this->Flash->success(__('Registration completed'));
              return $this->redirect(['action' => 'login']);
            } else {
              $this->Flash->error(__('Registration failed. Please try again.'));
            }
            $this->set(compact('user', 'artists', 'roles', 'contactpersons'));
              $this->set('_serialize', ['user']);
        }