views:

675

answers:

1

I have the current basic structure for each domain object that I need to create:

class Model_Company extends LP_Model
{   
    protected static $_gatewayName = 'Model_Table_Company';
    protected static $_gateway;
    protected static $_class;

    public static function init()
    {
     if(self::$_gateway == null)
     {
      self::$_gateway = new self::$_gatewayName();
      self::$_class = get_class();
     }
    }

    public static function get() 
    {
     self::init();

     $param = func_get_arg(0);

     if($param instanceof Zend_Db_Table_Row_Abstract)
     {
      $row = $param;
     }
     elseif(is_numeric($param))
     {
      $row = self::$_gateway->find($param)->current();
     }

     return new self::$_class($row);
    }

    public static function getCollection()
    {
     self::init();

     $param = func_get_arg(0);

     if($param instanceof Zend_Db_Table_Rowset_Abstract)
     {
      $rowset = $param;
     }
     elseif(!$param)
     {
      $rowset = self::$_gateway->fetchAll();
     }

     $array = array ();  

     foreach ($rowset as $row)
     {
      $array[] = new self::$_class($row);
     }

     return $array;
    }
}

I initially tried to refactor the static methods into the parent LP_Model class only to learn finally what "late static binding" means in the php world.

I'm just wondering if anyone has suggestions on how to refactor this code so that I don't have to redeclare the same three functions in every domain object that I create?

+3  A: 

How about this:

<?php

abstract class Model_Abstract
{
    protected $_gatewayName = null;
    protected $_gateway = null;

    protected function _init()
    {
        $this->_gateway = new $this->_gatewayName();
    }

    protected function __construct($row = null)
    {
        $this->_init();
        if ($row) {
            $this->_data = $row;
        }
    }

    public static function getAbstract($class, $param)
    {
        $model = new $class();
        if($param instanceof Zend_Db_Table_Row_Abstract)
        {
                $row = $param;
        }
        elseif(is_numeric($param))
        {
                $row = $model->_gateway->find($param)->current();
        }

        return new $class($row);
    }

    public static function getAbstractCollection($class, $param = null)
    {
        $model = new $class();
        if($param instanceof Zend_Db_Table_Rowset_Abstract)
        {
                $rowset = $param;
        }
        elseif($param === null)
        {
                $rowset = $model->_gateway->fetchAll();
        }

        $array = array ();

        foreach ($rowset as $row)
        {
                $array[] = new $class($row);
        }

        return $array;
    }

    abstract public static function get($param);
    abstract public static function getCollection($param = null);
}

class Model_Company extends Model_Abstract
{
    protected $_gatewayName = 'Model_Table_Company';

    public static function get($param) {
        return self::getAbstract(__CLASS__, $param);
    }

    public static function getCollection($param = null) {
        return self::getAbstractCollection(__CLASS__, $param);
    }
}

class Model_Table_Company extends Zend_Db_Table_Abstract
{
    protected $_name = 'company';
}

$model = Model_Company::get(1);
print "Got an object of type ".get_class($model)."\n";

$models = Model_Company::getCollection();
print "Got ".count($models)." objects of type ".get_class($models[0])."\n";

?>

Unfortunately, to make the functions easy to call, you have to duplicate get() and getCollection() in each subclass. The other option is to call the function in the parent class:

$model = Model_Abstract::getAbstract('Model_Company', 1);
print "Got an object of type ".get_class($model)."\n";

$models = Model_Abstract::getAbstractCollection('Model_Company');
print "Got ".count($models)." objects of type ".get_class($models[0])."\n";

You can rename the base class and its function names if you want to go that route. But the point is that you must name the child class in one place or the other: either make a boilerplate function in the child class as in my first example, or else name the class in a string as in my second example.

Bill Karwin
Thanks once again Bill. I know that you mentioned late static binding in a previous answer, but it wasn't until I started refactoring the code that I realized what the implications were of that limitation. Looking at your first solution, it seems pretty intuitive though.
Noah Goodrich
And while inheritance among the static properties and methods would be the most elegant solution, at least now the get and getCollection methods in each class are limited to a single line of code as opposed to a lot of duplicated code.
Noah Goodrich