Skip to content

Instantly share code, notes, and snippets.

@musaid
Created October 2, 2012 11:05
Show Gist options
  • Save musaid/3818209 to your computer and use it in GitHub Desktop.
Save musaid/3818209 to your computer and use it in GitHub Desktop.
Handler Loader Class
<?php
/**
* class to load the handlers as callbacks for the given events/tags
* @author musaid <musaid@alliedmaldives.net>
* @version v0.1
*
* usage:
* $handle = new HandlerLoader();
* var_dump($handle->getHandlers('/var/www/crawler/folder/'));
* var_dump($handle->getHandlers('/var/www/crawler/folder/', array('handler', 'denormalizer')));
*/
class HandlerLoader {
/**
* get handlers from the files in the given path
* @param string $path directory/path name
* @param array $tags tags to distinguish relevant files
* @return array $handlers relevant handlers
*/
public function getHandlers($path, $tags = array()) {
$files = $this->getFiles($path);
$closures = array();
$methods = array();
$handlers = array();
foreach ($files as $file) {
$tokens = $this->tokenize($file);
$handlers = $this->getClosures($handlers, $file, $tokens, $tags);
$handlers = $this->getMethods($handlers, $tokens, $tags);
}
return $handlers;
}
/**
* get the files in the given path
* @param string $path directory/path name
* @param string $ext extension of the files needed
* @return array $files list of files in the directory/path
*/
protected function getFiles($path, $ext = '.php') {
// handle the extension if a . is provided
if (preg_match('/^\.\w+/', $ext))
$ext = str_replace('.', '', $ext);
$iterator = new FilesystemIterator($path);
while($iterator->valid()) {
if (preg_match('/'.$ext.'/i', $iterator->getExtension())) {
$file_name = $iterator->getPathname();
$files[] = $file_name;
}
$iterator->next();
}
return $files;
}
/**
* tokenize the files into an array
* @param string $file file name including the path
* @return array files content as tokens
*/
protected function tokenize($file) {
return token_get_all(file_get_contents($file));
}
/**
* count the matching tags present in the docblock of the method/closure
* @param string $str docblock comment as a string
* @param array $tag_array tags to be compared
* @return int $matched_tag_count number of matched tags
*/
protected function tagCount($str, $tag_array = array()) {
preg_match('/@tags ([a-zA-Z0-9_\x7f-\xff, \n*\t]*)/ms', $str, $tags);
$tags = explode(',', isset($tags[1]) ? $tags[1] : null);
$tags = array_map(function($item) { return trim($item, "* \t\r\n"); }, $tags);
$matched_tag_count = 0;
foreach ($tag_array as $tag_name) {
foreach ($tags as $tags_name) {
if ($tag_name == $tags_name) {
$matched_tag_count++;
}
}
}
return $matched_tag_count;
}
/**
* get the events related to each method/closure
* @param string $str docblock comment as a string
* @return string event associated with the method/closure
*/
protected function getEvent($str) {
preg_match('/@event ([a-zA-Z0-9_\x7f-\xff, \t\x5C]*)/ms', $str, $event);
return !empty($event) ? $event[1] : null;
}
/**
* get the closures with the given tags and it's associated event
* throws an array if the closure misses the event annotation
* @param array $handlers previous handlers (if any)
* @param string $file file name where the relevant handlers belong to
* @param array $tokens tokens to search for handlers
* @param array $tags tags associated to the handlers (if any)
* @return array matched handlers as callbacks
*/
protected function getClosures($handlers = array(), $file, $tokens, $tags) {
// require the file
require_once($file);
foreach ($tokens as $token) {
if (!is_array($token) || $token[0] != T_VARIABLE) continue;
$var_name = str_replace('$', '', $token[1]);
if (!is_callable($$var_name)) continue;
$rf_func = new ReflectionFunction($$var_name);
$event = $this->getEvent($rf_func->getDocComment());
if (empty($event)) {
throw new Exception("No Event Provided in handler $$var_name in {$file} on line ".$rf_func->getStartLine(), 1);
}
if (!empty($tags) && $this->tagCount($rf_func->getDocComment(), $tags) == 0) continue;
if (!isset($handlers[$event])) {
$handlers[$event] = array();
}
$handlers[$event][] = $$var_name;
}
return $handlers;
}
/**
* get the methods with the given tags and it's associated event
* throws an array if the closure misses the event annotation
* @param array $handlers previous handlers (if any)
* @param array $tokens tokens to search for handlers
* @param array $tags tags associated to the handlers (if any)
* @return array matched handlers as a callback (class name and method name)
*/
protected function getMethods($handlers = array(), $tokens, $tags) {
$callback = false;
$class_token = false;
$i=0;
foreach ($tokens as $token) {
if (!is_array($token)) continue;
if ($token[0] == T_CLASS) {
$class_token = true;
} else if ($class_token && $token[0] == T_STRING) {
$reflection = new ReflectionClass($token[1]);
foreach($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $prop) {
$method = $prop->getName();
$rf_method = new ReflectionMethod($token[1], $method);
$callback = array($token[1], $method);
if (!is_callable($callback)) continue;
$event = $this->getEvent($rf_method->getDocComment());
if (empty($event)) {
throw new Exception("No Event Provided in handler '$method' in ".$rf_method->getFileName()." on line ".$rf_method->getStartLine(), 1);
}
if (!empty($tags) && $this->tagCount($rf_method->getDocComment(), $tags) == 0) continue;
if (!isset($handlers[$event])) {
$handlers[$event] = array();
}
$handlers[$event][] = $callback;
}
$class_token = false;
}
$i++;
}
return $handlers;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment