Skip to content

Instantly share code, notes, and snippets.

@mbrowne
Last active December 14, 2015 07:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mbrowne/5047982 to your computer and use it in GitHub Desktop.
Save mbrowne/5047982 to your computer and use it in GitHub Desktop.
(Hacky) DCI in PHP 5.4, version 2
<?php
//SEE ALSO: https://gist.github.com/mbrowne/5562643 (works in PHP 5.3 too)
//and related discussion: https://groups.google.com/d/msg/object-composition/g4BMSdluuC8/yPR3-a1b2sMJ
ini_set('display_errors', 1);
trait AssignableToRole
{
protected $roles = array();
protected $methods = array();
protected $currentRole;
public function addRole($traitOrClassName) {
if (trait_exists($traitOrClassName)) {
$className = $traitOrClassName.'Class';
//We need a real class in order to be able to instantiate it,
//so we can pass the instance to getClosure() below
if (!class_exists($className)) {
eval('class '.$className.' {use '.$traitOrClassName.';}');
}
}
else {
$this->checkRoleDefined($traitOrClassName);
$className = $traitOrClassName;
}
//copy the methods from the role class
$tmp = new $className;
$reflClass = new ReflectionClass($tmp);
$reflMethods = $reflClass->getMethods();
foreach ($reflMethods as $method) {
$this->addMethod($traitOrClassName, $method->name, $method->getClosure($tmp));
}
return $this;
}
protected function addMethod($roleName, $methodName, $methodCallable)
{
if (!is_callable($methodCallable)) {
throw new InvalidArgumentException('The $methodCallable parameter must be callable');
}
$this->methods[$methodName][$roleName] = Closure::bind($methodCallable, $this, get_class());
$this->roles[$roleName]['methodNames'][] = $methodName;
}
public function asRole($traitOrClassName) {
$this->checkRoleDefined($traitOrClassName);
$this->checkRoleAssigned($traitOrClassName);
$this->currentRole = $traitOrClassName;
return $this;
}
public function removeRole($traitOrClassName) {
$this->checkRoleDefined($traitOrClassName);
try {
$this->checkRoleAssigned($traitOrClassName);
foreach ($this->roles[$traitOrClassName]['methodNames'] as $methodName) {
unset($this->methods[$methodName][$traitOrClassName]);
}
unset($this->roles[$traitOrClassName]);
}
catch (InvalidArgumentException $e) {
trigger_error($e->getMessage(), E_USER_NOTICE);
}
return $this;
}
protected function checkRoleDefined($traitOrClassName) {
if (!trait_exists($traitOrClassName) && !class_exists($traitOrClassName.'Class')) {
throw new InvalidArgumentException("Trait or class named '$traitOrClassName' is not defined");
}
}
protected function checkRoleAssigned($traitOrClassName) {
if (!array_key_exists($traitOrClassName, $this->roles)) {
throw new InvalidArgumentException("Trait or class named '$traitOrClassName' is not currently assigned to this instance");
}
}
public function __call($methodName, array $args)
{
if (isset($this->methods[$methodName])) {
if ($this->currentRole) {
$ret = call_user_func_array($this->methods[$methodName][$this->currentRole], $args);
$this->currentRole = null;
return $ret;
}
else {
$methods = $this->methods[$methodName];
if (count($methods) > 1) {
throw new RuntimeException(
"There is more than one role with the method '$methodName' currently assigned to this object.
Use the asRole() method to specify which role to use."
);
}
$method = current($methods);
call_user_func_array($method, $args);
}
}
else throw new RuntimeException("There is no method named '$methodName' to call");
}
}
class Person
{
use AssignableToRole;
}
trait EmployeeRole
{
function work() {
echo "I'm working on it...";
}
function goofOff() {
echo "Goofing off now even though I should be doing my job :)";
}
}
trait StudentRole
{
function goofOff() {
echo "Goofing off now even though I should be studying :)";
}
}
$person = new Person;
$person->addRole('EmployeeRole');
$person->addRole('StudentRole');
$person->work();
$person->asRole('EmployeeRole')->goofOff();
$person->asRole('StudentRole')->goofOff();
$person->removeRole('StudentRole');
//$person->asRole('StudentRole')->goofOff(); //would throw an exception
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment