Adding a coating of civilization to PHP stdclass with the PGMObject base class

Previous posts in this series:

Bioinformatics has been notorious for the tension between public, supported access to data and algorithms and the often critical need to get under the hood. The ABI trace file and the Illumina output file system are good examples of data sources in which detailed internal access was necessary, but effective APIs did not exist and controlled, supported access was insufficient.
My approach to the data object portion of the API described below is to try to do some of both. I think I’ve found an accommodation of both structured access to the Torrent Server data objects and availability of the rawest form of data. If you know what you’re doing and are willing to take responsibility, take the raw data and go nuts. Otherwise, stick to the more managed parts of the API.
The Torrent Server RESTful API returns a JSON document that can be easily converted to a stdclass object via json_decode. If you’re not familiar with the stdclass, don’t feel bad; the only real documentation comes from third parties. It’s easy to understand, though; it’s what you get when you turn an associative array into an object. We’ll use this as the basis for the PHP API.
Here is an example of the JSON document returned by the previously described Torrent Server API, specifically for an instrument (“Rig” in Ion Torrent parlance):

JSON returned for an Instrument query

(Like some of the shots from other posts, this is a JSON document dressed up by a Chrome plugin.) This data structure is typical for API query results. If this document were run through PHPs json_decode, you’d get a stdclass that would allow you to do the following:

    $rigresult = json_decode($jsondoc);
        ...
    $rigresult->meta->limit == 20;
        ...
    $defaultrig = $rigresult->objects[0];
    $defaultrig->name == "default";

Easy, eh? We could probably just take these stdclass objects and convert them to MiniLIMS TypeInstances and we’d be done before lunch.
Yeah, you’re right. It’s a little too easy.
As I mentioned above, we’re going to try to provide some control and structure over the objects pulled from the server. This will not only help the general consumer of this PHP API, but it will also help the MiniLIMS system more clearly handle changes to the Torrent Server. Since you can’t add or change the methods of the stdclass, I’m going to use a wrapper object and call it PGMObject. From the PGMObject, more specific subclasses will be derived according to the Torrent Server types.
Before I describe the PGMObject, though, I’d like to use a subclass as an example of what I’d want to do. I’ll stick with the instrument data type since it’s relatively small.

PGM Instrument PHP class

There are 4 things I’m trying to accomplish here:

  1. PGMInstrument extends PGMObject, the class that will handle all of the common functions
  2. The properties array that is populated in the constructor lists the valid property names for this object so that we can check them.
  3. The required array allows me to check for required fields in the validation method (coming up shortly)
  4. The defaults array allows me to set defaults for a particular field.

This really is the essence of the distinction between one PGM object and another- a list of properties and the characteristics of those properties. How those properties and their characteristics are used can be defined in the base class for the most part by iterating through these arrays.
Below is a screenshot of the PGMObject interface (a collapsed object view from an Eclipse editor window).

PGMObject interface (collapsed object view in Eclipse)

First, I want to point out the constructor and the getJson/setJson methods. If you use the TorrentServer object (we’ll see that one soon) to retrieve a PGMObject from your Torrent Server instance, you can just grab the JSON document and do with it as you will. More often than not the object representations will be very handy, but, if you need low level access, it’s yours.
This JSON document represents a single one of the objects returned by the Torrent Server. A URL query typically returns an array of matching objects along with a {meta} document that can be used to support paging. The document maintained by these objects is a single data object with no associated meta.
If you don’t want to work with the raw JSON, you have two choices for interacting with the data values; either getPropertyValue/setPropertyValue or getters and setters for the individual properties.
The getPropertyValue/setPropertyValue method works just like the MiniLIMS methods of the same name; a property name and value are passed for setPropertyValue and just a property name for getPropertyValue. These are really handy for treating PGMObject subclasses generically and, particularly, for iterating through the full property list. If you wanted to write an HTML table you could do this:

$id  = "5";                           //An experiment id from the Torrent Server
$exp = $tserver->getExperiment($id);  //$tserver is a TorrentServer object
$html   = "<table><tr><th>Property</th><th>Value</th></tr>n";
foreach($exp->getProperties() as $prop){
  $html .= "<tr>";
  $html .= "<td>" . $prop . "</td>"; $html .= "<td>" . $exp->getPropertyValue($property) . "</td>";
  $html .= "</tr>n";
}
$html  .= "</table>n";

Property-specific getters and setters are nice to have for data objects like these. Popularized by Java, get/set methods for accessing object members allows for controlled access, but can be incredibly tedious to write for the most common case of simply getting or setting the attribute. The __call function is a special PHP object method that is called when an undefined function is called on an object. By having this function look for getters and setters that match the set of valid properties for the PGMObject, we can write one chunk of code to handle all of the routine get/set calls. Of course, if you want to do any special handling in a getter or setter (e.g. make something a computed value), you can define that function for your PGMObject subclass; if the method is explicitly defined, the __call function won’t get called.
Here is a chunk of __call where the parameter $name is the name of the function being called and $arguments is the array of argument names and values:

  public function __call($name,$arguments){
    if (!isset($this->properties)){
      throw new Exception("PGMObject cannot function without a properties list");
    }
    $matches = array();
    if (preg_match('/^get(.*)/',$name,$matches)){
      $prop = lcfirst($matches[1]);
      if (in_array($prop,$this->properties)){
        if (!isset($this->object)){
          if (isset($this->json)){
            $this->parseJson();
          }
          else {
            return "";
          }
        }
        return $this->object->$prop;
      }
    }
...
}

that allows you to do this:

$id  = "5";                           //An experiment id from the Torrent Server
$exp = $tserver->getExperiment($id);  //$tserver is a TorrentServer object
$chiptype   = $exp->getChipType();

without explicitly defining the getChipType() method.
The final “value-add” that we place on top of the PGMObject is the isValid() method. This is primarily to support objects that are being pushed to the Torrent Server (like a PlannedExperiment object). In the base class, this function goes through the $required array and makes sure that all of the properties listed have a value. If they do not, the method returns a null value and stores a message in the $validationErrors array.
This snippet shows how to use it:

$pexp = new PlannedExperiment();
$pexp->setId("5");
$pexp->setSample("Fish blood");
$pexp->setPlanPGM("default");
if (!$pexp->isValid()){
  print "Planned experiment is not valid for the following reason(s):<p/>n";
  print implode("<p/>",$pexp->getValidationErrors());
}
else {
  $tserver->putPlannedExperiment($pexp);
}

Long-winded though this was, I hope the essential point is clear. I’ve tried to accommodate both raw(ish) access to the Torrent Server data objects and an object-oriented structure that can support controlled access, while at the same time minimizing the amount of routine coding.
In the next post, we’ll examine the TorrentServer class, whose purpose is to provide access to a Torrent Server, much like a traditional database object broker.

Share:

Newsletter

Get updates from BioTeam in your inbox.