Using PHP classes to group data in memory

Beverley Portlock
CERTIFIED EXPERT
Published:

Purpose


When attempting to explain Object Oriented Programming (OOP) to people it is sometimes hard to explain the concepts in a way that is simple, but not so absurdly that it involves writing example code to manipulate bananas, apples or the colour blue.

So here is a simple, real world example of how OOP can make your programming life a lot easier. It involves a very common task - collecting data, reorganizing it, selecting parts of it and then using it on a webpage. I will also show how the use of OOP allows simple expandability of the code in a way that makes maintenance easy. This code is based on a real question from the Experts Exchange PHP section and I am grateful to EE member maccaj51 who agreed to let me use the questions as the basis for this article.


The problem to be solved


The brief is to accept data from an RSS feed sort it and select some of the data. The program code will then be extended to work with a number of feeds

Let us begin by looking at the RSS feed in question. The structure of the bit we are interested in looks like this.

<item>
                      <title>Ar&#xEA;ches - Beaufort</title>
                      <description>Closed  / Past 48 Hours: 0 cm / Primary: Hard Packed / Base Depth: 170 cm</description>
                      <guid isPermaLink="true">http://www.onthesnow.co.uk/france/areches-beaufort/skireport.html?XE_AFF=rss</guid>
                      <pubDate>Fri,  4 Jun 2010 04:47:05 -0400</pubDate>
                      <ots:open_staus>Closed</ots:open_staus>
                      <ots:resort_id>2658</ots:resort_id>
                      <ots:base_depth>170</ots:base_depth>
                      <ots:snowfall_48hr>0</ots:snowfall_48hr>
                      <ots:region_name>France</ots:region_name>
                      <ots:surface_condition>Hard Packed</ots:surface_condition>
                      <ots:base_depth_metric>cm</ots:base_depth_metric>
                      <ots:snowfall_48hr_metric>cm</ots:snowfall_48hr_metric>
                      <ots:resort_rss_link>http://www.onthesnow.co.uk/france/areches-beaufort/snow.rss</ots:resort_rss_link>
                      </item>
                      

Open in new window


and the information we want to collect is

- The title
- The snow depth
- The date


Storing the data


Rather than storing this in three arrays and passing them round everywhere, I propose that you should group the data in a simple PHP class which has little or no other purpose than simply grouping the data. I will not be applying the normal rules of data access and making class data "protected" or "private". So, we might start with this:

class SnowData {
                      
                           public $title;
                           public $description;
                           public $pubDate;
                      
                           // Constructor
                           //
                           function __construct( $t, $d, $p ) {
                                $this->title = $t;
                                $this->description = $d;
                                $this->pubDate = $p;
                           }
                      
                      }
                      

Open in new window


The constructor simply allows me to create the object with all 3 pieces of data in one statement. However the date is in an "awkward" format and it would suit me better if it was in a simpler format. Therefore I'll use the constructor to feed the date into strtotime()


class SnowData {
                      
                           public $title;
                           public $description;
                           public $pubDate;
                      
                           // Constructor
                           //
                           function __construct( $t, $d, $p ) {
                                $this->title = $t;
                                $this->description = $d;
                                $this->pubDate = strtotime($p);
                           }
                      
                      }
                      

Open in new window



From the XML it is also obvious that getting the depth out will be a pain so I will add a small member function to the class to allow me to use a RegEx to pull the data


class SnowData {
                      
                           public $title;
                           public $description;
                           public $pubDate;
                      
                           // Constructor
                           //
                           function __construct( $t, $d, $p ) {
                                $this->title = $t;
                                $this->description = $d;
                                $this->pubDate = strtotime($p);
                           }
                          
                           // Pull the base depth using a regex
                           //
                           function getDepth() {
                                preg_match('~^.*Base Depth:\s+([0-9]+)\s+cm\s*$~', $this->description, $matches );
                                return $matches[1];
                           }
                      
                      }
                      

Open in new window



Collecting the data


So we are now ready to go. We have somewhere that stores and formats the data and makes it easy to use. Now all we need to do is collect it. We will use the simpleXML object to collect the data and then we will break each entry down and store it as an array of SnowData objects

   
     // Load the XML
                           //
                           $xml = simplexml_load_file("http://......rss");
                      
                      
                           // Array to collect the data
                           //
                           $arr = array();
                      
                      
                           // Process the XML
                           //
                           foreach( $xml->channel->item as $anEntry ) {
                                $arr [] = new SnowData(
                                                         (string) $anEntry->title,
                                                         (string) $anEntry->description,
                                                         (string) $anEntry->pubDate
                                                      );
                           }
                      

Open in new window



Now we have an array of SnowData objects which we want to sort into descending depth of snow, since our skiers will want the deepest stuff. We can use PHP's uasort with a custom sort routine like so

     // A custom sort based on snow depth
                           //
                           function snowDepthSort( $a, $b ) {
                      
                                $aDepth = $a->getDepth();
                                $bDepth = $b->getDepth();
                      
                                if ( $aDepth < $bDepth )
                                     return 1;
                                else
                                     if ( $aDepth > $bDepth )
                                          return -1;
                      
                                return 0;                    
                           }
                      

Open in new window



Note how simple it is to get the depth. The class is doing all the hard work here. Now all we want is the best 10 results so we use array_slice to get the top 10

     $arr = array_slice( $arr, 0, 10 );

And we are done. We now have an array of the top 10 depths all ready for output
     

     // Produce a table
                           //
                           $tab = "<table>\n";
                      
                           foreach( $arr as $aSnowObj ) {
                                $tab .= "<tr>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->title . "(" . date("d/m/Y H:i", $aSnowObj->pubDate) . ")";
                                $tab .=    "</td>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->getDepth();
                                $tab .=    "</td>\n";
                                $tab .= "</tr>\n";
                                
                           }
                           
                           $tab .= "</table>\n";
                      
                           echo $tab;
                           
                      

Open in new window



Extending the code to handle more data


Let us now extend the class to add a simple text description, the location or country code. All we need to do is extend the class to have a location field and extend the constructor to have an extra parameter.

class SnowData {
                      
                           public $location;
                           public $title;
                           public $description;
                           public $pubDate;
                      
                           // Constructor
                           //
                           function __construct( $l, $t, $d, $p ) {
                                $this->location = $l;
                                $this->title = $t;
                                $this->description = $d;
                                $this->pubDate = strtotime($p);
                           }
                          
                           // Pull the base depth using a regex
                           //
                           function getDepth() {
                                preg_match('~^.*Base Depth:\s+([0-9]+)\s+cm\s*$~', $this->description, $matches );
                                return $matches[1];
                           }
                      
                      }
                      

Open in new window



We then update the collection loop

     
                           // Process the XML
                           //
                           foreach( $xml->channel->item as $anEntry ) {
                                $arr [] = new SnowData(
                                                         "France",
                                                         (string) $anEntry->title,
                                                         (string) $anEntry->description,
                                                         (string) $anEntry->pubDate
                                                      );
                           }
                      

Open in new window


and then amend the output table

     // Produce a table
                           //
                           $tab = "<table border='1'>\n";
                      
                           foreach( $arr as $aSnowObj ) {
                                $tab .= "<tr>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->location;
                                $tab .=    "</td>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->title . "(" . date("d/m/Y H:i", $aSnowObj->pubDate) . ")";
                                $tab .=    "</td>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->getDepth();
                                $tab .=    "</td>\n";
                                $tab .= "</tr>\n";
                                
                           }
                           
                           $tab .= "</table>\n";
                      
                           echo $tab;
                      

Open in new window


How simple was that?

Collecting from multiple feeds


Let us now go for a more substantial alteration. To collect the data from THREE separate RSS feeds and select only the top ten results. To achieve this all we need to do is to make the collection process a function that accepts a result array BY REFERENCE so that we can pass the alterations straight back out again. We get this:

     function collectRSS( $location, $feedUrl, & $arr ) {
                           
                                // Load the XML
                                //
                                $xml = simplexml_load_file( $feedUrl );
                      
                                // Process the XML
                                //
                                foreach( $xml->channel->item as $anEntry ) {
                                     $arr [] = new SnowData(
                                                              $location,
                                                              (string) $anEntry->title,
                                                              (string) $anEntry->description,
                                                              (string) $anEntry->pubDate
                                                         );
                                }
                      
                      
                           }
                      

Open in new window



which we can call like this

     $arr = array();
                      
                           collectRSS( "Austria", "http://www.....rss",  $arr );
                           collectRSS( "Austria", "http://www........rss", $arr );
                           collectRSS( "France", "http://www....rss",  $arr );
                      

Open in new window



And nothing else need change. Our final code looks like

class SnowData {
                      
                           public $location;
                           public $title;
                           public $description;
                           public $pubDate;
                      
                           // Constructor
                           //
                           function __construct( $l, $t, $d, $p ) {
                                $this->location = $l;
                                $this->title = $t;
                                $this->description = $d;
                                $this->pubDate = strtotime($p);
                           }
                          
                           // Pull the base depth using a regex
                           //
                           function getDepth() {
                                preg_match('~^.*Base Depth:\s+([0-9]+)\s+cm\s*$~', $this->description, $matches );
                                return $matches[1];
                           }
                      
                      }
                      
                           
                      
                      
                           // A custom sort based on snow depth
                           //
                           function snowDepthSort( $a, $b ) {
                      
                                $aDepth = $a->getDepth();
                                $bDepth = $b->getDepth();
                      
                                if ( $aDepth < $bDepth )
                                     return 1;
                                else
                                     if ( $aDepth > $bDepth )
                                          return -1;
                      
                                return 0;                    
                           }
                      
                      
                      
                      
                      
                      
                           function collectRSS( $location, $feedUrl, & $arr ) {
                           
                                // Load the XML
                                //
                                $xml = simplexml_load_file( $feedUrl );
                      
                                // Process the XML
                                //
                                foreach( $xml->channel->item as $anEntry ) {
                                     $arr [] = new SnowData(
                                                              $location,
                                                              (string) $anEntry->title,
                                                              (string) $anEntry->description,
                                                              (string) $anEntry->pubDate
                                                         );
                                }
                      
                      
                           }
                      
                      
                          
                           
                      
                           // Array to collect the data
                           //
                           $arr = array();
                      
                      
                           // Collect all the data
                           //
                           collectRSS( "Austria", "http://www......rss",  $arr );
                           collectRSS( "Italy", "http://www.....snow.rss", $arr );
                           collectRSS( "France", "http://www....snow.rss",  $arr );
                      
                      
                      
                           // Sort the array into descending snowdepth
                           //
                           uasort( $arr, 'snowDepthSort' );
                           $arr = array_slice( $arr, 0, 10);
                      
                      
                           // Produce a table
                           //
                           $tab = "<table border='1'>\n";
                      
                           foreach( $arr as $index => $aSnowObj ) {
                      
                                $tab .= "<tr>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->location;
                                $tab .=    "</td>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->title . "(" . date("d/m/Y H:i", $aSnowObj->pubDate) . ")";
                                $tab .=    "</td>\n";
                                $tab .=    "<td>\n";
                                $tab .=        $aSnowObj->getDepth();
                                $tab .=    "</td>\n";
                                $tab .= "</tr>\n";
                           }
                           
                           $tab .= "</table>\n";
                      
                           echo $tab;
                      

Open in new window



Summary


By grouping the data items into a PHP class, we make the maintenance and addition of further data items a trivial matter. We can even add methods to the class to tease out difficult data, but the real purpose of the class is to GROUP data to make parameter passing trivial.

By combining the class object with an array we can build up a list of data for further processing simply by passing into functions by reference. This allows us to construct functions which are completely focused on one task which results in simpler, more reliable code. Passing the array by reference also minimizes data copying and memory utilization as well. A final benefit is that the collection , processing and presentation can be split into completely independent tasks reducing interdependence between sections of code.

To collect new data items we need merely extend the class, the collection loop and what we show in the output table. No other parameters or control-flow control needs changing. If we wanted a different sort we could simply change the sort function. All the main functions are clear and are isolated from each other. Changing one presents a minimal impact on the others.

I have found this overall structure of an array of data objects, very flexible in use and very quick at producing reliable results. It encourages clear thinking at design time and although people may be put off by the apparent extra work ("You mean I have to code up a whole class? No way....") the benefits soon become apparent. This programming model is both flexible and straightforward.


Acknowledgments


My thanks to maccaj51 for allowing me to use the questions below as a basis for this article. The original questions are

https://www.experts-exchange.com/questions/26232550/sorting-external-XML-RSS-feeds-into-table.html
https://www.experts-exchange.com/questions/26235708/I-need-to-update-this-xml-php-script-with-new-features.html

4
3,723 Views
Beverley Portlock
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.