Editing relationships

The tables

Here are the tables for the goat app:

Tables

Users are going to use a form to record each goat's fave comedian. Like this:

Form

How will that work?

The HTML

Let's check out the HTML that we'll need to generate.

  1. <label>Fave comedian<br>
  2.   <select name='fave-comedian'>
  3.     <option value='0'>(Not given)</option>
  4.     <option value='1' selected >Aisling Bea</option>
  5.     <option value='9' >David Mitchell</option>
  6.     <option value='10' >Eddie Izzard</option>
  7.     <option value='11' >Frankie Boyle</option>
  8.     <option value='5' >Isy Suttie</option>
  9.     <option value='7' >Jimmy Carr</option>
  10.     <option value='8' >Joe Wilkinson</option>
  11.     <option value='2' >Paula Poundstone</option>
  12.     <option value='4' >Roisin Conaty</option>
  13.     <option value='12' >Russel Howard</option>
  14.     <option value='3' >Sara Pascoe</option>
  15.     <option value='6' >Sarah Millican</option>
  16.     <option value='15' >Trevor Noah</option>
  17.     </select>
  18. </label>

select doesn't have the multiple attribute, so users can only choose one comedian.

Each option has the name of a comedian, and that comedian's id. For example:

  • <option value='4' >Roisin Conaty</option>

Roisin (pronounced Ro-sheenn) is record 4.

When editing a goat's data, the goat might already have a fave comedian set. We want to make sure that comedian is selected when the form first displays. You do that with the selected property:

  • <option value='1' selected>Aisling Bea</option>

PHP for the form

We want PHP that...

  • Makes an option tag for every comedian in the DB.
  • Adds the selected property to the one that is the goat's current fave.

goat-add-edit.php is the page with the form. It has this on it:

  • <p><?= makeGenderWidget($goatGender) ?></p>
  • <p><?= makeFaveComedianWidget($goatFaveComedianId, $comedianRows) ?></p>
  • <p><?= makeClubMembershipWidget($goatClubMemberships, $clubRows) ?></p>

I made functiony friends to make widgets. The middle widget is the one we're going to talk about, the one for the fave comedian.

Ethan
Ethan

This function is in the library file, like useful-functions.php? Then it's easy to reuse.

Actually, I didn't put these friends in a library. I put them at the end of goat-add-edit.php, the page that makes the form.

  • </body>
  • </html><?php
  • ...
  • /**
  •  * Make HTML to render fave comedian widget.
  •  * @param int $faveComedianId The favorite comedian
  •  * @param array $comedianRows Available comedians.
  •  * @return string HTML to make the widget.
  •  */
  • function makeFaveComedianWidget($faveComedianId, $comedianRows) {
  •   ...
  •   return $result;
  • }
Adela
Adela

Why? If they're just on that page, the functions are hard to reuse. And that's why we make functions, for reuse.

That's one reason to make functions, but not the only one. Separating code into functions also makes code easier to write. When I was writing goat-add-edit.php, I just put in the call to the function...

  • <p><?= makeFaveComedianWidget($goatFaveComedianId, $comedianRows) ?></p>

When I was writing that code, I thought to myself:

Self, don't worry about the function's deets for now.

That reduced the load on my brain.

Later, I wrote the function. When doing that, I didn't have to think about the rest of the page. I could focus just on the function.

Principle

Reduce cognitive complexity

Make your program easy to think about. You'll spend less time wrestling with bugs.

Keeping the load on your brain under control is very important in programming. Functiony friends help you with that.

Here's the code for making the dropdown (the select) for the fave comedian. Remember, we want to make this HTML:

  1. <label>Fave comedian<br>
  2.   <select name='fave-comedian'>
  3.     <option value='0'>(Not given)</option>
  4.     <option value='1' selected >Aisling Bea</option>
  5.     <option value='9' >David Mitchell</option>
  6.     <option value='10' >Eddie Izzard</option>
  7.     <option value='11' >Frankie Boyle</option>
  8.     <option value='5' >Isy Suttie</option>
  9.     <option value='7' >Jimmy Carr</option>
  10.     <option value='8' >Joe Wilkinson</option>
  11.     <option value='2' >Paula Poundstone</option>
  12.     <option value='4' >Roisin Conaty</option>
  13.     <option value='12' >Russel Howard</option>
  14.     <option value='3' >Sara Pascoe</option>
  15.     <option value='6' >Sarah Millican</option>
  16.     <option value='15' >Trevor Noah</option>
  17.     </select>
  18. </label>

The PHP:

  1. /**
  2.  * Make HTML to render fave comedian widget.
  3.  * @param int $faveComedianId The favorite comedian
  4.  * @param array $comedianRows Available comedians.
  5.  * @return string HTML to make the widget.
  6.  */
  7. function makeFaveComedianWidget($faveComedianId, $comedianRows) {
  8.     $result = "
  9.         <label>Fave comedian<br>
  10.             <select name='fave-comedian'>
  11.                 <option value='0'>(Not given)</option>\n";
  12.     foreach ($comedianRows as $comedianRow) {
  13.         $comedianId = $comedianRow['comedian_id'];
  14.         $comedianName = $comedianRow['name'];
  15.         $selected = '';
  16.         if ($comedianId == $faveComedianId) {
  17.             $selected = 'selected';
  18.         }
  19.         $result .= "<option value='$comedianId' $selected>$comedianName</option>\n";
  20.     }
  21.     $result .="
  22.             </select>
  23.         </label>\n";
  24.     return $result;
  25. }

You send in two values:

  • The id of current fave. If there isn't one, it will be null.
  • The records for all of the comedians.

Lines 8-11 make HTML for the start of the widget. It will be the same always, for every goat, and as comedians are added and removed.

Lines 12-20 are repeated for every comedian. Line 19 outputs the HTML:

  • $result .= "<option value='$comedianId' $selected>$comedianName</option>\n";

Notice the variable $selected is output every time through the loop. It's set to MT towards the start of the loop (line 15):

  • $selected = '';

Then, if the comedian the loop is processing, is the fave, $selected is changed.

  • if ($comedianId == $faveComedianId) {
  •     $selected = 'selected';
  • }

Processing fave comedian

The form sends the data to another page, through the session:

  • $_SESSION['goat_fave_comedian'] = $goatFaveComedianId;

Before sending the data, though, the form page does a little processing.

  • $goatFaveComedianId = getParamFromPost('fave-comedian');
  • if ($goatFaveComedianId == 0) {
  •     // (Choose) option selected on prior page.
  •     $goatFaveComedianId = null;
  • }

Remember that getParamFromPost() returns null if it can't find a value for the param fave-comedian. But there's another case that might cause problems. What if the user select the first option?

Not given

Here's the HTML for that option:

  • <option value='0'>(Not given)</option>

The value is 0. No comedian has 0 for their id. When we do...

  • $goatFaveComedianId = getParamFromPost('fave-comedian');

... $goatFaveComedianId will be incorrect.

So, let's add an if to take care of that:

  • $goatFaveComedianId = getParamFromPost('fave-comedian');
  • if ($goatFaveComedianId == 0) {
  •     // (Not given) option selected.
  •     $goatFaveComedianId = null;
  • }

If the user doesn't touch the dropdown, or if they select the first option, $goatFaveComedianId will be null.

Saving

goat-save.php picks it the user's choice, a comedian id, or null.

  • $goatFaveComedianId = $_SESSION['goat_fave_comedian'];

Then inject the value into SQL, e.g.:

  • $sql = "
  •     INSERT INTO goats (
  •         name, gender, fave_comedian, comments
  •     )
  •     VALUES (
  •         :name, :gender, :comedian, :comments
  •     )
  • ";
  • $stmnt = $dbConnection->prepare($sql);
  • $isWorked = $stmnt->execute(
  •     [
  •         'name' => $goatName,
  •         'gender' => $goatGender,
  •         'comedian' => $goatFaveComedianId,
  •         'comments' => $goatComments
  •     ]
  • );

Recall that null is what MySQL uses for an MT field. We've already made sure that, if the user does not choose a comedian, $goatFaveComedianId will be null.

Clubs

Looking further down the form made by goat-add-edit.php, we see another dropdown for club membership:

Clubs

Here's the HTML that made it:

  1. <label>Club memberships<br>
  2.   <select name='club-memberships[]' multiple size='5'>
  3.     <option value='1' selected>Butting</option>
  4.     <option value='3' >Fashion</option>
  5.     <option value='5' >Singing</option>
  6.     <option value='2' >Sleeping</option>
  7.     <option value='4' selected>Theoretical Physics</option>
  8.   </select>
  9. </label>

There's the multiple property in line 2, along with [] at the end of the name. That means the processing page will get an array for this widget, which we need if users are to select more than one element.

You can see that clubs 1 and 5 are selected.

More PHP

To make the widget, we need to know two things:

  • Which clubs the goat is in.
  • The names and ids of all the club.

This function will find the clubs a goat is in.

  1. /**
  2.  * Get the clubs a goat is in.
  3.  * @param int $goatId Goat id.
  4.  * @return null|array Records, null if something went wrong.
  5.  */
  6. function getClubsGoatIsIn($goatId) {
  7.     global $dbConnection;
  8.     $result = null;
  9.     if (!is_null($goatId) && is_numeric($goatId)) {
  10.         $sql = "
  11.             select
  12.                 clubs.*
  13.             from
  14.                 club_members, clubs
  15.             where
  16.                 club_members.goat_id = :goat_id_to_show
  17.                 and club_members.club_id = clubs.club_id
  18.             order by clubs.name;
  19.         ";
  20.         $stmnt = $dbConnection->prepare($sql);
  21.         $isQueryWorked = $stmnt->execute([':goat_id_to_show' => $goatId]);
  22.         if ($isQueryWorked) {
  23.             $result = $stmnt->fetchAll();
  24.         }
  25.     }
  26.     return $result;
  27. }

Pass it a goat id, and it will return an array of the clubs. It returns all of the data about the clubs, rather than just their ids.

So, to make the widget, we need to know two things:

  • Which clubs the goat is in. >>DONE<<
  • The names and ids of all the club.

The second one is easier.

  1. /**
  2.  * Get all clubs.
  3.  * @return null|array Club rows, null if something broke.
  4.  */
  5. function getAllClubs() {
  6.     global $dbConnection;
  7.     $result = null;
  8.     $sql = "select * from clubs order by name;";
  9.     $stmnt = $dbConnection->prepare($sql);
  10.     $isQueryWorked = $stmnt->execute();
  11.     if ($isQueryWorked) {
  12.         $result = $stmnt->fetchAll();
  13.     }
  14.     return $result;
  15. }

To make the widget, we need to know two things:

  • Which clubs the goat is in. >>DONE<<
  • The names and ids of all the club. >>DONE<<

Here's the first line of the function that makes the widget:

  • function makeClubMembershipWidget(array $goatClubMemberships, array $clubRows) {

The first param is the clubs the goat is in. The second is all of the club data. Here's what the debugger says about the first param, $goatClubMemberships:

Debugger

The goat is in two clubs, so there are two elements in the array. Each element gives the deets of a club:

Debugger

The second param of...

  • function makeClubMembershipWidget(array $goatClubMemberships, array $clubRows) {

... is $clubRows.

Debugger

Adela
Adela

It looks like $goatClubMemberships is a subset of $clubRows.

Aye! Good thinking, Adela.

Here's PHP to make the widget.

  1. /**
  2.  * Make HTML for club membership widget.
  3.  * @param array $goatClubMemberships The goat's memberships.
  4.  * 'club_id' element in each row has the id of the club the goat is in.
  5.  * @param array $clubRows All of the clubs.
  6.  * @return string HTML to render widget.
  7.  */
  8. function makeClubMembershipWidget(array $goatClubMemberships, array $clubRows) {
  9.     $memberOfClubIds= array_column($goatClubMemberships, 'club_id');
  10.     $result = "
  11.         <label>Club memberships<br>
  12.             <select name='club-memberships[]' multiple size='5'>\n";
  13.     foreach ($clubRows as $clubRow) {
  14.         $clubId = $clubRow['club_id'];
  15.         $clubName = $clubRow['name'];
  16.         $selected = '';
  17.         // Is the goat in the club?
  18.         if (in_array($clubId, $memberOfClubIds)) {
  19.             $selected = 'selected';
  20.         }
  21.         $result .= "<option value='$clubId' $selected>$clubName</option>\n";
  22.     }
  23.     $result .= "
  24.             </select>
  25.         </label>\n";
  26.     return $result;
  27. }

Line 9 has something new:

  • $memberOfClubIds= array_column($goatClubMemberships, 'club_id');

Remember that $goatClubMemberships is an array of arrays:

Debugger

There's one element in $goatClubMemberships for each club. Each of those elements has an element called club_id. Every club has to have an id, so the ids will be there for every record.

  • $memberOfClubIds= array_column($goatClubMemberships, 'club_id');

...extracts the club_ids for every element in $goatClubMemberships, and returns an array of them. Here's what the debugger sees:

$memberOfClubIds

So, $memberOfClubIds is an array of the ids the goat is a member of. That comes in handy later in the function.

Here's the loop that makes the option tags.

  1.     foreach ($clubRows as $clubRow) {
  2.         $clubId = $clubRow['club_id'];
  3.         $clubName = $clubRow['name'];
  4.         $selected = '';
  5.         // Is the goat in the club?
  6.         if (in_array($clubId, $memberOfClubIds)) {
  7.             $selected = 'selected';
  8.         }
  9.         $result .= "<option value='$clubId' $selected>$clubName</option>\n";
  10.     }

The loop runs across $clubRows. That's an array of all of the clubs. So the code in the loop is run for every club. Each time through the loop, $clubRow is the deets for one club.

By line 18, $clubId has the id of the club being processed. Line 18 is:

  • if (in_array($clubId, $memberOfClubIds)) {

in_array() returns true of the $clubId is in the array $memberOfClubIds. That's the ids of the clubs the goat is in, that we made earlier with array_column().

Georgina
Georgina

Hey, that's cool!

Aye, it is.