Created
December 20, 2013 04:06
-
-
Save mbrowne/8050276 to your computer and use it in GitHub Desktop.
DCI reverse wrapper technique for PHP (for full implementation, see https://github.com/mbrowne/dci-php)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace DCI; | |
/** | |
* DCI Role base class | |
*/ | |
abstract class Role | |
{ | |
/** | |
* The data object playing this role (the RolePlayer). | |
* @var RolePlayerInterface | |
*/ | |
protected $data; | |
/** | |
* The context to which this role belongs | |
* @var Context | |
*/ | |
protected $context; | |
function __construct(RolePlayerInterface $data, Context $context) { | |
$this->data = $data; | |
$this->context = $context; | |
$context->_addToInternalRolePlayerArray($data); | |
} | |
/** | |
* Get a data property | |
*/ | |
function __get($propName) { | |
return $this->data->$propName; | |
} | |
/** | |
* Set a data property | |
*/ | |
function __set($propName, $val) { | |
$this->data->$propName = $val; | |
} | |
/** | |
* Returns whether or not the property is set on the data object. | |
* Note that for private/protected properties, this will always return false. | |
* A future version of this library may change that. | |
*/ | |
function __isset($propName) { | |
return isset($this->data->$propName); | |
} | |
/** | |
* Call a method on the data object. Data object methods should only be getters, setters, | |
* or simple data manipulation methods (or very simple calculation methods like a getName() | |
* method that returns a first and last name concatenated together). | |
* All other behavior should go in role methods, which are called normally and don't involve | |
* this __call function. | |
*/ | |
function __call($methodName, $args) { | |
//If the method is public | |
if (in_array($methodName, get_class_methods($this->data))) { | |
return call_user_func_array(array($this->data, $methodName), $args); | |
} | |
else { | |
throw new \BadMethodCallException( | |
"The method '$methodName' does not exist on the class '".get_class($this->data)."' | |
nor on any of the roles it's currently playing."); | |
} | |
return call_user_func_array(array($this->data, $methodName), $args); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace DCI; | |
/** | |
* DCI base RolePlayer trait | |
* All data/domain objects that could potentially be used as role players in | |
* a DCI context should use this trait, or inherit from a base class that uses this trait | |
* | |
* NOTE: In an ideal DCI implementation, it would be possible to override a data object method | |
* "foo" with a role method also named "foo". Unfortunately, the __call() magic method in | |
* PHP only gets called if a method isn't found, so the call $this->foo() will always go to the data | |
* object if there is a "foo" method defined there. So the names of role methods always need to be | |
* different from any existing methods on the data class. | |
*/ | |
trait RolePlayer | |
{ | |
/** | |
* An array of the methods currently being played by this object, | |
* indexed by the classname of the context. The method names are keys | |
* pointing to the role objects, e.g.: | |
* | |
* $roleMethods = array( | |
* 'MoneyTransferContext' => array( | |
* 'withdraw' => [SourceAccount role object] | |
* 'transferFrom' => [SourceAccount role object] | |
* 'deposit' => [Destinationaccount role object] | |
* ) | |
* ) | |
* | |
* The current struture obviously does not allow for more than one role in the same | |
* context with the same method name, but that feature is certainly possible | |
* and may be added in the future. | |
* | |
* @var array | |
*/ | |
private $roleMethods = array(); | |
/** | |
* The currently active DCI context | |
* @var DCI\Context | |
*/ | |
private $currentContext; | |
private $currentContextClassName; | |
/** | |
* Add a role to this object | |
* @param string $roleName | |
* @param Context $context | |
* @return \DCI\RolePlayer | |
*/ | |
function addRole($roleName, Context $context) { | |
$this->_setCurrentContext($context); | |
$roleClassName = $this->currentContextClassName.'\Roles\\'.$roleName; | |
if (!class_exists($roleClassName, false)) { | |
throw new \InvalidArgumentException("The role '$roleClassName' is not defined | |
(it should be defined in the same *file* as the context to which it belongs)." | |
); | |
} | |
$role = new $roleClassName($this, $context); | |
$this->bindRoleMethods($role); | |
return $this; | |
} | |
function removeRole($roleName, Context $context) { | |
$roleMethods = &$this->roleMethods[get_class($context)]; | |
if ($roleMethods) { | |
foreach ($roleMethods as $methodName => $role) { | |
if (preg_match('/\\'.$roleName.'$/i', get_class($role))) { | |
unset($roleMethods[$methodName]); | |
} | |
} | |
} | |
return $this; | |
} | |
protected function bindRoleMethods($role) { | |
//Add the role methods to the $this->roleMethods array | |
... | |
} | |
/** | |
* IMPORTANT! | |
* If subclasses implement __call(), they MUST call parent::__call() | |
* (either before or after their own __call() logic) or else role methods will not work! | |
*/ | |
function __call($methodName, $args) { | |
if (isset($this->roleMethods[$this->currentContextClassName][$methodName])) { | |
$role = $this->roleMethods[$this->currentContextClassName][$methodName]; | |
} | |
if (!isset($role) || !method_exists($role, $methodName)) { | |
//This throws \DCI\BadMethodCallException instead of just \BadMethodCallException for an important reason. | |
//See \BadMethodCallException for what that reason is. | |
throw new \DCI\BadMethodCallException( | |
"There is no public method '$methodName' on class '".get_class($this)."' nor on any of the roles it is currently playing ". | |
"(note that it might not be playing any roles, in which case this is just a regular bad method call). ". | |
"If the role belongs to a context that acts as a sub-context in another context, make sure that the parent context initialized the ". | |
'sub-context correctly, e.g. $this->fooContext = $this->initSubContext(new \UseCases\Foo($arg1, $arg2)).'); | |
} | |
//Call $role->$methodName() with the given arguments | |
return call_user_func_array(array($role, $methodName), $args); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment