Skip to content

Instantly share code, notes, and snippets.

@leapingbytes
Last active December 21, 2015 07:29
Show Gist options
  • Save leapingbytes/6271917 to your computer and use it in GitHub Desktop.
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
<?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();
}
}
<?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'));
}
@leapingbytes
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment