diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a056307 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +testing/logs \ No newline at end of file diff --git a/README.md b/README.md index b9d4da6..bb23ec6 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,10 @@ Method hooking in PHP. ## Installation -You can install HookPHP with Composer or Bower. +You can install HookPHP with Composer. ```sh composer require sciactive/hookphp - -bower install https://github.com/sciactive/hookphp.git ``` ## Getting Started @@ -30,7 +28,7 @@ class Test { echo $string; } } -$obj = new Test; +$obj = new Test(); \SciActive\Hook::hookObject($obj, 'Test->'); ``` @@ -103,6 +101,14 @@ A callback can replace `$function` to cause a different function or method to ru A callback can add and alter data in the `$data` array to communicate with other callbacks. This is especially useful when communicating with callbacks that run on the other side of the method call. +## Retrieving a Hooked Object + +If you need to retrieve an object that has been hooked, you can use the `_hookObject` method: + +```php +$originalObject = $hookedObject->_hookObject(); +``` + ## Contacting the Developer There are several ways to contact HookPHP's developer with your questions, concerns, comments, bug reports, or feature requests. diff --git a/bower.json b/bower.json deleted file mode 100644 index b252c86..0000000 --- a/bower.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "hookphp", - "version": "1.1.0", - "main": "src/Hook.php", - "ignore": [ - "test*", - "composer.json" - ], - "keywords": [ - "method hooking", - "hook", - "interception" - ] -} \ No newline at end of file diff --git a/composer.json b/composer.json index 5644d86..1244464 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "sciactive/hookphp", "description": "Method hooking in PHP.", - "version": "1.1.0", + "version": "1.2.0", "type": "library", "keywords": [ "method hooking", @@ -25,7 +25,7 @@ "source": "https://github.com/sciactive/hookphp" }, "archive": { - "exclude": ["bower.json", "test*"] + "exclude": ["testing"] }, "autoload": { "psr-0": { diff --git a/src/Hook.php b/src/Hook.php index ff78614..04799f3 100644 --- a/src/Hook.php +++ b/src/Hook.php @@ -7,14 +7,14 @@ * Hooks are used to call a callback when a method is called and optionally * manipulate the arguments/function call/return value. * - * @version 1.1.0 + * @version 1.2.0 * @license https://www.gnu.org/licenses/lgpl.html * @author Hunter Perrin * @copyright SciActive.com * @link http://requirephp.org */ -if (!class_exists('\\SciActive\\HookOverride')) { +if (!class_exists('\SciActive\HookOverride')) { include_once(__DIR__.DIRECTORY_SEPARATOR.'HookOverride.php'); } @@ -141,7 +141,7 @@ public static function hookObject(&$object, $prefix = '', $recursive = true) { // recursively calling ourself. Some system classes shouldn't be hooked. $className = str_replace('\\', '_', $isString ? $object : get_class($object)); global $_; - if (isset($_) && in_array($className, array('\\SciActive\\Hook', 'depend', 'config', 'info'))) { + if (isset($_) && in_array($className, array('\SciActive\Hook', 'depend', 'config', 'info'))) { return false; } @@ -153,7 +153,7 @@ public static function hookObject(&$object, $prefix = '', $recursive = true) { } } - if (!class_exists("\\SciActive\\HookOverride_$className")) { + if (!class_exists("\SciActive\HookOverride_$className")) { if ($isString) { $reflection = new \ReflectionClass($object); } else { @@ -164,7 +164,7 @@ public static function hookObject(&$object, $prefix = '', $recursive = true) { $code = ''; foreach ($methods as &$curMethod) { $fname = $curMethod->getName(); - if (in_array($fname, array('__construct', '__destruct', '__get', '__set', '__isset', '__unset', '__toString', '__invoke', '__set_state', '__clone', '__sleep'))) { + if (in_array($fname, array('__construct', '__destruct', '__get', '__set', '__isset', '__unset', '__toString', '__invoke', '__set_state', '__clone', '__sleep', 'jsonSerialize'))) { continue; } @@ -201,15 +201,15 @@ public static function hookObject(&$object, $prefix = '', $recursive = true) { //."\t\tfor (\$i = \$arg_count; \$i < \$real_arg_count; \$i++)\n" //."\t\t\t\$arguments[] = func_get_arg(\$i);\n" //."\t}\n" - ."\t\$function = array(\$this->_hook_object, '$fname');\n" + ."\t\$function = array(\$this->_hookObject, '$fname');\n" ."\t\$data = array();\n" - ."\t\\SciActive\\Hook::runCallbacks(\$this->_hook_prefix.'$fname', \$arguments, 'before', \$this->_hook_object, \$function, \$data);\n" + ."\t\\SciActive\\Hook::runCallbacks(\$this->_hookPrefix.'$fname', \$arguments, 'before', \$this->_hookObject, \$function, \$data);\n" ."\tif (\$arguments !== false) {\n" ."\t\t\$return = call_user_func_array(\$function, \$arguments);\n" ."\t\tif ((object) \$return === \$return && get_class(\$return) === '$className')\n" ."\t\t\t\\SciActive\\Hook::hookObject(\$return, '$prefix', false);\n" ."\t\t\$return = array(\$return);\n" - ."\t\t\\SciActive\\Hook::runCallbacks(\$this->_hook_prefix.'$fname', \$return, 'after', \$this->_hook_object, \$function, \$data);\n" + ."\t\t\\SciActive\\Hook::runCallbacks(\$this->_hookPrefix.'$fname', \$return, 'after', \$this->_hookObject, \$function, \$data);\n" ."\t\tif ((array) \$return === \$return)\n" ."\t\t\treturn \$return[0];\n" ."\t}\n" @@ -218,10 +218,10 @@ public static function hookObject(&$object, $prefix = '', $recursive = true) { unset($curMethod); // Build a HookOverride class. $include = str_replace(array('_NAMEHERE_', '//#CODEHERE#', ''), array($className, $code, '', ''), Hook::$hookFile); - eval ($include); + eval($include); } - eval ('$object = new \\SciActive\\HookOverride_'.$className.' ($object, $prefix);'); + eval('$object = new \SciActive\HookOverride_'.$className.' ($object, $prefix);'); return true; } diff --git a/src/HookOverride.php b/src/HookOverride.php index 2186b57..dbfb3da 100644 --- a/src/HookOverride.php +++ b/src/HookOverride.php @@ -2,7 +2,7 @@ /** * Dynamic HookOverride class. * - * @version 1.1.0 + * @version 1.2.0 * @license https://www.gnu.org/licenses/lgpl.html * @author Hunter Perrin * @copyright SciActive.com diff --git a/src/HookOverride_extend.php b/src/HookOverride_extend.php index 0811425..c5d8cd2 100644 --- a/src/HookOverride_extend.php +++ b/src/HookOverride_extend.php @@ -2,7 +2,7 @@ /** * Dynamic HookOverride class. * - * @version 1.1.0 + * @version 1.2.0 * @license https://www.gnu.org/licenses/lgpl.html * @author Hunter Perrin * @copyright SciActive.com @@ -15,74 +15,74 @@ * This class is dynamically edited during the takeover of an object for * hooking. */ -class HookOverride__NAMEHERE_ extends HookOverride { +class HookOverride__NAMEHERE_ extends HookOverride implements \JsonSerializable { /** * Used to store the overridden class. - * @var mixed $_hook_object + * @var mixed $_hookObject */ - protected $_hook_object = null; + protected $_hookObject = null; /** * Used to store the prefix (the object's variable name). - * @var string $_hook_prefix + * @var string $_hookPrefix */ - protected $_hook_prefix = ''; + protected $_hookPrefix = ''; - public function _hook_object() { - return $this->_hook_object; + public function _hookObject() { + return $this->_hookObject; } public function __construct(&$object, $prefix = '') { - $this->_hook_object = $object; - $this->_hook_prefix = $prefix; + $this->_hookObject = $object; + $this->_hookPrefix = $prefix; } public function &__get($name) { - return $val =& $this->_hook_object->$name; + return $val =& $this->_hookObject->$name; } public function __set($name, $value) { - return $this->_hook_object->$name = $value; + return $this->_hookObject->$name = $value; } public function __isset($name) { - return isset($this->_hook_object->$name); + return isset($this->_hookObject->$name); } public function __unset($name) { - unset($this->_hook_object->$name); + unset($this->_hookObject->$name); } public function __toString() { - return (string) $this->_hook_object; + return (string) $this->_hookObject; } public function __invoke() { - if (method_exists($this->_hook_object, '__invoke')) { + if (method_exists($this->_hookObject, '__invoke')) { $args = func_get_args(); - return call_user_func_array(array($this->_hook_object, '__invoke'), $args); + return call_user_func_array(array($this->_hookObject, '__invoke'), $args); } } public function __set_state() { - if (method_exists($this->_hook_object, '__set_state')) { + if (method_exists($this->_hookObject, '__set_state')) { $args = func_get_args(); - return call_user_func_array(array($this->_hook_object, '__set_state'), $args); + return call_user_func_array(array($this->_hookObject, '__set_state'), $args); } } public function __clone() { // TODO: Test this. Make sure cloning works properly. - $newObject = clone $this->_hook_object; + $newObject = clone $this->_hookObject; Hook::hookObject($newObject, get_class($newObject).'->', false); return $newObject; } public function jsonSerialize() { - if (method_exists($this->_hook_object, 'jsonSerialize')) { + if (is_callable($this->_hookObject, 'jsonSerialize')) { $args = func_get_args(); - return call_user_func_array(array($this->_hook_object, 'jsonSerialize'), $args); + return call_user_func_array(array($this->_hookObject, 'jsonSerialize'), $args); } else { - return json_decode(json_encode($this->_hook_object), true); + return $this->_hookObject; } } diff --git a/test.php b/test.php deleted file mode 100644 index 655cc6f..0000000 --- a/test.php +++ /dev/null @@ -1,60 +0,0 @@ -\n"; - -class Test { - public $test = 'right'; - - public function testFunction($argument) { - echo "Function redefinition failed.
\n"; - } - - public function testFunctionReal($argument) { - echo "Output: $argument
\n"; - return 2; - } -} - -$obj = new Test; -Hook::hookObject($obj, 'Test->'); -Hook::addCallback('Test->testFunction', -2, function(&$arguments, $name, &$object, &$function, &$data){ - $arguments[0] = 'Still Failure!'; - if ($object->test !== 'right') { - echo "Object check failed.
\n"; - } else { - echo "Object check passed.
\n"; - } - if ($name !== 'Test->testFunction') { - echo "Name check failed.
\n"; - } else { - echo "Name check passed.
\n"; - } -}); -Hook::addCallback('Test->testFunction', -1, function(&$arguments, $name, &$object, &$function, &$data){ - $arguments[0] = 'Success!'; - $data['test'] = 1; - $function = array($object, 'testFunctionReal'); -}); -Hook::addCallback('Test->testFunction', 1, function(&$return, $name, &$object, &$function, &$data){ - if ($data['test'] !== 1) { - echo "Data check failed.
\n"; - } else { - echo "Data check passed.
\n"; - } - if ($name !== 'Test->testFunction') { - echo "Name check failed.
\n"; - } else { - echo "Name check passed.
\n"; - } - if ($return[0] !== 2) { - echo "Return value check failed.
\n"; - } else { - echo "Return value check passed.
\n"; - } -}); - -$obj->testFunction('Failure!'); diff --git a/testing/TestModel.php b/testing/TestModel.php new file mode 100644 index 0000000..45a4c02 --- /dev/null +++ b/testing/TestModel.php @@ -0,0 +1,13 @@ + + + + tests + + + + + ../src + + ../src/HookOverride_extend.php + + + + \ No newline at end of file diff --git a/testing/run b/testing/run new file mode 100755 index 0000000..23a60fa --- /dev/null +++ b/testing/run @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +echo "Running HookPHP Testing" +echo "Use \`run coverage\` to build coverage clover." +echo + +if [[ "$1" == "coverage" ]]; then + CVG="--coverage-clover logs/clover.xml" +else + CVG="" +fi + +cd $(dirname $0) + +echo "################################################################################" +echo "# Running tests #" +echo -e "################################################################################\n" + +phpunit --testsuite default --bootstrap bootstrap.php $CVG diff --git a/testing/tests/HookTest.php b/testing/tests/HookTest.php new file mode 100644 index 0000000..fc65b77 --- /dev/null +++ b/testing/tests/HookTest.php @@ -0,0 +1,192 @@ +'); + $this->assertInstanceOf('\SciActive\HookOverride_TestModel', $testModel); + + return $testModel; + } + + /** + * @depends testHooking + */ + public function testObjectAccess($testModel) { + $that = $this; + $ids = Hook::addCallback('TestModel->testFunction', -2, function(&$arguments, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals('right', $object->test); + $that->assertEquals('TestModel->testFunction', $name); + }); + $this->assertTrue($testModel->testFunction(true)); + $this->assertEquals(1, Hook::delCallbackByID('TestModel->testFunction', $ids[0])); + } + + /** + * @depends testHooking + */ + public function testFunctionOverride($testModel) { + $ids = Hook::addCallback('TestModel->testFunction', -1, function(&$arguments, $name, &$object, &$function, &$data){ + $function = array($object, 'testFunctionFake'); + }); + $this->assertFalse($testModel->testFunction(true)); + $this->assertEquals(1, Hook::delCallbackByID('TestModel->testFunction', $ids[0])); + } + + /** + * @depends testHooking + */ + public function testReturnValues($testModel) { + $that = $this; + $ids = Hook::addCallback('TestModel->testFunction', 2, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals('success', $return[0]); + }); + $testModel->testFunction('success'); + $this->assertEquals(1, Hook::delCallbackByID('TestModel->testFunction', $ids[0])); + } + + /** + * @depends testHooking + */ + public function testArgumentModification($testModel) { + $ids = Hook::addCallback('TestModel->testFunction', -1, function(&$arguments, $name, &$object, &$function, &$data){ + $arguments[0] = 'success'; + }); + $this->assertEquals('success', $testModel->testFunction(true)); + $this->assertEquals(1, Hook::delCallbackByID('TestModel->testFunction', $ids[0])); + } + + /** + * @depends testHooking + */ + public function testDataPassing($testModel) { + $that = $this; + Hook::addCallback('TestModel->testFunction', -1, function(&$arguments, $name, &$object, &$function, &$data){ + $data['test'] = 1; + }); + $ids = Hook::addCallback('TestModel->testFunction', 2, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(1, $data['test']); + }); + $testModel->testFunction(true); + $this->assertEquals(1, Hook::delCallbackByID('TestModel->testFunction', $ids[0])); + } + + /** + * Do this one last, cause it leaves its callbacks. + * + * @depends testHooking + * @expectedException Exception + * @expectedExceptionMessage Everything is good. + */ + public function testTimeline($testModel) { + $that = $this; + Hook::addCallback('TestModel->testFunction', -1000, function(&$arguments, $name, &$object, &$function, &$data){ + $data['timeline'] = 1; + }); + Hook::addCallback('TestModel->testFunction', -100, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(1, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -90, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(2, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -70, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(3, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -30, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(4, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -10, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(5, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -9, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(6, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -8, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(7, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -7, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(8, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -6, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(9, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -5, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(10, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -4, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(11, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -3, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(12, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -2, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(13, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', -1, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(14, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 1, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(15, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 2, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(16, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 3, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(17, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 4, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(18, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 5, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(19, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 6, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(20, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 7, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(21, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 8, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(22, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 9, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(23, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 10, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(24, $data['timeline']); + $data['timeline']++; + }); + Hook::addCallback('TestModel->testFunction', 1000, function(&$return, $name, &$object, &$function, &$data) use ($that){ + $that->assertEquals(25, $data['timeline']); + // Cool, it all worked. + throw new Exception('Everything is good.'); + }); + $that->assertEquals(27, count(Hook::getCallbacks()['TestModel->testFunction'])); + $testModel->testFunction(true); + } + +}