title: Getting an OOP PHP calendar app to update two tables at once
body: <p>I'm building a PHP calendar app for a photography group. They need to be able to assign multiple dates/times to a single event.</p>
  
  <p>As of now, I have an Events table and a Slots table (for time slots). Events only has id, title, and description, while Slots has id, start time, end time, and event_id as a foreign key.</p>
  
  <p>I've created an Event class and a Slot class, and have a form for updating events that should be able to update both tables on submit by triggering an $event->save() method, and then cycling through each of the slots and triggering a $slot->save() method for each.</p>
  
  <p>I've included the code below. When I submit the form, I'm getting back an error on the $slot->save() section, and I can't figure out why.</p>
  
  <p>UPDATE: Adding code for some methods.</p>
  
  <pre><code>        public static function find_by_id($id=0) {
              global $database;
              $result_array = self::find_by_sql("SELECT * FROM " . self::$table_name . " WHERE id={$id} LIMIT 1 ");
              return !empty($result_array) ? array_shift($result_array) : false;
          }
  
          public static function find_by_sql($sql="") {
              global $database;
              $result_set = $database-&gt;query($sql);
              $object_array = array();
              while ($row = $database-&gt;fetch_array($result_set)) {
                  $object_array[] = self::instantiate($row);
              }
              return $object_array;
          }
  
          private static function instantiate($record) {
              // Could check that $record exists and is an array
              // Simple, long-form approach: (if 100 cols, this would be huge)
              // PHP 5.3 LSB : $class_name = get_called_class();
              $object = new self;
              // $object-&gt;id      = $record['id'];
              // $object-&gt;username    = $record['username'];
              // $object-&gt;password    = $record['password'];
              // $object-&gt;first_name = $record['first_name'];
              // $object-&gt;last_name   = $record['last_name'];
  
              // More dynamic, short-form approach:
              foreach($record as $attribute=&gt;$value) {
                  if($object-&gt;has_attribute($attribute)) {
                      $object-&gt;$attribute = $value;
                  }
              }       
              return $object;
          }
  
          public function save() {
              // A new record won't have an id yet.
              return isset($this-&gt;id) ? $this-&gt;update() : $this-&gt;create();
          }
  
          protected function create() { // protected so you are forced to call save() method for safety (no duplicate creates)
              global $database;
              $attributes = $this-&gt;sanitized_attributes();
              $sql = "INSERT INTO ". self::$table_name ." (";
              $sql .= join(", ", array_keys($attributes));
              $sql .= ") VALUES ('";
              $sql .= join("', '", array_values($attributes));
              $sql .= "')";
              if($database-&gt;query($sql)) {
                  $this-&gt;id = $database-&gt;insert_id();
                  return true;
              } else {
                  return false;
              }
          }
  
          protected function update() {
              global $database;
              $attributes = $this-&gt;sanitized_attributes();
              $attribute_pairs = array();
              foreach ($attributes as $key =&gt; $value) {
                  $attribute_pairs[] = "{$key} = '{$value}'";
              }
              $sql = "UPDATE ". self::$table_name ." SET ";
              $sql .= join(", ", $attribute_pairs);
              $sql .= " WHERE id=". $database-&gt;escape_value($this-&gt;id);
              $database-&gt;query($sql);
              return ($database-&gt;affected_rows() == 1) ? true : false;
  
          }           
  </code></pre>
  
  <p>And now onto the original code:</p>
  
  <pre><code>&lt;?php
  require_once('../../includes/init.php');
  if (!$session-&gt;is_logged_in()) { redirect_to("login.php"); }
  $user = User::find_by_id($session-&gt;user_id);
  
  if (!isset($_GET['e'])) { redirect_to("calendar.php"); }
  else { $event_id = $_GET['e']; }
  
  $sql = "SELECT * FROM slots WHERE event_id = {$event_id}";
  
  // find_by_sql() method also instantiates each row as a new Slot object
  // $slots is now an array() of objects
  $slots = Slot::find_by_sql($sql);
  
  // new Slot object used for adding a time slot to current event
  $blank_slot = new Slot();
  
  // initialize $message var to ""
  $message="";
  
  // Check to see if either of the submit buttons have been set in the $_POST
  if (isset($_POST['edit_event']) || isset($_POST['add_slot'])) {
  
  // Counts the number of slots associated with the current event id
  // Add one so that there is a blank field to add another time slot
  $count = count($slots) + 1;
  
  // Vars for checking if the last m/d/y is set in the form, before adding another slot field
  $check_month = "month" . $count;
  $check_year = "year" . $count;
  $check_day = "day" . $count;
  
  // initialize running errors var
  $errors = 0;
  
  // Don't add another slot field if the previous one has not been set
  if (isset($_POST['add_slot']) &amp;&amp; (empty($_POST[$check_month]) || empty($_POST[$check_year]) || empty($_POST[$check_day]))) {
                  $message .= "Oops, you must enter a value for all months, dates, and years before adding a new date to this event.&lt;br /&gt;";
                  $errors++;
  
  } else {
      // create new Event object and set attrs from $_POST vars   
      $event = new Event();       
      $event-&gt;id = $event_id;
      $event-&gt;title = $_POST['title'];
      $event-&gt;description = $_POST['description'];
      $event-&gt;category = $_POST['cat'];
  
      $new_event = $event-&gt;save();
      if ($new_event = true) {
          $i = 1;
          // for each of the $slots objects...
          foreach ($slots as $slot) {
              // get the db_fields for the object
              $fields = Slot::get_db_fields();
              // for each field...
              foreach ($fields as $field) {
                  // create a $field_n var that appends $i, to match $_POST name
                  $field_n = $field . $i;
                  if ($field == 'id') {
                      $slot_id_n = "slot_id" . $i;
                      $slot-&gt;id = $_POST[$slot_id_n];
                  }
                  elseif ($field == 'start_hour') {
                      $start_ampm_n = "start_ampm" . $i;
                      $start_hour = Slot::convert_to_military($_POST[$field_n], $_POST[$start_ampm_n]);
                      $slot-&gt;start_hour = $start_hour;
                  } elseif ($field == 'end_hour') {
                      $end_ampm_n = "end_ampm" . $i;
                      $end_hour = Slot::convert_to_military($_POST[$field_n], $_POST[$end_ampm_n]);
                      $slot-&gt;end_hour = $end_hour;
                  } elseif ($field == 'start_ampm' || $field == 'end_ampm') {
                  } elseif ($field == 'event_id') {
                      $slot-&gt;event_id = $event_id;
                  } elseif ($field == 'status') {
                      $slot-&gt;status = "confirmed";
                  } else {
                      $slot-&gt;$field = $_POST[$field_n];
                  }
              }
  
              // save() method runs create() or update() method
              $save_slot = $slot-&gt;save();
              // save m and y for redirecting back to calendar
              $last_month = $slot-&gt;month;
              $last_year = $slot-&gt;year;
              if ($save_slot == false) {
  
   // THIS IS THE ERROR I AM ALWAYS GETTING
  
                  $message .= "(1) Problem saving time slot number {$i}.&lt;br /&gt;"; 
                  // rollback previous $slot and $event saves!
                  $errors++;
              }
  
          $i++;
          }
          // add the new time slot, if it exists
          if (!empty($_POST[$check_month]) &amp;&amp; !empty($_POST[$check_year]) &amp;&amp; !empty($_POST[$check_day])) {
              $fields = Slot::get_db_fields();
              foreach ($fields as $field) {
                  $field_n = $field . $i;
                  if ($field == 'id') {
                  $slot_id_n = "slot_id" . $i;
                      if(!empty($_POST[$slot_id_n])) {
                          $blank_slot-&gt;id = $_POST[$slot_id_n];
                      } else {
                          $blank_slot-&gt;id = NULL;
                      }
                  }
                  elseif ($field == 'start_hour') {
                      $start_ampm_n = "start_ampm" . $i;
                      $start_hour = Slot::convert_to_military($_POST[$field_n], $_POST[$start_ampm_n]);
                      $blank_slot-&gt;start_hour = $start_hour;
                  } elseif ($field == 'end_hour') {
                      $end_ampm_n = "end_ampm" . $i;
                      $end_hour = Slot::convert_to_military($_POST[$field_n], $_POST[$end_ampm_n]);
                      $blank_slot-&gt;end_hour = $end_hour;
                  } elseif ($field == 'start_ampm' || $field == 'end_ampm') {
                  } elseif ($field == 'event_id') {
                      $blank_slot-&gt;event_id = $event_id;
                  } elseif ($field == 'status') {
                      $blank_slot-&gt;status = "confirmed";
                  } else {
                      $blank_slot-&gt;$field = $_POST[$field_n];
                  }
              }
              $save_slot = $blank_slot-&gt;save();
              $last_month = $blank_slot-&gt;month;
              $last_year = $blank_slot-&gt;year;
              if ($save_slot == false) {
                  $message .= "Problem saving time slotb number {$i}.&lt;br /&gt;";
                  // rollback previous $slot and $event saves!
                  $errors++;
              }
          }       
      } else {
          $message .= "Error updating the event.&lt;br /&gt;";
          $errors++;
      }
  
      if ($errors == 0) {
          $redirect = "calendar.php";
          if (isset($last_month) &amp;&amp; isset($last_year)) {
              $redirect .= "?m={$last_month}&amp;y={$last_year}";
          }
          if (isset($_POST['edit_event'])) { redirect_to($redirect); }
      }
  }
  }
  $event = Event::find_by_id($event_id);
  ?&gt;
  
  &lt;?php include_layout_template('header.php'); ?&gt;
  &lt;div id="main"&gt; 
      &lt;h1&gt;Edit Event&lt;/h1&gt;
      &lt;?php if (!empty($message)) { echo "&lt;p class='message'&gt;" . $message . "&lt;/p&gt;"; } ?&gt;
      &lt;a href="calendar.php"&gt;Cancel (Go back to the Calendar)&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;
      &lt;form style="width: 700px;" action="?e=&lt;?php echo $event_id; ?&gt;" method="post"&gt;
      &lt;?php echo $event-&gt;build_event_form(); ?&gt;
      &lt;?php 
      $sql = "SELECT * FROM slots WHERE event_id = {$event_id}";
      $slots = Slot::find_by_sql($sql);
      ?&gt;
  &lt;br /&gt;&lt;br /&gt;
  &lt;?php
      $num = 1;
      foreach ($slots as $slot) {
          echo $slot-&gt;build_slot_form($num);
          $num++;
      }
      echo $blank_slot-&gt;build_slot_form($num);
  ?&gt;
      &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
      &lt;input type="hidden" name="date_count" value="&lt;?php echo $num; ?&gt;"&gt;
      &lt;input type="submit" name="edit_event" value="Edit Event" /&gt;
      &lt;input type="submit" name="add_slot" value="Add Another Date"&gt;
      &lt;/form&gt;
  
  &lt;/div&gt;
  
  &lt;?php include_layout_template('footer.php'); ?&gt;
  </code></pre>