Last active
December 21, 2015 07:29
-
-
Save leapingbytes/6271917 to your computer and use it in GitHub Desktop.
Base classes for OO-Drupal 7 - DOO7 See the corresponding blog post at https://www.leapingbytes.com/blog/doo7_you_can_teach_old_dog_new_tricks
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 | |
/** @file | |
* Base classes for OO-Drupal 7 - DOO7 :) | |
* | |
* @author atchijov | |
* | |
*/ | |
/** | |
* Poor man mixins. | |
* Example: | |
* | |
* class A extends DOO7Mixin { | |
* public function a_method() { | |
* print_r( $this->master ); | |
* } | |
* } | |
* | |
* class B extends DOO7Object { | |
* public function __construct() { | |
* $this->mixin( 'A' ); | |
* } | |
* } | |
* | |
* $b = new B(); | |
* | |
* // This will call A::a_method on on-demand created object of class A | |
* $b->a_method(); | |
* | |
*/ | |
class DOO7Object { | |
var $mixins = array(); | |
/** | |
* "magic" method __call. @see http://us3.php.net/manual/en/language.oop5.overloading.php#object.call | |
* | |
* @param string $method | |
* @param array $args | |
* @return mixed | |
* @throws Exception | |
*/ | |
public function __call($method, $args) { | |
foreach ($this->mixins as $idx => $a_mixin) { | |
if (method_exists($a_mixin, $method)) { | |
return call_user_func_array(array($this->mixins[$idx], $method), $args); | |
} | |
} | |
throw new Exception('No Such Method : ' . $method ); | |
} | |
/** | |
* "magic" method __get. @see http://us3.php.net/manual/en/language.oop5.overloading.php#object.get | |
* | |
* @param string $name | |
* TODO: We may want to kill properties mixins. I have uneasy feeling about them. | |
*/ | |
public function __get($name) { | |
foreach ($this->mixins as $idx => $a_mixin) { | |
if (isset($this->mixins[$idx]->$name)) { | |
return $this->mixins[$idx]->$name; | |
} | |
} | |
} | |
/** | |
* Add mixin class(es) to the caller. | |
* | |
* @param string/array mixin(s) | |
* | |
* @example | |
* public function __construct() { | |
* $this->mixin('SuperMixinClass'); | |
* $this->mixin(array('OneGoodMixinClass', 'AnotherGoodMixinClass')); | |
* | |
* } | |
*/ | |
public function mixin() { | |
$class_names = func_get_args(); | |
if (count($class_names) == 1 && is_array($class_names[0])) { | |
$class_names = $class_names[0]; | |
} | |
foreach ($class_names as $class_name) { | |
$new_mixin = new $class_name($this); | |
foreach ($this->mixins as $mixin_class_name => $mixin ) { | |
// do not add same class twice | |
if ($mixin_class_name == $class_name) { | |
$new_mixin = FALSE; | |
break; | |
} | |
// if we have superclass in mixins, replace it with $new_mixin | |
else if (is_subclass_of($new_mixin, $mixin_class_name)) { | |
unset($this->mixins[$mixin_class_name]); | |
break; | |
} | |
// if we have subclass in mixins, ignore $new_mixin | |
else if( is_subclass_of($this->mixins[$mixin_class_name], $class_name)) { | |
$new_mixin = FALSE; | |
break; | |
} | |
} | |
if ($new_mixin) { | |
$this->mixins[$class_name] = $new_mixin; | |
} | |
} | |
} | |
/** | |
* Return an object providing mixin for given class. | |
* | |
* @param string $class_name | |
* @return stdClass | |
* @throws Exception | |
*/ | |
public function mixinThis($class_name) { | |
if (isset($this->mixins[$class_name])) { | |
return $this->mixins[$class_name]; | |
} | |
throw new Exception('No Such Mixin : ' . $class_name ); | |
} | |
} | |
/** | |
* Base class for all mxins. | |
* | |
* @author atchijov | |
* | |
*/ | |
class DOO7Mixin { | |
protected $master; | |
public function __construct($master) { | |
$this->master = $master; | |
} | |
/* | |
* This will allow us to use "mater's" methods in mixin code. | |
*/ | |
public function __call($method, $args) { | |
if (method_exists($this->master, $method)) { | |
return call_user_func_array(array($this->master, $method), $args); | |
} | |
throw new Exception('No Such Method : ' . $method ); | |
} | |
public function master() { | |
return $this->master; | |
} | |
} | |
/** | |
* This mixin class implements methods which allow automagically create define proxy functions. Such functions | |
* provide "bridge" between class methods and Drupal in situations when you have to use | |
* real PHP function (instead of "callable"). | |
* | |
* @author atchijov | |
* | |
*/ | |
class DOO7Proxy extends DOO7Mixin { | |
// NOTE: Because this class has own implemetation of functionNamePrefix, | |
// we can not use $this->master->functionNamePrefix(). If master does | |
// not provide method implementatino we will endup with Exception | |
/** | |
* Returns string which will be used as a prefix for proxy function. If master | |
* implements functionNamePrefix method, than result of this method will be used. | |
* Otherwise, master class name will be used. | |
* | |
* @return mixed|string | |
*/ | |
function functionNamePrefix() { | |
if (method_exists($this->master,'functionNamePrefix')) { | |
return call_user_func(array($this->master,'functionNamePrefix')); | |
} | |
else { | |
return get_class($this->master); | |
} | |
} | |
/** | |
* This method return PHP snipped which could be used to locate "target" object in proxy function. | |
* Master has to implement this method. See examples of this method implementation in DOO7Module and DOO7Singleton. | |
* | |
* @return string PHP snipet | |
* | |
* @throws Exception | |
*/ | |
public function instanceLocator() { | |
if (method_exists($this->master,'instanceLocator')) { | |
return call_user_func(array($this->master,'instanceLocator')); | |
} | |
else { | |
throw new Exception('DOO7Proxy::instanceLocator has to be implemented by master class'); | |
} | |
} | |
/** | |
* Simplest way to declare proxy function. Use result as Drupal 'callback'. | |
* | |
* NOTE: In some cases (especially when you defined callbacks for menus) it is | |
* nessesary to have proxy function defined very early in the process of Drupal bootstrap. | |
* In most situations, you can achieve this by using declareCallback in your class constructor | |
* (in addition to using it where ever the callback is needed). | |
* | |
* @param string $method_name | |
* @return string proxy function name | |
* | |
* @example | |
* function __constructor() { | |
* ... | |
* $this->declareCallback('createPage'), | |
* ... | |
* } | |
* | |
* function hook_menu() { | |
* ... | |
* $menu['foo/bar'] = array( | |
* ... | |
* 'page callback' => $this->declareCallback('createPage'), | |
* ... | |
* ); | |
* ... | |
* } | |
*/ | |
public function declareCallback($method_name) { | |
$function_name = $this->functionNamePrefix() . '_' . $method_name . '_callback'; | |
return $this->declare_proxy_function($method_name, $function_name); | |
} | |
/** | |
* Main magic souce. You will not neet to use it direction. | |
* | |
* @param string $method_name | |
* @param string $function_name | |
* @param int $context_token | |
* @param array $function_parameters | |
* @param array $method_parameters | |
* @return string | |
*/ | |
function declare_proxy_function( $method_name, $function_name = FALSE, $context_token = FALSE, $function_parameters = FALSE, $method_parameters = FALSE) { | |
$reflection = new ReflectionClass($this->master()); | |
$method_reflection = $reflection->getMethod($method_name); | |
$function_name = $function_name ? $function_name : $this->functionNamePrefix() . '_' . $method_name . '_proxy'; | |
if (!function_exists($function_name)) { | |
$parameters = $method_reflection->getParameters(); | |
if ($function_parameters === FALSE) { | |
$function_parameters = array(); | |
$method_parameters = array(); | |
foreach ($parameters as $id => $parameter ) { | |
$default_value = ''; | |
if ($parameter->isOptional()) { | |
$v = $parameter->getDefaultValue(); | |
$default_value = ' = ' . var_export($v, TRUE); | |
} | |
$function_parameters[] = ($parameter->isPassedByReference() ? '&$' : '$' ) . $parameter->name . $default_value; | |
$method_parameters[] = '$' . $parameter->name; | |
} | |
} | |
if ($context_token !== FALSE) { | |
array_unshift($method_parameters, var_export($context_token, TRUE)); | |
} | |
$src = 'function ' . $function_name . '(' . implode(',', $function_parameters) . '){'; | |
$src .= ' return ' . $this->instanceLocator(). '->' . $method_name . '(' . implode(',', $method_parameters) . ');'; | |
$src .= '}'; | |
eval( $src ); | |
if (!function_exists($function_name)) { | |
error_log('Fail to defined function : ' . $function_name ); | |
} | |
} | |
return $function_name; | |
} | |
} | |
/** | |
* Base superclass for all subclasses implementing singleton pattern. | |
* | |
* @author atchijov | |
* | |
*/ | |
abstract class DOO7Singleton extends DOO7Object { | |
static $singleton = NULL; | |
/** | |
* This is not "silver bullet", but it should work if you saving instance of DOO7Singleton in $form_state | |
* (or any other situation where object gets deserialized once) | |
*/ | |
public function __wakeup() { | |
if (self::$singleton == NULL) { | |
self::$singleton = $this; | |
$this->wakeup(); | |
} | |
else { | |
watchdog('rcn_lib', 'Multiple calls to __wakeup(). Class name: ' . \get_called_class(), array(), WATCHDOG_WARNING); | |
} | |
} | |
protected function wakeup() { | |
// | |
} | |
/** | |
* Returns singleton for the class. | |
* | |
* NOTE: Must be re-implemented by subclass. | |
* | |
* @return stdClass singleton object of the class | |
* @throws Exception | |
*/ | |
public static function singleton() { | |
$sub_class_name = \get_called_class(); | |
if (!isset(self::$singleton[$sub_class_name])) { | |
self::$singleton[$sub_class_name] = new $sub_class_name; | |
} | |
return self::$singleton[$sub_class_name]; | |
} | |
/** | |
* Most of singletons will want to have DOO7Peoxy mixin, so lets mix it in here. | |
*/ | |
public function __construct() { | |
$this->mixin('DOO7Proxy'); | |
} | |
/** | |
* The way to get singleton object is to use static method singleton. | |
* @see DOO7Porxy | |
* | |
* @return string php snippet | |
*/ | |
public function instanceLocator() { | |
return get_class($this) . '::singleton()'; | |
} | |
} | |
/** | |
* Basic class for all classes which implements drupal modules. | |
* | |
* @author atchijov | |
* | |
*/ | |
abstract class DOO7Module extends DOO7Object { | |
var $module_name; | |
/** | |
* Convinence method. Returns module name based on full $file_name of any file inside module | |
* | |
* @param string $file_name | |
* @return string/bool module name of FALSE | |
*/ | |
protected function defaultModuleName( $file_name ) { | |
$dir = dirname( $file_name ); | |
$parts = explode( DIRECTORY_SEPARATOR, $dir); | |
while (count($parts) > 1 ) { | |
$last = array_pop($parts); | |
if ($last == 'includes') { | |
continue; | |
} | |
$path_to_info = array_merge($parts, array( $last, $last . '.info')); | |
if (file_exists(implode( DIRECTORY_SEPARATOR, $path_to_info))) { | |
return $last; | |
} | |
} | |
return FALSE; | |
} | |
public function __construct($module_name) { | |
$this->mixin('DOO7Proxy'); | |
$this->module_name = $module_name; | |
if (isset($this->callbacks)) { | |
foreach ($this->callbacks as $callback) { | |
$this->declareCallback($callback); | |
} | |
} | |
// In some situations (xmlrpc, cron) | |
DOO7Module::declareModule($this); | |
} | |
/* | |
* DOO7Proxy methods | |
*/ | |
/** | |
* @see DOO7Proxy functionNamePrefix | |
*/ | |
public function functionNamePrefix() { | |
return $this->module_name; | |
} | |
/** | |
* @see DOO7Proxy instanceLocator | |
*/ | |
public function instanceLocator() { | |
return 'DOO7Module::getModule(\'' . $this->module_name. '\')'; | |
} | |
/** | |
* Uses DOO7Proxy functionality to declare hook proxy function | |
* | |
* @param string $hook_name | |
* @return string hook function name | |
*/ | |
public function declareHook( $hook_name ) { | |
$function_name = str_replace('hook_', $this->module_name . '_', $hook_name); | |
return $this->declare_proxy_function($hook_name, $function_name); | |
} | |
/** | |
* Enumerate all methods implemented by caller and use declareHook on any | |
* method which starts with hook_. | |
* | |
* @return DOO7Module $this | |
*/ | |
public function declareHooks() { | |
$reflection = new ReflectionClass( $this ); | |
$methods = $reflection->getMethods(); | |
foreach ($methods as $id => $reflection_method ) { | |
if (strpos($reflection_method->name, 'hook_') === 0) { | |
if ($reflection_method->name == 'hook_boot' && drupal_is_cli()) { | |
continue; | |
} | |
$this->declareHook( $reflection_method->name ); | |
} | |
} | |
return $this; | |
} | |
/** | |
* Save/Retrieve module instance to/from static cache. Could be used to retrieve array of all modules. | |
* | |
* @param string $module_name | |
* @param DOO7Module $a_module | |
* | |
* @return DOO7Module/DOO7Module[] | |
*/ | |
public static function _register_module( $module_name = FALSE, $a_module = NULL ) { | |
static $modules = array(); | |
if ($a_module != NULL ) { | |
$modules[$module_name] = $a_module; | |
} | |
return $module_name? $modules[$module_name] : $modules; | |
} | |
/** | |
* Return cached object implementing module with given name | |
* | |
* @param string $name | |
* @return DOO7Module a module | |
*/ | |
public static function getModule($name) { | |
return self::_register_module($name); | |
} | |
/** | |
* This is how you turning DOO7Module subclass object into Drupal Module. | |
* | |
* @param DOO7Module $a_module | |
* @return DruplaModule | |
* | |
* @example | |
* | |
* // in your_module.module file | |
* | |
* function your_module_boot() { | |
* DOO7Module::declareModule(new YourModule('your_module')); | |
* } | |
*/ | |
public static function declareModule($a_module) { | |
return self::_register_module( $a_module->module_name, $a_module )->declareHooks(); | |
} | |
} | |
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 | |
/** | |
* | |
* @author atchijov | |
* | |
*/ | |
class ExampleModule extends DOO7Module { | |
public function hook_init() { | |
$foo = $bar; | |
} | |
public function hook_page_alter( &$page ) { | |
return $page; | |
} | |
} | |
function doo7_example_boot() { | |
DOO7Module::declareModule( new ExampleModule('doo7_example')); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See the corresponding blog post at https://www.leapingbytes.com/blog/doo7_you_can_teach_old_dog_new_tricks