Martyn Bissett


Tutorials | Property Object Class

This is a type of object I use frequently and its main purpose is to represent a real life object stored in a database. When I refer to real life object I may refer to a person, car, country, etc stored as row in a table.

The object has the ability to loads its field values, save changes, create a new entry as well as many other functions custom to that particular object type.

This object makes use of the database abstraction class, its recommended that you read that tutorial before this one to understand some of the techniques discussed in this one.

Below is an example of how a property object might be instantiated:

$Car = new Car($_GET['car_id']);

Below shows how the Car class will construct the object and retrieve field values:

class Car {
  private $_carId;
  private $_make;
  private $_model;
  
  private $_db;
  
  public function __construct($carId = null) {
    $this->_db = Database::getInstance();
    if(! is_null($carId)) {
      $this->_carId = $carId;
      $sql = "SELECT * FROM cars WHERE car_id = $carId";
      $result = $this->_db->Select($sql);
      foreach($result as $row) {
        $this->_make = $row['make'];
        $this->_model = $row['model'];
      }
    }
  }
  
  public function GetMake() {
    return $this->_make;
  }
  
  public function SetMake($value) {
    $this->_model = $value;
  }
}

What happens above is that when the class is instantiated, if an id has been passed it will fetch the field values for the object. I've included two accessor methods for the 'make' property although you would generally use some form of accessor methods for all properties available to the script. I recommend rather than using public properties encase we require any validation/authentication to be performed but also for lazy instantiation which is discussed later in this tutorial.

Save

Now that we have gathered the values, we may want to alter their values and before terminating our script - save changes. This is done via the Save() function of the property object.

public function Save() {
  if(is_null($this->_carId)) { // insert
    $arFV = array (
      'make' => $this->_make,
      'model' => $this->_model
    );
    $this->_db->Insert('cars', $arFV);
  } else { // update
    $arFV = array (
    'make' => $this->_make,
    'model' => $this->_model
    );
    $arCond = array(
    'car_id' => $this->_carId
    );
    $this->_db->Update('cars', $arFV, $arCond);
  }
}

One clever thing about the property object Save() method is that it knows when to insert a new entry and when to update an existing entry. In the above example we passed an id value to fetch a row from a table. The Save() function checks to see if an existing id has been passed and if so it knows to update a row, otherwise it will insert a new row. This is very useful when using forms to manipulate a database table so that the same method can be used when completing an add form as when completing an edit form - only difference is that one form (edit) an id is passed to the action script.

Delete

This function is used to delete the entry from the database. Below shows this method within the class:

public function Delete() {
  $arCond = array (
    'car_id' => $this->_carId
  );
  $this->_db->Delete('cars', $arCond);
}

.. and its use within a script:

$Car->Delete();

The main benefit of this is that if on deletion of this object we also need to tidy up other elements of our application then we can do so within this function. For example, by deleting this car from our records we may also want to delete service history from another table or maybe even add an note of this action to a user activity table – this can all be included within the Delete function() so all we need to do is call this function and everything else that needs doing is taken care of.

So far this class falls at this stage because in order to delete an entry we perform two queries, the initial SELECT during construction and the DELETE performed here. This isn't very efficient, however, as before we only had to perform the DELETE from the script using the id attribute. This will affect performance as we are performing more database queries than needed. Later we discuss lazy instantiated which allows us only to fetch row fields when required so whilst we only want to instantiate an object to delete it - no initial SELECT is performed during construction.

Lazy Instantiation

Lazy instantiation is a term I first heard of whilst reading Wrox Professional PHP5. Although my implementation of this is slightly different the main concept remains the same - by only loading field values during certain events. For example, we have seen that we don't need to load field values when we only want to delete an object because the id attribute is all we need, cant we just bypass loading when constructing and only load when required? Well that is the basis of lazy instantiation.

Below shows a slightly modified version of our Cars class:

class Car {
  public $_carId;
  private $_isLoaded;
  
  public $_make;
  public $_model;
  
  private $_db;
  
  public function __construct($carId = null) {
  $this->_db = Database::getInstance();
  
  if(! is_null($carId))
    $this->_carId = $carId;
  }
  
  private function _LoadObjectFields() {
    if(is_null($this->_carId)
      return false;
    
    if($this->_isLoaded)
      return true;
    
    $sql = “SELECT * FROM cars WHERE car_id = $carId”;
    $result = $this->_db->Select($sql);
    foreach($result as $row) {
      $this->_make = $row['make'];
      $this->_model = $row['model'];
    }
    
    return $this->_isLoaded = true;
  }
  .
  .
  .

So now, we only set the id attribute of the object during construction and bypass our initial SELECT query because we don't know yet if we need it. This will work better if we were to immediately call the Delete() method of the object because we are only performing a single query instead of two.

However, as we know there are cases of then we might need those field values as we wont have any values to return to the script if it requires these.

Below I have modified the accessor method GetMake() and SetMake() to use the new private _LoadObjectFields() method:

public function GetMake() {
  $this->_LoadObjectFields();
  
  return $this->_make;
}

public function SetMake($value) {
  $this->_LoadObjectFields();
  
  $this->_model = $value;
}

Best shown in the GetMake() method is that it will attempt to load field properties when this particular method is called (or any method that requires these fields). Remember, the SELECT query only happens once during _LoadObjectFields() because we use the isLoaded property to flag that the object has been loaded already and returns true.

Now it may seem strange to also load the field values when using SetMake() but this is quite important also. Say we were to do the following:

$Car = new Car($_GET['car_id']);
$Car->SetMake('Toyota');
echo $Car->GetMake();

What would happen would be that if we didn't use _LoadObjectFields() within the SetMake() method it would only set the value of make and the object would remain unloaded. When we then went to retrieve the make using GetMake() it would overwrite the value we had entered with the value stored in the database for that id. So, what I do is on every Set* action - load values prior to changing the property so that any future actions that require _LoadObjectFields() wont overwrite my changes. So long as you remember on every action within the object, if field values are required for that operation then use the _LoadObjectFields().