[fw-general] Re: [fw-db] A common pattern for instantiation of alternative classes by an object.

2007-03-22 Thread Mark Gibson

Matthew Ratzloff wrote:

This would be a nice idea for a generalized component that would apply to
all classes, but unfortunately you can't do it.

For example, this doesn't work:

$object = new Zend_Delegate::getClassName('Zend_Example');

It has to be in two steps:

$class  = Zend_Delegate::getClassName('Zend_Example');
$object = new $class;

And it's downright impossible for static classes unless you use
call_user_func() or call_user_func_array():

$class = Zend_Delegate::getClassName('Zend_Example');
call_user_func(array($class, 'method')); // Works
$class::method(); // Syntax error


There was no intention of it being used for static classes.
It is purely a generalisation or a standard practice for classes
that instantiate objects, and allow the class of these new objects
to be changed. Zend_Db_Table_Abstract has these methods that
manage this:

 setRowClass();
 getRowClass();
 setRowsetClass();
 getRowsetClass();

Also, Zend_Db_Adapter_Abstract can currently generate Zend_Db_Profiler
and Zend_Db_Select objects, but provides no way of selecting alternative
implementations or subclasses of these.

My proposal is to provide a generic way of allowing any class that
has this behaviour to allow the user to set the class of these
objects before they are instantiated.

It's all about instantiation, so static access isn't an issue.

If we went along the global registration route,
using Zend_Db_Adapter_Abstract for example:

public function select()
{
return new Zend_Db_Select($this);
}

becomes:

public function select()
{
$class = Zend_Factory::getClass(__CLASS__, 'select');
return new $class();
}

Then we can register an alternative/subclass of Zend_Db_Select using
something like:

Zend_Factory::setClass('Zend_Db_Adapter_Abstract', 'select',
'MyAlternativeSelect');

then everytime we call $adapter->select(), an instance of
MyAlternativeSelect is returned rather than Zend_Db_Select.

This would eliminate the need for get/set*Class() style methods in
each class.

- Mark.


[fw-general] Re: [fw-db] A common pattern for instantiation of alternative classes by an object.

2007-03-21 Thread Matthew Ratzloff
Hi Mark,

This would be a nice idea for a generalized component that would apply to
all classes, but unfortunately you can't do it.

For example, this doesn't work:

$object = new Zend_Delegate::getClassName('Zend_Example');

It has to be in two steps:

$class  = Zend_Delegate::getClassName('Zend_Example');
$object = new $class;

And it's downright impossible for static classes unless you use
call_user_func() or call_user_func_array():

$class = Zend_Delegate::getClassName('Zend_Example');
call_user_func(array($class, 'method')); // Works
$class::method(); // Syntax error

Furthermore, because PHP doesn't have late static binding (in 5.2.1, at
least), you can't say "self::$property = $xyz".  You must use accessors
for all properties and have to go through call_user_func[_array]() to
ensure that the proper classes are being used.

In short, it would be great to have, but limitations in PHP itself prevent
it from being anything close to easy or efficient.

-Matt

On Wed, March 21, 2007 7:35 am, Mark Gibson wrote:
> As I mentioned in the response to 'Zend_Db_Select::distinct', I have
> an idea, here it is:
>
> A common pattern for classes that instantiate objects of another class,
> and wish to allow to user to declare an alternative implementation
> or subclass for the instantiated object.
>
> This provides a consistent interface for setting alternative classes,
> and a possibly a single implementation, reducing overall class sizes.
>
> I've demonstrated this using the Zend_Db_Table_Abstract class, as
> at present it allow you to select an alternative class for rows
> and rowsets. This pattern could be applied to any part of the
> Zend Framework that requires this behaviour.
>
> Ultimately this functionality could be factored into a base class
> and/or helper class.
>
> Example:
>
> class Zend_Db_Table_Abstract
> {
>  const ROW_CLASS = 'rowClass';
>  const ROWSET_CLASS = 'rowsetClass';
>
>  // Defines restrictions on the superclass of an object
>  protected $_roles = array(
>  self::ROW_CLASS=> true,
>  self::ROWSET_CLASS => 'Zend_Db_Table_Rowset'
>  );
>
>  // The default classes to be used
>  protected $_classes = array(
>  self::ROW_CLASS=> 'Zend_Db_Table_Row',
>  self::ROWSET_CLASS => 'Zend_Db_Table_Rowset'
>  );
>
>  public function setClass($role, $classname)
>  {
>  if (!isset($this->_roles[$role])) {
>  // throw exception - unknown role
>  }
>
>  // Ensure the class actually exists
>  Zend_Loader::loadClass($classname);
>
>  if (is_string($this->_roles[$role])) {
>  if (!is_subclass_of($classname, $this->_roles[$role])) {
>  // throw exception - the alternative class must subclass
>  // or implement the interface declared in $this->_roles
>  }
>  } else {
>  // $this->_roles[$role] can just be set to true instead of a
>  // string if strict inheritance is not a requirement
>
>  $this->_classes[$role] = $classname;
>  }
>  }
>
>  public function getClass($role)
>  {
>  return $this->_classes[$role];
>  }
>
>  public function instantiate($role)
>  {
>  $class = $this->getClass($role);
>  return new $class();
>
>  // If arguments need to be passed on instantiation, then
>  // just perform the above directly, with the added args.
>  }
> }
>
>
> Example usage:
>
> $table->setClass(Zend_Db_Table_Abstract::ROW_CLASS, 'MyTableRow');
>
> Additionally a global registry or configuration could be used to declare
> alternatives:
>
> Zend_???::setClass('Zend_Db_Table_Abstract', 'rowClass', 'MyTableRow');
>
> or declared in an ini file:
>
> [classes]
> Zend_Db_Table_Abstract.rowClass = MyTableRow
>
>
> Any thoughts?
> Is it worth turning into a proposal?
>
> - Mark.