Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit a24b73e7c43ec648f3879e43a27759ab4919ca38 1 parent fffb21a
@shuber authored
View
7 README.md
@@ -0,0 +1,7 @@
+= phuby
+
+== Todo
+
+* class\_eval and instance\_eval - (should also accept string arguments which are embedded in a new random module and evaluated, then included/extended)
+* ${ClassName} syntax
+* instance/class variables (class's instance\_vars array should store references to real vars)
View
89 lib/class_reflector.php
@@ -0,0 +1,89 @@
+<?php
+
+class ClassReflector {
+
+ protected $_methods;
+ protected $_name;
+ protected $_parent;
+ protected $_reflection;
+ protected $_variables;
+
+ static $instances = array();
+
+ function __construct($class) {
+ $this->_name = $class;
+ $this->_parent = get_parent_class($class);
+ }
+
+ function &__get($method) {
+ $result = &$this->$method();
+ return $result;
+ }
+
+ function ancestors() {
+ return array_map(function($ancestor) { return ClassReflector::instance($ancestor); }, class_parents($this->name));
+ }
+
+ function class_methods($include_super = true) {
+ return array_filter($this->methods($include_super), function($method) { return $method->isStatic(); });
+ }
+
+ function class_variables($include_super = true) {
+ return array_filter($this->variables($include_super), function($variable) { return $variable->isStatic(); });
+ }
+
+ function instance_methods($include_super = true) {
+ return array_filter($this->methods($include_super), function($method) { return !$method->isStatic(); });
+ }
+
+ function instance_variables($include_super = true) {
+ return array_filter($this->variables($include_super), function($variable) { return !$variable->isStatic(); });
+ }
+
+ function methods($include_super = true) {
+ if (!isset($this->_methods)) {
+ $this->_methods = array();
+ foreach ($this->reflection->getMethods() as $method) $this->_methods[$method->name] = $method;
+ }
+ $methods = $this->_methods;
+ if (!$include_super) {
+ foreach ($methods as $name => $method) {
+ if ($method->getDeclaringClass()->name != $this->name) unset($methods[$name]);
+ }
+ }
+ return $methods;
+ }
+
+ function name() {
+ return $this->_name;
+ }
+
+ function &reflection() {
+ if (!isset($this->_reflection)) $this->_reflection = new ReflectionClass($this->name);
+ return $this->_reflection;
+ }
+
+ function &superclass() {
+ if ($this->_parent) return static::instance($this->_parent);
+ }
+
+ function variables($include_super = true) {
+ if (!isset($this->_variables)) {
+ $this->_variables = array();
+ foreach ($this->reflection->getProperties() as $variable) $this->_variables[$variable->name] = $variable;
+ }
+ $variables = $this->_variables;
+ if (!$include_super) {
+ foreach ($variables as $name => $variable) {
+ if ($variable->getDeclaringClass()->name != $this->name) unset($variables[$name]);
+ }
+ }
+ return $variables;
+ }
+
+ static function &instance($class) {
+ if (!isset(static::$instances[$class])) static::$instances[$class] = new static($class);
+ return static::$instances[$class];
+ }
+
+}
View
156 lib/environment.php
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * Manages include paths, error handlers, and autoloading
+ *
+ * @package phuby
+ * @author Sean Huber <shuber@huberry.com>
+ * @abstract
+**/
+abstract class Environment {
+
+ /**
+ * The filename extension used by Environment::filename_for_class() when autoloading.
+ * Defaults to '.php'
+ *
+ * @static
+ **/
+ static $autoload_filename_extension = '.php';
+
+ /**
+ * Stores registered custom error handlers.
+ *
+ * @static
+ **/
+ static $error_handlers = array();
+
+ /**
+ * Appends a path to the end of the include_path.
+ * Returns the old include_path or false on failure.
+ *
+ * @param string $path
+ * @return string
+ * @static
+ **/
+ static function append_include_path($path) {
+ return self::set_include_paths(array_merge(self::include_paths(), array($path)));
+ }
+
+ /**
+ * Default autoload implementation.
+ * Requires a file with the underscored version of a class name and subdirectories for each namespace.
+ *
+ * <code>
+ * Environment::autoload('ActiveRecord\Base'); // => require_once 'active_record/base.php';
+ * </code>
+ *
+ * @param string $class
+ * @return void
+ * @static
+ **/
+ static function autoload($class) {
+ require_once self::filename_for_class($class);
+ }
+
+ /**
+ * Default error handler implementation.
+ * Passes errors to the handlers registered with the Environment class.
+ * If all custom handlers do not handle the error, then the error is passed to the default php handler.
+ *
+ * @param string $number
+ * @param string $message
+ * @param string $file
+ * @param string $line
+ * @param string $context
+ * @return void|boolean
+ * @static
+ **/
+ static function error_handler($number, $message, $file, $line, &$context) {
+ foreach (self::$error_handlers as $handler) {
+ if (call_user_func($handler, $number, $message, $file, $line, $context) !== false) return;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the underscored version of $class prefixed with its namespaces as directories
+ *
+ * <code>
+ * Environment::filename_for_class('ActiveRecord\Base'); // => 'active_record/base.php'
+ * </code>
+ *
+ * @param string $class
+ * @return string
+ * @static
+ **/
+ static function filename_for_class($class) {
+ $namespaces = array_filter(preg_split('#\\\\|::#', $class), function($part) { return !empty($part); });
+ $parts = array_map(function($namespace) { return strtolower(preg_replace('/[^A-Z^a-z^0-9]+/', '_', preg_replace('/([a-z\d])([A-Z])/', '\1_\2', preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', $namespace)))); }, $namespaces);
+ return implode(DIRECTORY_SEPARATOR, $parts).static::$autoload_filename_extension;
+ }
+
+ /**
+ * Returns an array of the current include paths.
+ *
+ * @return array
+ * @static
+ **/
+ static function include_paths() {
+ return explode(PATH_SEPARATOR, get_include_path());
+ }
+
+ /**
+ * Prepends a path to the beginning of the include_path.
+ * Returns the old include_path or false on failure.
+ *
+ * @param string $path
+ * @return string
+ * @static
+ **/
+ static function prepend_include_path($path) {
+ return self::set_include_paths(array_merge(array($path), self::include_paths()));
+ }
+
+ /**
+ * Prepends a custom error handler with the Environment class.
+ * If your custom error handler returns false, the error is passed to the next error handler registered with Environment.
+ *
+ * @param string|array $handler
+ * @return void
+ * @static
+ * @link http://us.php.net/set_error_handler#function.set-error-handler.parameters
+ **/
+ static function register_error_handler($handler) {
+ array_unshift(self::$error_handlers, $handler);
+ }
+
+ /**
+ * Removes a path from the include_path.
+ * Returns the old include_path or false on failure.
+ *
+ * @param string $path
+ * @return string
+ * @static
+ **/
+ static function remove_include_path($path) {
+ $paths = array();
+ foreach (self::include_paths() as $include_path) {
+ if ($include_path != $path) array_push($paths, $include_path);
+ }
+ self::set_include_paths($paths);
+ }
+
+ /**
+ * Replaces the current include_path with $paths.
+ * Returns the old include_path or false on failure.
+ *
+ * @param array $paths
+ * @return string
+ * @static
+ **/
+ static function set_include_paths($paths) {
+ if (is_array($paths)) $paths = implode(PATH_SEPARATOR, $paths);
+ return set_include_path($paths);
+ }
+
+}
View
15 lib/object.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Object {
+ abstract class InstanceMethods {
+ //
+ }
+}
+
+namespace {
+ class Object {
+
+ function __construct() { }
+
+ }
+}
View
56 lib/phuby.php
@@ -0,0 +1,56 @@
+<?php
+
+abstract class Phuby {
+
+ const VERSION = '0.0.0';
+
+ /**
+ * Delegates the call to Environment::autoload($class).
+ * If the class is successfully loaded, a function with the name of the class is created (if it doesn't already exist)
+ * which calls its corresponding class's invoke method.
+ *
+ * @param string $class
+ * @return void
+ * @static
+ **/
+ static function autoload($class) {
+ Environment::autoload($class);
+ if (class_exists($class, false)) {
+ if (!function_exists($class)) {
+ $function = sprintf('function &%s() { $result = &Klass::instance("%s")->call_array(func_get_args()); return $result; }', $class, $class);
+ eval($function);
+ }
+ }
+ }
+
+ /**
+ * Hides the "Non-static method should not be called statically" strict errors since this functionality is required.
+ *
+ * @param int $number
+ * @param string $message
+ * @param string $file
+ * @param string $line
+ * @param array $context
+ * @return void|bool
+ * @static
+ **/
+ static function non_static_method_call_error_handler($number, $message, $file, $line, &$context) {
+ if ($number != 2048) return false;
+ }
+
+ /**
+ * Hides the "undefined constant XXX" errors if XXX is the name of a class.
+ *
+ * @param int $number
+ * @param string $message
+ * @param string $file
+ * @param string $line
+ * @param array $context
+ * @return void|bool
+ * @static
+ **/
+ static function undefined_constant_error_handler($number, $message, $file, $line, &$context) {
+ if ($number != 8 || !preg_match('#constant (\S+)#', $message, $matches) || !class_exists($matches[1])) return false;
+ }
+
+}
View
10 phuby.php
@@ -0,0 +1,10 @@
+<?php
+
+foreach (array('environment', 'phuby') as $file) require_once 'lib'.DIRECTORY_SEPARATOR.$file.'.php';
+
+Environment::register_error_handler('Phuby::non_static_method_call_error_handler');
+Environment::register_error_handler('Phuby::undefined_constant_error_handler');
+set_error_handler('Environment::error_handler');
+
+Environment::append_include_path(__DIR__.DIRECTORY_SEPARATOR.'lib');
+spl_autoload_register('Phuby::autoload');
View
17 test/run
@@ -0,0 +1,17 @@
+#!/usr/bin/php
+<?php
+
+error_reporting(E_ALL);
+ini_set('display_errors', true);
+
+require_once 'ztest/ztest.php';
+require_once __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'phuby.php';
+require_once 'test_helper.php';
+
+$reporter = new ztest\ConsoleReporter;
+$reporter->enable_color();
+
+$suite = new ztest\TestSuite('phuby unit tests');
+$suite->require_all(__DIR__.DIRECTORY_SEPARATOR.'unit');
+$suite->auto_fill();
+$suite->run($reporter);
View
39 test/test_helper.php
@@ -0,0 +1,39 @@
+<?php
+
+function assert_all_equal($value, $other) {
+ $arguments = func_get_args();
+ $value = array_shift($arguments);
+ foreach ($arguments as $argument) {
+ assert_equal($value, $argument);
+ }
+}
+
+function assert_difference($expression, $lambda) {
+ $expression = 'return '.$expression.';';
+ $value = eval($expression);
+ $lambda();
+ assert_not_equal($value, eval($expression));
+}
+
+function assert_no_difference($expression, $lambda) {
+ $expression = 'return '.$expression.';';
+ $value = eval($expression);
+ $lambda();
+ assert_equal($value, eval($expression));
+}
+
+function assert_in_array($needle, $haystack) {
+ ensure(in_array($needle, $haystack));
+}
+
+function assert_not_in_array($needle, $haystack) {
+ ensure(!in_array($needle, $haystack));
+}
+
+function assert_matches($pattern, $subject) {
+ ensure(preg_match($pattern, $subject));
+}
+
+function assert_not_matches($pattern, $subject) {
+ ensure(!preg_match($pattern, $subject));
+}
View
3  test/unit/class_reflector_test.php
@@ -0,0 +1,3 @@
+<?php
+
+class ClassReflectorTest extends ztest\UnitTestCase { }
View
104 test/unit/environment_test.php
@@ -0,0 +1,104 @@
+<?php
+
+class EnvironmentTest extends ztest\UnitTestCase {
+
+ function setup() {
+ $this->include_path = get_include_path();
+ $this->autoload_filename_extension = Environment::$autoload_filename_extension;
+ $this->error_handlers = Environment::$error_handlers;
+ }
+
+ function teardown() {
+ set_include_path($this->include_path);
+ Environment::$autoload_filename_extension = $this->autoload_filename_extension;
+ Environment::$error_handlers = $this->error_handlers;
+ }
+
+ function exception_error_handler($number, $message, $file, $line, &$context) {
+ throw new RuntimeException;
+ }
+
+ function passing_error_handler($number, $message, $file, $line, &$context) {
+ $this->passing_error_handler_called = true;
+ }
+
+ function failing_error_handler($number, $message, $file, $line, &$context) {
+ $this->failing_error_handler_called = true;
+ return false;
+ }
+
+ function test_should_append_include_path() {
+ Environment::append_include_path(__DIR__);
+ assert_equal($this->include_path.PATH_SEPARATOR.__DIR__, get_include_path());
+ }
+
+ function test_should_call_error_handlers() {
+ Environment::register_error_handler(array($this, 'exception_error_handler'));
+ assert_throws('RuntimeException', function() {
+ $context = array();
+ Environment::error_handler(false, false, false, false, $context);
+ });
+
+ Environment::register_error_handler(array($this, 'passing_error_handler'));
+ Environment::error_handler(false, false, false, false, $context);
+ ensure($this->passing_error_handler_called);
+
+ $this->passing_error_handler_called = false;
+ Environment::register_error_handler(array($this, 'failing_error_handler'));
+ Environment::error_handler(false, false, false, false, $context);
+ ensure($this->failing_error_handler_called);
+ ensure($this->passing_error_handler_called);
+ }
+
+ function test_should_return_correct_filename_for_class() {
+ assert_equal('user.php', Environment::filename_for_class('User'));
+ assert_equal('namespaced/user.php', Environment::filename_for_class('Namespaced\User'));
+ assert_equal('namespaced/user.php', Environment::filename_for_class('\Namespaced\User'));
+ assert_equal('namespaced/user.php', Environment::filename_for_class('Namespaced::User'));
+ assert_equal('namespaced/user.php', Environment::filename_for_class('::Namespaced::User'));
+ assert_equal('camel_cased/user.php', Environment::filename_for_class('CamelCased\User'));
+ assert_equal('under_scored/user.php', Environment::filename_for_class('under_scored\User'));
+ assert_equal('capital_under_scored/user.php', Environment::filename_for_class('Capital_under_scored\User'));
+ assert_equal('user.php', Environment::filename_for_class('user'));
+ assert_equal('user.php', Environment::filename_for_class('USER'));
+ assert_equal('user99.php', Environment::filename_for_class('User99'));
+ }
+
+ function test_should_return_correct_filename_for_class_with_different_extension() {
+ Environment::$autoload_filename_extension = '.inc';
+ assert_equal('user.inc', Environment::filename_for_class('User'));
+ }
+
+ function test_should_return_include_paths() {
+ assert_equal(explode(PATH_SEPARATOR, $this->include_path), Environment::include_paths());
+ }
+
+ function test_should_prepend_include_path() {
+ Environment::prepend_include_path(__DIR__);
+ assert_equal(__DIR__.PATH_SEPARATOR.$this->include_path, get_include_path());
+ }
+
+ function test_should_register_error_handler() {
+ Environment::register_error_handler('test');
+ assert_equal(array_merge(array('test'), $this->error_handlers), Environment::$error_handlers);
+ }
+
+ function test_should_remove_include_path() {
+ $paths = explode(PATH_SEPARATOR, $this->include_path);
+ Environment::remove_include_path(array_pop($paths));
+ assert_equal(implode(PATH_SEPARATOR, $paths), get_include_path());
+ }
+
+ function test_should_set_include_path() {
+ $paths = array('test', 'paths');
+ Environment::set_include_paths($paths);
+ assert_equal(implode(PATH_SEPARATOR, $paths), get_include_path());
+ }
+
+ function test_should_set_include_path_as_string() {
+ $paths = 'test'.PATH_SEPARATOR.'paths';
+ Environment::set_include_paths($paths);
+ assert_equal($paths, get_include_path());
+ }
+
+}
View
3  test/unit/object_test.php
@@ -0,0 +1,3 @@
+<?php
+
+class ObjectTest extends ztest\UnitTestCase { }
View
3  test/unit/phuby_test.php
@@ -0,0 +1,3 @@
+<?php
+
+class PhubyTest extends ztest\UnitTestCase { }
Please sign in to comment.
Something went wrong with that request. Please try again.