/**
  * Saves all allocations to the db.
  * $mysqli : manual db connection (for transaction handling)
  * $bookingId : booking id for this allocation
  * Throws AllocationException if one or more allocations failed due to lack of availability
  */
 function save($mysqli, $bookingId)
 {
     // we need to delete any allocations that have been removed since we last saved
     $oldAllocationRows = AllocationDBO::fetchAllocationRowsForBookingId($bookingId, $this->resourceMap, false);
     // existing (possibly changed) allocations, keep in a array indexed by id
     $allocationRowsById = array();
     // indexed by id where id > 0; we need to update these rows if they have changed
     foreach ($this->allocationRows as $ar) {
         if ($ar->id > 0) {
             $allocationRowsById[$ar->id] = $ar;
         }
     }
     // diff existing records with the ones we want to save
     // if it exists in the old but not in the new, delete it
     error_log("allocation table.save() : " . var_export(array(array_keys($oldAllocationRows), array_keys($allocationRowsById)), true));
     $allocationRowsToRemove = array_diff_key($oldAllocationRows, $allocationRowsById);
     foreach ($allocationRowsToRemove as $allocId => $ar) {
         AllocationDBO::deleteAllocation($mysqli, $allocId);
     }
     $failedAllocation = false;
     foreach ($this->allocationRows as $alloc) {
         try {
             $alloc_id = $alloc->save($mysqli, $bookingId);
             $alloc->isAvailable = true;
         } catch (Exception $e) {
             error_log("Update allocation row failed with exception: " . $e);
             $alloc->isAvailable = false;
             $failedMessage = $e->getMessage();
         }
     }
     // report (the last) error if any of the rows failed to save
     if ($failedMessage) {
         throw new AllocationException($failedMessage);
     }
 }