Let’s say I want to store dogs in a database table with each dog having its own subclass in PHP.
Basically I want to avoid storing/listing the subclass names in different places in the code. What would be a good OOP approach for that?
abstract class Dog {
protected $data;
public function __construct($data) {
$this->data = $data;
}
public function name() {
return $this->data["name"];
}
abstract public function breed();
}
class GermanShepherd extends Dog {
public function breed() {
return _("German Shepherd");
}
}
class BullDog extends Dog {
public function breed() {
return _("Bulldog");
}
}
Now I have this class that handles groups of objects (i.e. dogs):
class Dogs {
public static function getDogs() {
// ...
$ret = array();
while ($row = mysql_fetch_assoc()) {
switch ($row["type"]) { // I could do this using a lookup array
case "shepherd": $dog = "GermanShepherd"; break;
case "bulldog": $dog = "Bulldog"; break;
}
$ret[] = new $dog($row);
}
return $ret;
}
}
And I would like to use this class to get the dog types in my view (especially for an add dog form), instead of listing the class names:
?><form><select name="type"><?php
foreach (array("GermanShepherd", "Bulldog") as $dog) { // here I would like to do avoid listing the class names again
?><option value="<?=$dog ?>"><?php
$d = new $dog; // actually I can't instantiate the class here because I don't have any data at this point
echo $d->name();
?></option><?php
}
?></select></form><?php
I would like to incorporate this into the Dogs class, something along the lines of this:
class Dogs {
private static $dogs = array(
"shepherd" => "GermanShepherd",
"bulldog" => "Bulldog",
);
public static function getDogs() {
// ...
$ret = array();
while ($row = mysql_fetch_assoc()) {
$dog = self::$dogs[$row["type"]];
$ret[] = new $dog($row);
}
return $ret;
}
public static function getDogTypes() {
return array_values(self::$dogs);
}
}
?><form><select name="type"><?php
foreach (Dogs::getDogTypes() as $dog) {
?><option value="<?=$dog ?>"><?php
$d = new $dog; // here I still need to instantiate the class and I don't have any data to provide it with
echo $d->name();
?></option><?php
}
?></select></form><?php
This would somewhat work so far, but what if I need more class specific information, for example when I have more fields specific to a dog type?
foreach (Dogs::getDogTypes() as $dog) {
$d = new $dog; // instantiate again?
foreach ($d->formFields() as $f) { // I wouldn't do it like this, just putting this here for demonstrative purposes
echo $f;
}
}
I think part of the problem lies in the fact that I need to be able to use my classes with and without database data: Everything seems very reasonable when I have the data from the database table, but I also need the data when I generate the form when creating a new dog.
Thanks for your ideas!
First make use of Interfaces. This will show you that having more specific interfaces (different class methods and properties per subclass) will make you need to deal with them differently in concrete. So they will show you where the deficiencies are and will enable you to streamline your objects into something more re-useable.
As long as your objects are only storing some data, use a data transfer object instead – which is of any “type”. So you don’t need to deal with type. However, you can use
StdClassorArrayfor that as well if you want to keep it basic. The plus-side is: You don’t need to actually write that much code.In case it’s not sufficient (as it will be), only add the code when you need to. Should keep things more simple in the long run then. So start with a basic data transfer object class and build upon it.
So use the classes you write to separate concerns, not to interweave the concerns. Encapsulate what varies, so your code can actually benefit from your design.