Adding Associated Records

In the previous tutorial, Table Relationships, we set up associated tables and displaying associated data, but there's no point to displaying associated data if you can't add it first.

There are multiple ways to save associated data. We'll start with the easiest way, discretely.

Save Associated Records Discretely

In our previous tutorials we set up Phone Numbers which belong to Users. We can simply add phone numbers by themselves and identify the related user. This saves all the data into the phone_numbers table, but through the user_id variable the records are related to the users table.

Edit your Phone Numbers controller at /src/Controller/PhoneNumbersController.php and update the add() method to load a list of users to choose from.

  public function add()
  {
    $phoneNumber = $this->PhoneNumbers->newEmptyEntity();
    if ($this->request->is('post')) {
      $phoneNumber = $this->PhoneNumbers->patchEntity($phoneNumber, $this->request->getData());
      if ($this->PhoneNumbers->save($phoneNumber)) {
        $this->Flash->success(__('The phone number has been saved.'));
        return $this->redirect(['action' => 'index']);
      }
      $this->Flash->error(__('The phone number could not be saved. Please, try again.'));
    }
    $users = $this->PhoneNumbers->Users->find('list', ['limit' => 200, 'order' => ['first_name', 'last_name']])->all();
    $types = $this->types;
    $this->set(compact('phoneNumber', 'types', 'users'));
  }

And in our phone numbers' add View at /templates/PhoneNumbers/add.php, we have a field to select the associated user.

<div class="row">
  <aside class="column">
    <div class="side-nav">
      <h4 class="heading"><?= __('Actions') ?></h4>
      <?php echo $this->Html->link(__('List Phone Numbers'), ['action' => 'index'], ['class' => 'side-nav-item']) ?>
    </div>
  </aside>
  <div class="column column-80">
    <div class="phoneNumbers form content">
      <?php echo $this->Form->create($phoneNumber) ?>
      <fieldset>
        <legend><?php echo __('Add Phone Number') ?></legend>
        <?php
          echo $this->Form->control('user_id');
          echo $this->Form->control('phone_number');
          echo $this->Form->control('type');
        ?>
      </fieldset>
      <?php echo $this->Form->button(__('Submit')) ?>
      <?php echo $this->Form->end() ?>
      </div>
  </div>
</div>

CakePHP will automatically populate the user_id field with the list of users stored in $users, and the type field with the list of types stored in $types. If you want to use a different list for a select control, simply add it as an option.

  $otherIndexedArray = [999 => 'User 1', 998 => 'User 2'];
  echo $this->Form->control('user_id', ['options' => $otherIndexedArray]);

Save Associated Records Discretely part 2

An immediate issue with the method above is you are forced to select the user every time you add a phone number since we didn't pass the user id from the user. We can easily adjust the "New Phone Number" link in the User view to include the user_id, and update the Phone Number add() function and view to accept and use that id.

In the Users view /templates/Users/view.php we update the link:

<?php echo $this->Html->link(__('New Phone Number'), ['controller' => 'phone_numbers', 'action' => 'add', $user->id], ['class' => 'button float-right']) ?>

So now when we click the link, instead of going to http://localhost/tutorial/phone-numbers/add it appends the user id to the end of the url.

Now we need to modify the phone number's add() function in the Controller  to make use of the passed id. Edit /src/Controller/PhoneNumbersController.php, to accept the user_id and filter the Users by the id if it is passed in.

  public function add($user_id = null)
  {
    ...
    if ($user_id) {
	  $users = $this->PhoneNumbers->Users->find('list')->where(['id' => $user_id])->all();
	} else {
	  $users = $this->PhoneNumbers->Users->find('list', ['limit' => 200, 'order' => ['first_name', 'last_name']])->all();
	}
    ...
  }  

Now when we load http://localhost/tutorial/phone-numbers/add/1 it automatically loads user #1 and only user #1 for our list of Users.

Cleanup

Every time we add or delete a phone number, our application redirects us to the phone number list. Instead, let's make it redirect us back to the User view we started from.

First, update the Phone Numbers' add() function to get the User from the submitted $user_id then redirect to the User view.

  public function add($user_id = null)
  {
    ...
      if ($this->PhoneNumbers->save($phoneNumber)) {
        $this->Flash->success(__('The phone number has been saved.'));
        $user = $this->PhoneNumbers->Users->get($user_id);
        return $this->redirect(['controller' => 'users', 'action' => 'view', $user->slug]);
      }
      ...

Next, update the Phone Numbers delete() function to get the user information when it gets the phone number, and redirect back to the User view.

  public function delete($id = null)
  {
    ...
    $phoneNumber = $this->PhoneNumbers->get($id, [
      'contain' => ['Users'],
    ]);
    ...
    return $this->redirect(['controller' => 'Users', 'action' => 'view', $phoneNumber->user->slug]);
  }

Now you can test this by adding and deleting a number from a user and it should always redirect back to the specified user.