The GenericObjectCollection Class

The GenericObjectCollection Class

There are two main cases for which you will have code that needs to generate a collection of entity objects, be they user objects, customer objects, order objects, or any object written using the GenericObject concept just discussed.

These two cases are the following:

  • You are generating a list of matching entities related to some other entity.

  • You are generating a list of matching objects by some arbitrary search criterion.

Good examples of each include:

  • Generating a collection of user objects that are members of a group administrators.

  • Generating a collection of user objects whose first name begins with the letter A.

The difference between the two is subtle, but important.

In the first case, a method of a foreign entity is clearly at work. A method called GetAllUsersWhoAreMembers would be appended to the Group class, which would somehow return a collection of users who are members of that group.

In the second case, your criterion is some simple parameter not readily expressed as another class. Sure, you could be intensely pedantic and invent a LetterOfTheAlphabet class, and give it a method called GetAllUsersWhoseFirstNameBeginsWith, but that would be OOP correctness run amok.

Instead, many developers opt for what is known as a Home class. This is a class whose purpose is solely to create other classes. For this reason it is sometimes called a Factory class.

In the previous example, you might well have a class called UserHome, which would have a method called GetAllUsersWithFirstNameBeginningWith that would take a single letter as its parameter. Usage would therefore be something along the lines of:

   $arUserCollection = Array();    $arUserCollection = UserHome::GetAllUsersWithFirstNameBeginningWith('A'); 

Note the instantiation of UserHome as a static class. There is no difference between any instance of UserHome, hence it is always used as a static class. Refer to Chapter 3, "Putting Objects to Work,'' if you need further convincing.

Let's look now at how you might be tempted to implement the previous example method. You'll see a bit later why this is not the best solution to the problem.

Traditional Implementation

There is nothing wrong, per se, with the following code:

   class UserHome {      public function GetAllUsersWithFirstNameBeginningWith($strLetter) {        $sql = new sql();        $strLetter = strtolower($strLetter);        $sql->query("SELECT id FROM \"user\" WHERE lower(first_name) LIKE    '$strLetter%'");       $result_rows = $sql->get_table_hash();       $arUserObjects = Array();       for ($i=0; $i<=sizeof($result_rows)-1; $i++) {         $arUserObjects[] = new User($result_rows[$i]["id"]);       };       return($arUserObjects);     }    }; 

Go ahead and try it. Save the file as userhome.phpm and try the following code:

   require("sql.phpm");    require("genericobject.phpm");    require("user.phpm");    require("userhome.phpm");    $arUsers = UserHome::GetAllUsersWithFirstNameBeginningWith('j');    print sizeof($arUsers); 

You should get a 2 on-screen if you used our test data from earlier in the chapter, which featured both John Doe and Jane Doe (hence two Js, resulting in the number 2 appearing).

You can use the previous style of method to implement any form of collection-generating requirement, be it as part of a Home class as demonstrated previously, or as part of a more complicated method comprising part of a foreign entity.

It does have a failing, however. The next section discusses what that is and how GenericObjectCollection may prove to be a more useful alternative.

Where the Traditional Implementation Fails

The way in which the approach described in the previous section fails is in SQL efficiency.

Consider the previous example. All we have done is ask for a collection of user objects to be returned. Say that the total was twenty, not two. Still, only one query would be needed the query used in our previous method.

However, what if we want to do something useful with the twenty users returned, such as output their full names to a list?

Consider the following code extract:

   $arUsers = UserHome::GetAllUsersWithFirstNameBeginningWith('j');    for ($i=0; $i<=sizeof($arUsers)-1; $i++) {      print $arUsers[$i]->GetField("first_name")."".            $arUsers[$i]->GetField("last_name") . "<br />\n";    }; 

Sure, it works, but think about each iteration of your loop. You are causing GenericObject to call its Load() method to populate the fields of every single object.

If you had twenty matching users, you'd perform twenty-one queries one query to find all the matching users and twenty to get their details:

   SELECT id FROM "user" WHERE first_name LIKE 'j%';    SELECT * FROM "user" WHERE id = 12091;    SELECT * FROM "user" WHERE id = 12092;    SELECT * FROM "user" WHERE id = 12093; 

Not exactly efficient. As you can surely see, only one query is required:

   SELECT * FROM "user" WHERE first_name LIKE 'j%'; 

Is there a more efficient way to achieve the data set of the preceding set of queries, minimizing SQL while still allowing you to remain as OO compliant as possible?

There is, and it's called GenericObjectCollection.

Principles Behind GenericObjectCollection

The reason we were encountering a twenty-one query marathon previously was quite simple. The first query returned a set of empty, unpopulated objects, each with its id identifier set but nothing else. By treating them all individually as part of a loop, you must perform individual queries; but what if you treated them as a set and populated them all simultaneously?

Say that your original query informs you that the following ten rows match; those with id values as follows:

   12091, 12092, 12093, 12094, 12095, 12096, 12097, 12098, 12099, 12100 

It is perfectly feasible to execute a single bulk population query as follows:

   SELECT * FROM "user" WHERE id IN (12091, 12092, 12093, 12094, 12095,    12096, 12097, 12098, 12099, 12100) 

Okay, this approach is not quite as efficient as doing it one query because, counting our original filter query, this uses two, but it's a darned sight better than the eleven it would be using the traditional approach described earlier.

GenericObjectCollection uses this bulk population query to great effect. It allows you to find the id values of matching rows in the same way as the traditional approach but then invites you to push those id values onto a stack. By your having told it what class name and database table names these id values represent, it will then create an array of instantiated objects for you, and, crucially, populate them for you, too. It will set the values for you and force them to be set as loaded (see earlier in the chapter), hence negating any need for them to repopulate when they are called again.

The following section shows the code.

Meet the Code

Following is the code in the file genericobjectcollection.phpm. Shortly, we present a better version of the UserHome class that can make practical use of this code.

   <?      class GenericObjectCollection {        # Member Variables        var $table_name;        var $class_name;        var $items_per_page;        var $item_count = 0;        var $id_array;        var $obj_array;        function __construct($table_name, $class_name) {          $this->table_name = $table_name;          $this->class_name = $class_name;        }        function AddTuple($id) {          if (!$this->id_array) {            $this->id_array = array();          };          array_push($this->id_array, $id);          $this->item_count = sizeof($this->id_array);        }        function SetPageSize($items_per_page) {          $this->items_per_page = $items_per_page;        }        function GetItemCount() {          return $this->item_count;        }        function GetNumPages() {          return(ceil($this->item_count / $this->items_per_page));        }        function _GetCommaSeparatedIDList($start_lim = 0, $end_lim = -1) {          $s = "";          if ($end_lim == -1) {            $end_lim = sizeof($this->id_array)-1;          };          for ($i=$start_lim; $i<=$end_lim; $i++) {            if (is_numeric($this->id_array[$i])) {              $s = $s . $this->id_array[$i] . ",";            };          };          $s = substr($s, 0, strlen($s) - 1);          return $s;        }        function _GetIndexFromTupleID($tuple_id) {          $index = -1;          for ($i=0; $i<=sizeof($this->id_array)-1; $i++) {            if ($this->id_array[$i] == $tuple_id) {              $index = $i;            };          };          return $index;        }        function PopulateObjectArray($page_num = 0) {          $items_per_page = $this->items_per_page;          if ($this->item_count > 0) {            if ($page_num > 0) {              $start_lim = ($items_per_page * ($page_num - 1));              $end_lim = ($start_lim + $items_per_page) - 1;              if ($end_lim > ($this->item_count-1)) {                $end_lim = $this->item_count - 1;              };              $stmt = "SELECT * FROM \"" . $this->table_name . "\" WHERE id IN (" .    $this->_GetCommaSeparatedIDList($start_lim, $end_lim). ")";            } else {              $stmt = "SELECT * FROM \"" . $this->table_name . "\" WHERE id IN (" .    $this->_GetCommaSeparatedIDList(). ")";            };            # Perform SQL query            $sql = new sql(0);            $sql->query($stmt);            $result_rows = $sql->get_table_hash();            for ($i=0; $i<=sizeof($result_rows)-1; $i++) {              $this_row = $result_rows[$i];              $this_db_row_id = $this_row["id"];              $this_index = $this->_GetIndexFromTupleID($this_db_row_id);              if ($this_index >= 0) {                $refObjArrayIndexObj = &$this->obj_array[$this_index];                $s="\$refObjArrayIndexObj = new " . $this->class_name . "(" .    $this_db_row_id . ");";                eval($s);                $refObjArrayIndexObj->ForceLoaded();                foreach ($this_row as $key => $value) {                  if (!(is_numeric($key))) {                    $refObjArrayIndexObj->SetField($key, $value);                  };                };              };            };          };        }         function RetrievePopulatedObjects($page_num = 0) {           if ($page_num > 0) {             $items_per_page = $this->items_per_page;             # Calculate start and end limits from page number.             $start_lim = ($items_per_page * ($page_num - 1));             $end_lim = ($start_lim + $items_per_page) - 1;               if ($end_lim > ($this->item_count-1)) {                 $end_lim = $this->item_count - 1;               };           } else {             $start_lim = 0;             $end_lim = $this->item_count - 1;           };           $return_array = array();           $counter = 0;           for ($i=$start_lim; $i<=$end_lim; $i++) {             $return_array[$counter] = $this->obj_array[$i];             $counter++;           };           return($return_array);         }       }    ?> 

Professional PHP5 (Programmer to Programmer Series)
Professional PHP5 (Programmer to Programmer Series)
Year: 2003
Pages: 182
BUY ON AMAZON © 2008-2017.
If you may any questions please contact us: