.gitignore000064400000000217144760114140006536 0ustar00# Exclude vendor files vendor/ # Exclude ide metadatas nbproject/ # Lock file composer.lock # PhpUnit working configuration file phpunit.xml.travis.yml000064400000000150144760114140006653 0ustar00language: php php: - 5.5 - 5.4 - 5.3 before_script: - composer --prefer-source --dev installCHANGELOG.md000064400000003767144760114140006374 0ustar00Changelog ====== 0.4.1 (Aug 27, 2013) * Better parsing of complex "include" expressions for Yii (by @zvirusz) * Support for dynamic arguments count for methods by checking for func_get_args() inside method body * Fixed a bug with autoloaders reodering (by @zvirusz) 0.4.0 (Aug 04, 2013) * Privileged advices for aspect: allows to access private and protected properties and methods of objects inside advice * Full integration with composer that allows for easy configuration and workflow with AOP * Fix some bugs with caching on Windows * "True" pointcut references that gives the ability to compose a complex pointcut from a simple pointcuts. * Pointcut now accept "$this" in references to point to the current aspect instance (Allows for abstract aspects and abstract pointcuts) * AspectContainer interface was extracted. This gives the way to integrate with another DIC. Look at Warlock framework. * Intercepting system functions such as `fopen()`, `file_get_contents()`, etc * Annotation property pointcut was added * Ability to declare multiple interfaces and/or traits with single `DeclareParent` introduction * DeclareError interceptor was added. This can be used for generating an runtime error for methods that should not be executed in such a way. 0.3.0 (May 27, 2013) * Support for dynamic pointcuts: pointcut that match a specific point in the code, if it is under the control flow (look at AspectJ cflow and cflowbelow) * Performance optimizations * Case-sensitive matching for pointcuts * Primitive pointcuts (&&, ||, !) * [BC break] Changes in the kernel configuration (look at the demo for appLoader and autoloadPaths) * Fix a logic bug for a composite pointcuts 0.2.0 (Mar 15, 2013) * Intercepting methods in traits * Pointcut parser/grammar * Huge pointcuts refactoring, cleaning * Lazy loading services, pointcuts 0.1.1 (Jan 20, 2013) * Introduction advice support * Fix bug with composer autoloader prepending * Fix doctrine/common dependency: >=2.0.0, <2.4.0 0.1.0 (Jan 08, 2013) * Initial release of libraryLICENSE000064400000002102144760114140005546 0ustar00Copyright (c) 2011 Lissachenko Alexander Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.README.md000064400000020267144760114140006034 0ustar00README ====== What is Go! AOP? ----------------- Go! AOP is a PHP 5.4 library based on OOP and AOP paradigms. It allows developers to add support of AOP to every PHP application. Go! AOP doesn't require any PECL-extentions, it neither uses any dark magic of Runkit nor evals, the library doesn't use DI-containers. The code with weaved aspects is fully readable and native, it can be easily debugged with XDebug. You can debug either classes or aspects. The main advantage of Go! AOP is that potentially it can be installed in every PHP-application, because you don't have to change the application source code at all. As an example, with the help of 10-20 lines of code we can intercept all the public, protected and static methods in all the classes of application and display the name and the arguments of each method during its execution. [![Build Status](https://secure.travis-ci.org/lisachenko/go-aop-php.png?branch=master)](https://travis-ci.org/lisachenko/go-aop-php) [![Dependencies Status](https://d2xishtp1ojlk0.cloudfront.net/d/1416156)](http://depending.in/lisachenko/go-aop-php) [![Latest Stable Version](https://poser.pugx.org/lisachenko/go-aop-php/v/stable.png)](https://packagist.org/packages/lisachenko/go-aop-php) Flattr this project! What is AOP? ------------ [AOP (Aspect-Oriented Programming)](http://en.wikipedia.org/wiki/Aspect-oriented_programming) is an approach to cross-cutting concerns, where the concerns are designed and implemented in a "modular" way (that is, with appropriate encapsulation, lack of duplication, etc.), then integrated into all the relevant execution points in a succinct and robust way, e.g. through declarative or programmatic means. In AOP terms, the execution points are called join points, a particular set of them is called a pointcut and the new behavior that is executed before, after, or "around" a join point is called advice. You can read more about AOP in [Introduction](http://go.aopphp.com/docs/introduction/) section. PHP traits can be used to implement some aspect-like functionality. Requirements ------------ Go! AOP library is developed for PHP 5.4.0 and up, but it can partially work with PHP 5.3.0 code. Note that PHP versions before 5.4 will not work completely, if you try to use aspects for code that uses Late Static Binding (LSB) feature. Go! AOP library will not work with eAccelerator. Known bugs ------------ * Version 5.4.5 of PHP has a [bug #62836 Seg fault or broken object references on unserialize()](https://bugs.php.net/bug.php?id=62836). Class advices will be always broken after unserialize(). * Version 5.4.8 of PHP has a [bug #63481 Segmentation fault caused by unserialize()](https://bugs.php.net/bug.php?id=63481). Interceptors for class can be unserialized incorrectly. * Version 5.4.11 of PHP has a [bug #64070 Inheritance with Traits failed with error](https://bugs.php.net/bug.php?id=64070). Method interception in traits is broken, however DeclareParent advice can be used correctly. Installation ------------ Go! AOP library can be installed with composer or manually with git submodules. Installation is quite easy: 1. Download the library using composer or git submodule 2. Create an application aspect kernel 3. Configure the aspect kernel in the front controller 4. Create an aspect 5. Register the aspect in the aspect kernel ### Step 1: Download the library using composer or git submodule Ask composer to download the Go! AOP library with its dependencies by running the command: ``` bash $ php composer.phar require lisachenko/go-aop-php:0.4.0 ``` Composer will install the library to your project's `vendor/lisachenko/go-aop-php` directory. Alternative way for installing the library is to use git submodules: ``` bash git submodule add https://github.com/lisachenko/go-aop-php vendor/lisachenko/go-aop-php ``` Dependencies should be also installed as submodules: ``` bash git submodule add https://github.com/Andrewsville/PHP-Token-Reflection vendor/andrewsville/php-token-reflection git submodule add https://github.com/doctrine/common vendor/doctrine/common (use 2.3.x tags) git submodule add https://github.com/jakubledl/dissect vendor/jakubledl/dissect ``` ### Step 2: Create an application aspect kernel The aim of this library is to provide easy AOP integration to your application. Your first step then is to create the `AspectKernel` class for your application. This class will manage all aspects of your application in one place. The library provides base class to make it easier to create your own kernel. To create your application kernel extend the abstract class `Go\Core\AspectKernel` ``` php init(array( 'debug' => true, // use 'false' for production mode // Cache directory 'cacheDir' => __DIR__ . '/path/to/cache/for/aop', // Include paths restricts the directories where aspects should be applied, or empty for all source files 'includePaths' => array( __DIR__ . '/../src/' ) )); ``` ### 4. Create an aspect Aspect is the key element of AOP philosophy. And Go! AOP library just uses simple PHP classes for declaring aspects! Therefore it's possible to use all features of OOP for aspect classes. As an example let's intercept all the methods and display their names: ``` php // Aspect/MonitorAspect.php namespace Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\After; use Go\Lang\Annotation\Before; use Go\Lang\Annotation\Around; use Go\Lang\Annotation\Pointcut; /** * Monitor aspect */ class MonitorAspect implements Aspect { /** * Method that will be called before real method * * @param MethodInvocation $invocation Invocation * @Before("execution(public Example->*(*))") */ public function beforeMethodExecution(MethodInvocation $invocation) { $obj = $invocation->getThis(); echo 'Calling Before Interceptor for method: ', is_object($obj) ? get_class($obj) : $obj, $invocation->getMethod()->isStatic() ? '::' : '->', $invocation->getMethod()->getName(), '()', ' with arguments: ', json_encode($invocation->getArguments()), "
\n"; } } ``` Easy, isn't it? We declared here that we want to install a hook before the execution of all dynamic public methods in the class Example. This is done with the help of annotation `@Before("execution(public Example->*(*))")` Hooks can be of any types, you will see them later. But we doesn't change any code in the class Example! I can feel you astonishment now ) ### 5. Register the aspect in the aspect kernel To register the aspect just add an instance of it in the `configureAop()` method of the kernel: ``` php // app/ApplicationAspectKernel.php use Aspect/MonitorAspect; //... protected function configureAop(AspectContainer $container) { $container->registerAspect(new MonitorAspect()); } //... ``` Now you are ready to use the power of aspects! Feel free to change anything everywhere. Documentation ------------- Documentation about Go! library can be found at [official site][1]. [1]: http://go.aopphp.com composer.json000064400000001471144760114140007273 0ustar00{ "name": "lisachenko/go-aop-php", "description": "Library for aspect-oriented programming in PHP.", "type": "library", "keywords": ["library", "aop", "php", "aspect"], "homepage": "http://go.aopphp.com/", "license": "MIT", "require": { "php": ">=5.3.0", "andrewsville/php-token-reflection": "~1.3", "jakubledl/dissect": "~1.0", "doctrine/annotations": "~1.0" }, "authors": [ { "name": "Lissachenko Alexander", "homepage": "https://github.com/lisachenko" } ], "autoload": { "psr-0": { "Go": "src/" }, "files": ["src/Go/Instrument/ClassLoading/AopComposerLoader.php"] }, "extra": { "branch-alias": { "dev-master": "0.4-dev" } } } demos/Demo/Annotation/Cacheable.php000064400000000704144760114140013214 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Annotation; use Doctrine\Common\Annotations\Annotation; /** * @Annotation * @Target("METHOD") */ class Cacheable extends Annotation { /** * Time to cache * * @var int */ public $time = 0; } demos/Demo/Aspect/AwesomeAspectKernel.php000064400000001445144760114140014376 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Aspect; use Go\Core\AspectKernel; use Go\Core\AspectContainer; /** * Awesome Aspect Kernel class */ class AwesomeAspectKernel extends AspectKernel { /** * Configure an AspectContainer with advisors, aspects and pointcuts * * @param AspectContainer $container * * @return void */ protected function configureAop(AspectContainer $container) { $container->registerAspect(new DebugAspect()); $container->registerAspect(new FluentInterfaceAspect()); $container->registerAspect(new HealthyLiveAspect()); } } demos/Demo/Aspect/DebugAspect.php000064400000010703144760114140012660 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\FunctionInvocation; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\After; use Go\Lang\Annotation\Before; use Go\Lang\Annotation\Around; use Go\Lang\Annotation\Pointcut; use Go\Lang\Annotation\DeclareParents; use Go\Lang\Annotation\DeclareError; /** * Debug aspect */ class DebugAspect implements Aspect { /** * Message to show when calling the method * * @var string * @DeclareError("execution(public Demo\Example\General->cacheMe(*))") */ protected $message = 'Method should not be called in debug mode'; /** * @DeclareParents( * value="class(Demo\Example\General)", * interface="Serializable", * defaultImpl="Demo\Aspect\Introduce\SerializableImpl" * ) * * @var null */ protected $introduction = null; /** * Pointcut for example class * * @Pointcut("execution(public Demo\Example\General->*(*))") */ protected function examplePublicMethods() {} /** * Method that should be called before real method * * @param MethodInvocation $invocation Invocation * @Before("$this->examplePublicMethods") */ public function beforeMethodExecution(MethodInvocation $invocation) { $obj = $invocation->getThis(); echo 'Calling Before Interceptor for method: ', is_object($obj) ? get_class($obj) : $obj, $invocation->getMethod()->isStatic() ? '::' : '->', $invocation->getMethod()->getName(), '()', ' with arguments: ', json_encode($invocation->getArguments()), PHP_EOL; } /** * Method that should be called after real method * * @param MethodInvocation $invocation Invocation * @After("$this->examplePublicMethods") */ public function afterMethodExecution(MethodInvocation $invocation) { $obj = $invocation->getThis(); echo 'Calling After Interceptor for method: ', is_object($obj) ? get_class($obj) : $obj, $invocation->getMethod()->isStatic() ? '::' : '->', $invocation->getMethod()->getName(), '()', ' with arguments: ', json_encode($invocation->getArguments()), PHP_EOL; } /** * Cacheable methods * * @param MethodInvocation $invocation Invocation * * @Around("@annotation(Demo\Annotation\Cacheable)") */ public function aroundCacheable(MethodInvocation $invocation) { static $memoryCache = array(); $time = microtime(true); $obj = $invocation->getThis(); $class = is_object($obj) ? get_class($obj) : $obj; $key = $class . ':' . $invocation->getMethod()->name; if (!isset($memoryCache[$key])) { $memoryCache[$key] = $invocation->proceed(); } echo "Take ", sprintf("%0.3f", (microtime(true) - $time) * 1e3), "ms to call method", PHP_EOL; return $memoryCache[$key]; } /** * Method that should be called around property * * @param FieldAccess $property Joinpoint * * @Around("access(* Demo\Example\General->*)") * @return mixed */ public function aroundFieldAccess(FieldAccess $property) { $type = $property->getAccessType() === FieldAccess::READ ? 'read' : 'write'; $value = $property->proceed(); echo "Calling Around Interceptor for field: ", get_class($property->getThis()), "->", $property->getField()->getName(), ", access: $type", ", value: ", json_encode($value), PHP_EOL; return $value; } /** * @param FunctionInvocation $invocation * * @Around("execution(Demo\Example\*(*))") * * @return mixed */ public function aroundFunction(FunctionInvocation $invocation) { echo 'Calling Around Interceptor for function: ', $invocation->getFunction()->getName(), '()', ' with arguments: ', json_encode($invocation->getArguments()), PHP_EOL; return $invocation->proceed(); } } demos/Demo/Aspect/FluentInterface.php000064400000000522144760114140013546 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Aspect; /** * Fluent interface marker * * @author Alexander Lisachenko */ interface FluentInterface { }demos/Demo/Aspect/FluentInterfaceAspect.php000064400000001345144760114140014712 0ustar00set*(*))") * * @param MethodInvocation $invocation * @return mixed|null|object */ protected function aroundMethodExecution(MethodInvocation $invocation) { $result = $invocation->proceed(); return $result!==null ? $result : $invocation->getThis(); } }demos/Demo/Aspect/HealthyLiveAspect.php000064400000003342144760114140014051 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\After; use Go\Lang\Annotation\Before; use Go\Lang\Annotation\Pointcut; /** * Healthy live aspect */ class HealthyLiveAspect implements Aspect { /** * Pointcut for eat method * * @Pointcut("execution(public Demo\Example\Human->eat(*))") */ protected function humanEat() {} /** * Washing hands before eating * * @param MethodInvocation $invocation Invocation * @Before("$this->humanEat") */ protected function washUpBeforeEat(MethodInvocation $invocation) { /** @var $person \Demo\Example\Human */ $person = $invocation->getThis(); $person->washUp(); } /** * Method that advices to clean the teeth after eating * * @param MethodInvocation $invocation Invocation * @After("$this->humanEat") */ protected function cleanTeethAfterEat(MethodInvocation $invocation) { /** @var $person \Demo\Example\Human */ $person = $invocation->getThis(); $person->cleanTeeth(); } /** * Method that advice to clean the teeth before going to sleep * * @param MethodInvocation $invocation Invocation * @Before("execution(public Demo\Example\Human->sleep(*))") */ protected function cleanTeethBeforeSleep(MethodInvocation $invocation) { /** @var $person \Demo\Example\Human */ $person = $invocation->getThis(); $person->cleanTeeth(); } } demos/Demo/Aspect/Introduce/SerializableImpl.php000064400000001624144760114140015660 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Aspect\Introduce; /** * Example class to test aspects */ trait SerializableImpl { /** * String representation of object * @return string the string representation of the object or null */ public function serialize() { return serialize(get_object_vars($this)); } /** * Constructs the object * @param string $serialized

* The string representation of the object. *

* @return mixed the original value unserialized. */ public function unserialize($serialized) { $data = unserialize($serialized); foreach($data as $key=>$value) { $this->$key = $value; } } } demos/Demo/Example/General.php000064400000005321144760114140012223 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Example; use Demo\Annotation\Cacheable; /** * Example class to test aspects */ class General { /** * Message for example * * @var string */ protected $message = ''; /** * Public message to read/write * * @var string */ public $publicMessage = ''; /** * Constructs example class * * @param string $message Message to show */ public function __construct($message) { $this->message = $message; } /** * Protected method that shows hello */ protected function protectedHello() { echo 'Hello, you have a protected message: ', $this->message, "
", PHP_EOL; } /** * Public method that shows hello */ public function publicHello() { echo 'Hello, you have a public message: ', $this->message, "
", PHP_EOL; } /** * Public method to test interception of protected method */ public function runProtectedHello() { echo 'Calling protected method...', "
", PHP_EOL; $this->protectedHello(); } /** * Protected method to return the value of message * * @return string */ protected function getProtectedMessage() { return $this->message; } /** * Executes a protected method to read the message and show it */ public function showProtectedMessage() { echo 'Getting value from protected method...', "
", PHP_EOL; $message = $this->getProtectedMessage(); echo 'Hello, have you read that message: ', $message, "
", PHP_EOL; } /** * Test function for static method inter * * @param string $message Message to show */ public static function showStaticMessage($message) { echo "Static message is: ", $message, "
", PHP_EOL; echo "Scope is: ", get_called_class(), "
", PHP_EOL; } /** * Test function for recursive calls * * @param integer $level Level of recursion */ public function testRecursion($level) { echo "Recursive call, level is: ", $level, "
", PHP_EOL; if ($level > 0) { $this->testRecursion($level - 1); } } /** * Test cacheable by annotation * * @Cacheable * @param float $timeToSleep Amount of time to sleep * * @return string */ public function cacheMe($timeToSleep) { usleep($timeToSleep * 1e6); return 'Yeah'; } } demos/Demo/Example/Human.php000064400000001537144760114140011723 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Example; /** * Human class example */ class Human { /** * Eat something */ public function eat() { echo "Eating...", PHP_EOL; } /** * Clean the teeth */ public function cleanTeeth() { echo "Cleaning teeth...", PHP_EOL; } /** * Washing up */ public function washUp() { echo "Washing up...", PHP_EOL; } /** * Working */ public function work() { echo "Working...", PHP_EOL; } /** * Go to sleep */ public function sleep() { echo "Go to sleep...", PHP_EOL; } } demos/Demo/Example/User.php000064400000001464144760114140011570 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Demo\Example; use Demo\Aspect\FluentInterface; /** * Example class to show fluent interface in action */ class User implements FluentInterface { protected $name; protected $surname; protected $password; public function setName($name) { $this->name = $name; } public function setSurname($surname) { $this->surname = $surname; } public function setPassword($password) { if (!$password) { throw new \InvalidArgumentException("Password shouldn't be empty"); } $this->password = $password; } } demos/autoload.php000064400000001215144760114140010175 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ /** * Show all errors in code */ ini_set('display_errors', true); // Composer autoloading if (file_exists(__DIR__ . '/../vendor/autoload.php')) { /** @var Composer\Autoload\ClassLoader $loader */ $loader = include __DIR__ . '/../vendor/autoload.php'; $loader->add('Demo', __DIR__); } if (php_sapi_name()!='cli') { ob_start(function($content) { return str_replace(PHP_EOL, "
" . PHP_EOL, $content); }); } demos/autoload_aspect.php000064400000001315144760114140011535 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ use Doctrine\Common\Annotations\AnnotationRegistry; use Demo\Aspect\AwesomeAspectKernel; include __DIR__ .'/autoload.php'; // Initialize demo aspect container AwesomeAspectKernel::getInstance()->init(array( 'debug' => true, 'appDir' => __DIR__ . '/../demos', 'cacheDir' => __DIR__ . '/cache', 'interceptFunctions' => true, // Enable support for function interception (Since 0.4.0) )); AnnotationRegistry::registerFile(__DIR__ . './Demo/Annotation/Cacheable.php');demos/cache/.gitignore000064400000000001144760114140010677 0ustar00*demos/cache/.gitkeep000064400000000000144760114140010337 0ustar00demos/index.php000064400000001344144760114140007477 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ include __DIR__ . (isset($_GET['original']) ? './autoload.php' : './autoload_aspect.php'); $example = new Demo\Example\General('test'); $class = new ReflectionObject($example); if ($example instanceof Serializable) { echo "Yeah, Example is serializable!", PHP_EOL; var_dump($class->getTraitNames(), $class->getInterfaceNames()); } else { echo "Ooops, {$class->name} isn't serializable!", PHP_EOL; } unserialize(serialize($example)); $example->publicHello(); for ($i=10; $i--; ) { $example->cacheMe(0.2); } demos/life.php000064400000001306144760114140007305 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ include __DIR__ . (isset($_GET['original']) ? './autoload.php' : './autoload_aspect.php'); // Test case with human $man = new Demo\Example\Human(); echo "Want to eat something, let's have a breakfast!", PHP_EOL; $man->eat(); echo "I should work to earn some money", PHP_EOL; $man->work(); echo "It was a nice day, go to bed", PHP_EOL; $man->sleep(); // Test case for fluent interface $user = new Demo\Example\User(); $user ->setName('test') ->setSurname('a') ->setPassword('root'); phpunit.xml.dist000064400000001710144760114140007720 0ustar00 ./tests/Go/ ./src/Go/ performance src/Go/Aop/Advice.php000064400000000637144760114140010333 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Tag interface for Advice. Implementations can be any type of advice, such as Interceptors. * * @author Rod Johnson * @author Lissachenko Alexander */ interface Advice { } src/Go/Aop/AdviceAfter.php000064400000000635144760114140011313 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; use Go\Aop\Advice; /** * Tag class for all after advices either for field access or method calling * @author Lissachenko Alexander */ interface AdviceAfter extends Advice { } src/Go/Aop/AdviceAround.php000064400000000637144760114140011504 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; use Go\Aop\Advice; /** * Tag class for all around advices either for field access or method calling * @author Lissachenko Alexander */ interface AdviceAround extends Advice { } src/Go/Aop/AdviceBefore.php000064400000000637144760114140011456 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; use Go\Aop\Advice; /** * Tag class for all before advices either for field access or method calling * @author Lissachenko Alexander */ interface AdviceBefore extends Advice { } src/Go/Aop/Advisor.php000064400000001660144760114140010544 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Base interface holding AOP advice (action to take at a joinpoint) and a filter determining the * applicability of the advice (such as a pointcut). */ interface Advisor { /** * Return the advice part of this aspect. An advice may be an interceptor, a before advice, a throws advice, etc. * * @return Advice The advice that should apply if the pointcut matches */ public function getAdvice(); /** * Return whether this advice is associated with a particular instance or shared with all instances * of the advised class * * @return bool Whether this advice is associated with a particular target instance */ public function isPerInstance(); } src/Go/Aop/Aspect.php000064400000001141144760114140010346 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Aspect class realization for PHP * * Aspect is a module that encapsulates a concern. An aspect is composed of pointcuts, advice bodies and inter-type * declarations. In some approaches, and aspect may also contain classes and methods. * * @link http://en.wikipedia.org/wiki/Aspect_%28computer_science%29 * @package go * @subpackage aop */ interface Aspect { } src/Go/Aop/AspectException.php000064400000000676144760114140012241 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; use RuntimeException; /** * Superclass for all AOP infrastructure exceptions * * @author Rod Johnson * @author Bob Lee * @author Lissachenko Alexander */ class AspectException extends RuntimeException { } src/Go/Aop/Framework/AbstractInvocation.php000064400000001661144760114140014670 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Invocation; /** * Abstract class for all invocations joinpoints * * It is an implementation of Go\Aop\Intercept\Invocation interface * * @see Go\Aop\Intercept\Invocation * @package go */ abstract class AbstractInvocation extends AbstractJoinpoint implements Invocation { /** * Arguments for invocation * * @var array */ protected $arguments = array(); /** * Get the arguments as an array object. * It is possible to change element values within this array to change the arguments * * @return array the arguments of the invocation */ public function getArguments() { return $this->arguments; } } src/Go/Aop/Framework/AbstractJoinpoint.php000064400000005030144760114140014522 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Advice; use Go\Aop\AdviceAfter; use Go\Aop\AdviceBefore; use Go\Aop\AdviceAround; use Go\Aop\Intercept\Joinpoint; /** * Abstract joinpoint for framework * * Join points are points in the execution of the system, such as method calls, * where behavior supplied by aspects is combined. A join point is a point in * the execution of the program, which is used to define the dynamic structure * of a crosscutting concern. * * @link http://en.wikipedia.org/wiki/Aspect-oriented_software_development#Join_point_model * @package go */ abstract class AbstractJoinpoint implements Joinpoint { /** * List of advices * * @var array|Advice[] */ protected $advices = array(); /** * Current advice index * * @var int */ protected $current = 0; /** * Stack frames to work with recursive calls or with cross-calls inside object * * @var array */ protected $stackFrames = array(); /** * Recursion level for invocation * * @var int */ protected $level = 0; /** * Initializes list of advices for current joinpoint * * @param array $advices List of advices */ public function __construct(array $advices) { $this->advices = $advices; } /** * Sorts advices by priority * * @param array|Advice[] $advices * @return array|Advice[] Sorted list of advices */ static public function sortAdvices(array $advices) { $sortedAdvices = $advices; usort($sortedAdvices, function(Advice $first, Advice $second) { switch (true) { case $first instanceof AdviceBefore && !($second instanceof AdviceBefore): return -1; case $first instanceof AdviceAround && !($second instanceof AdviceAround): return 1; case $first instanceof AdviceAfter && !($second instanceof AdviceAfter): return $second instanceof AdviceBefore ? 1 : -1; case ($first instanceof OrderedAdvice && $second instanceof OrderedAdvice): return $first->getAdviceOrder() - $second->getAdviceOrder(); default: return 0; } }); return $sortedAdvices; } } src/Go/Aop/Framework/AbstractMethodInvocation.php000064400000006234144760114140016032 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use ReflectionMethod; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Intercept\MethodInterceptor; /** * Abstract method invocation implementation * * @see Go\Aop\Intercept\MethodInvocation * @package go */ abstract class AbstractMethodInvocation extends AbstractInvocation implements MethodInvocation { /** * Instance of object for invoking or null * * @var object */ protected $instance = null; /** * Instance of reflection method for class * * @var null|ReflectionMethod */ protected $reflectionMethod = null; /** * Name of the invocation class * * @var string */ protected $className = ''; /** * Constructor for method invocation * * @param string $className Class name * @param string $methodName Method to invoke * @param $advices array List of advices for this invocation */ public function __construct($className, $methodName, array $advices) { parent::__construct($advices); $this->className = $className; $this->reflectionMethod = $method = new ReflectionMethod($this->className, $methodName); // Give an access to call protected method if ($method->isProtected()) { $method->setAccessible(true); } } /** * Gets the method being called. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionMethod the method being called. */ public function getMethod() { return $this->reflectionMethod; } /** * Returns the object that holds the current joinpoint's static * part. * *

For instance, the target object for an invocation. * * @return object|null the object (can be null if the accessible object is * static). */ public function getThis() { return $this->instance; } /** * Returns the static part of this joinpoint. * *

The static part is an accessible object on which a chain of * interceptors are installed. * @return object */ public function getStaticPart() { return $this->getMethod(); } /** * Invokes current method invocation with all interceptors * * @return mixed */ final public function __invoke($instance = null, array $arguments = array()) { if ($this->level) { array_push($this->stackFrames, array($this->arguments, $this->instance, $this->current)); } ++$this->level; $this->current = 0; $this->instance = $instance; $this->arguments = $arguments; $result = $this->proceed(); --$this->level; if ($this->level) { list($this->arguments, $this->instance, $this->current) = array_pop($this->stackFrames); } return $result; } } src/Go/Aop/Framework/BaseAdvice.php000064400000014432144760114140013061 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use ReflectionFunction; use ReflectionMethod; use Go\Aop\Aspect; use Go\Aop\Intercept\Joinpoint; use Go\Core\AspectKernel; /** * Base class for all framework advices implementations * * This class describe an action taken by the AOP framework at a particular * joinpoint. Different types of advice include "around", "before" and "after" * advices. * * Around advice is an advice that surrounds a joinpoint such as a method * invocation. This is the most powerful kind of advice. Around advices will * perform custom behavior before and after the method invocation. They are * responsible for choosing whether to proceed to the joinpoint or to shortcut * executing by returning their own return value or throwing an exception. * After and before advices are simple closures that will be invoked after and * before main invocation. * Framework model an advice as an PHP-closure interceptor, maintaining a * chain of interceptors "around" the joinpoint: * function(Joinpoint $joinPoint) { * echo 'Before action'; * // call chain here with Joinpoint->proceed() method * $result = $joinPoint->proceed(); * echo 'After action'; * return $result; * } * @author Lissachenko Alexander */ abstract class BaseAdvice implements OrderedAdvice { /** * Advice order * * @var int */ protected $order = 0; /** * Local cache of advices for faster unserialization on big projects * * @var array */ protected static $localAdvicesCache = array(); /** * Returns the advice order * * @return int */ public function getAdviceOrder() { return $this->order; } /** * Serialize advice method into array * * @param callable|\Closure $adviceMethod An advice for aspect * * @return array */ public static function serializeAdvice($adviceMethod) { $refAdvice = new ReflectionFunction($adviceMethod); $refVariables = $refAdvice->getStaticVariables(); $scope = 'aspect'; if (isset($refVariables['scope'])) { $scope = $refVariables['scope']; $refAdvice = new ReflectionFunction($refVariables['adviceCallback']); } if (IS_MODERN_PHP) { $method = $refAdvice; $aspect = $refAdvice->getClosureThis(); } else { $vars = $refAdvice->getStaticVariables(); $method = $vars['refMethod']; $aspect = $vars['aspect']; } return array( 'scope' => $scope, 'method' => $method->name, 'aspect' => get_class($aspect) ); } /** * Unserialize an advice * * @param array $adviceData Information about advice * * @return callable|\Closure */ public static function unserializeAdvice($adviceData) { $aspectName = $adviceData['aspect']; $methodName = $adviceData['method']; $scope = $adviceData['scope']; if (!isset(static::$localAdvicesCache["$aspectName->$methodName"]['aspect'])) { $refMethod = new ReflectionMethod($aspectName, $methodName); $aspect = AspectKernel::getInstance()->getContainer()->getAspect($aspectName); $advice = static::fromAspectReflection($aspect, $refMethod); static::$localAdvicesCache["$aspectName->$methodName"]['aspect'] = $advice; } if ($scope !== 'aspect' && !isset(static::$localAdvicesCache["$aspectName->$methodName"][$scope])) { $advice = static::$localAdvicesCache["$aspectName->$methodName"]['aspect']; $advice = static::createScopeCallback($advice, $scope); static::$localAdvicesCache["$aspectName->$methodName"][$scope] = $advice; } return static::$localAdvicesCache["$aspectName->$methodName"][$scope]; } /** * Returns an advice from aspect method reflection * * @param Aspect $aspect Instance of aspect * @param ReflectionMethod $refMethod Reflection method of aspect * * @return callable|object */ public static function fromAspectReflection(Aspect $aspect, ReflectionMethod $refMethod) { if (IS_MODERN_PHP) { return $refMethod->getClosure($aspect); } else { return function () use ($aspect, $refMethod) { return $refMethod->invokeArgs($aspect, func_get_args()); }; } } /** * Creates an advice with respect to the desired scope * * @param callable| $adviceCallback Advice to call * @param string $scope Scope for callback * * @throws \InvalidArgumentException is scope is not supported * @return callable */ public static function createScopeCallback($adviceCallback, $scope) { switch ($scope) { case 'aspect': return $adviceCallback; case 'proxy': return function (Joinpoint $joinpoint) use ($adviceCallback, $scope) { $instance = $joinpoint->getThis(); $isNotObject = $instance !== (object) $instance; $object = $isNotObject ? null : $instance; $target = $isNotObject ? $instance : get_class($instance); $callback = $adviceCallback->bindTo($object, $target); return $callback($joinpoint); }; case 'target': return function (Joinpoint $joinpoint) use ($adviceCallback, $scope) { $instance = $joinpoint->getThis(); $isNotObject = $instance !== (object) $instance; $object = $isNotObject ? null : $instance; $target = $isNotObject ? $instance : get_parent_class($instance); $callback = $adviceCallback->bindTo($object, $target); return $callback($joinpoint); }; default: throw new \InvalidArgumentException("Unsupported scope `{$scope}`"); } } } src/Go/Aop/Framework/BaseInterceptor.php000064400000004511144760114140014161 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Serializable; use Go\Aop\Pointcut; use Go\Aop\Intercept\Interceptor; /** * @package go */ class BaseInterceptor extends BaseAdvice implements Interceptor, Serializable { /** * Name of the aspect * * @var string */ public $aspectName = ''; /** * Pointcut instance * * @var null|Pointcut */ public $pointcut = null; /** * Advice to call * * In Spring it's ReflectionMethod, but this will be slowly * * @var null|\Closure */ protected $adviceMethod = null; /** * Default constructor for interceptor * * @param callable $adviceMethod Interceptor advice to call * @param integer $order Order of interceptor * @param Pointcut $pointcut Pointcut instance where interceptor should be called */ public function __construct($adviceMethod, $order = 0, Pointcut $pointcut = null) { assert('is_callable($adviceMethod) /* Advice method should be callable */'); $this->adviceMethod = $adviceMethod; $this->order = $order; $this->pointcut = $pointcut; } /** * Getter for extracting the advice closure from Interceptor * * @return callable|null */ public function getRawAdvice() { return $this->adviceMethod; } /** * Serializes an interceptor into string representation * * @return string the string representation of the object or null */ public function serialize() { $vars = array_filter(get_object_vars($this)); $vars['adviceMethod'] = static::serializeAdvice($this->adviceMethod); return serialize($vars); } /** * Unserialize an interceptor from the string * * @param string $serialized The string representation of the object. * @return void */ public function unserialize($serialized) { $vars = unserialize($serialized); $vars['adviceMethod'] = static::unserializeAdvice($vars['adviceMethod']); foreach ($vars as $key=>$value) { $this->$key = $value; } } } src/Go/Aop/Framework/ClassFieldAccess.php000064400000011716144760114140014230 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use ReflectionProperty; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\FieldInterceptor; /** * @package go */ class ClassFieldAccess extends AbstractJoinpoint implements FieldAccess { /** * Mapping of access type with method to invoke for interceptor * * @var array */ private static $interceptorMethodMapping = array( self::READ => 'get', self::WRITE => 'set' ); /** * Instance of object for accessing * * @var object */ protected $instance = null; /** * Instance of reflection property * * @var null|\ReflectionProperty */ protected $reflectionProperty = null; /** * New value to set * * @var mixed */ protected $newValue = null; /** * Access type for field access * * @var null|integer */ private $accessType = null; /** * Copy of the original value of property * * @var mixed */ private $value = null; /** * Constructor for field access * * @param string $className Class name * @param string $fieldName Field name * @param $advices array List of advices for this invocation */ public function __construct($className, $fieldName, array $advices) { parent::__construct($advices); $this->reflectionProperty = $reflectionProperty = new ReflectionProperty($className, $fieldName); // Give an access to protected field if ($reflectionProperty->isProtected()) { $reflectionProperty->setAccessible(true); } } /** * Returns the access type. * * @return integer */ public function getAccessType() { return $this->accessType; } /** * Gets the field being accessed. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionProperty the field being accessed. */ public function getField() { return $this->reflectionProperty; } /** * Gets the value that must be set to the field. * *

This value can be intercepted and changed by a field * interceptor. * @return mixed */ public function getValueToSet() { return $this->newValue; } /** * Proceed to the next interceptor in the Chain * * Typically this method is called inside previous closure, as instance of Joinpoint is passed to callback * Do not call this method directly, only inside callback closures. * * @return mixed */ final public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor FieldInterceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->{self::$interceptorMethodMapping[$this->accessType]}($this); } if ($this->accessType === self::WRITE) { return $this->getValueToSet(); } return $this->value; } /** * Invokes current field access with all interceptors * * @param object $instance Instance of object * @param integer $accessType Type of access: READ or WRITE * @param mixed $originalValue Original value of property * @param mixed $newValue New value to set * * @return mixed */ final public function __invoke($instance, $accessType, $originalValue, $newValue = NAN) { if ($this->level) { array_push($this->stackFrames, array($this->instance, $this->accessType, $this->value, $this->newValue)); } ++$this->level; $this->current = 0; $this->instance = $instance; $this->accessType = $accessType; $this->value = $originalValue; $this->newValue = $newValue; $result = $this->proceed(); --$this->level; if ($this->level) { list($this->instance, $this->accessType, $this->value, $this->newValue) = array_pop($this->stackFrames); } return $result; } /** * Returns the object that holds the current joinpoint's static * part. * *

For instance, the target object for an invocation. * * @return object|null the object (can be null if the accessible object is * static). */ final public function getThis() { return $this->instance; } /** * Returns the static part of this joinpoint. * *

The static part is an accessible object on which a chain of * interceptors are installed. * @return object */ final public function getStaticPart() { return $this->getField(); } } src/Go/Aop/Framework/ClosureDynamicMethodInvocation.php000064400000004354144760114140017211 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\MethodInterceptor; /** * NB: This invocation is only used for dynamic methods to support LSB * * @package go */ class ClosureDynamicMethodInvocation extends AbstractMethodInvocation { /** * Closure to use * * @var \Closure */ private $closureToCall = null; /** * Previous scope of invocation * * @var null */ private $previousInstance = null; /** * Invokes original method and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor MethodInterceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->invoke($this); } // Fill the closure only once if it's empty if (!$this->closureToCall) { $this->closureToCall = $this->reflectionMethod->getClosure($this->instance); } // Rebind the closure if instance was changed since last time if ($this->previousInstance !== $this->instance) { $this->closureToCall = $this->closureToCall->bindTo($this->instance, $this->className); $this->previousInstance = $this->instance; } $closureToCall = $this->closureToCall; $args = $this->arguments; switch(count($args)) { case 0: return $closureToCall(); case 1: return $closureToCall($args[0]); case 2: return $closureToCall($args[0], $args[1]); case 3: return $closureToCall($args[0], $args[1], $args[2]); case 4: return $closureToCall($args[0], $args[1], $args[2], $args[3]); case 5: return $closureToCall($args[0], $args[1], $args[2], $args[3], $args[4]); default: return forward_static_call_array($closureToCall, $args); } } } src/Go/Aop/Framework/ClosureStaticMethodInvocation.php000064400000004157144760114140017055 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\MethodInterceptor; /** * NB: This invocation is only used for static methods to support LSB * * @package go */ class ClosureStaticMethodInvocation extends AbstractMethodInvocation { /** * Closure to use * * @var \Closure */ private $closureToCall = null; /** * Previous scope of invocation * * @var null */ private $previousScope = null; /** * {@inheritdoc} */ public function __construct($className, $methodName, array $advices) { parent::__construct($className, $methodName, $advices); $this->closureToCall = $this->getStaticInvoker($this->className, $methodName); } /** * Invokes original method and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor MethodInterceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->invoke($this); } // Rebind the closure if scope (class name) was changed since last time if ($this->previousScope !== $this->instance) { $this->closureToCall = $this->closureToCall->bindTo(null, $this->instance); $this->previousScope = $this->instance; } $closureToCall = $this->closureToCall; return $closureToCall($this->arguments); } /** * Returns static method invoker * * @param string $className Class name to forward request * @param string $method Method name to call * * @return \Closure */ protected static function getStaticInvoker($className, $method) { return function (array $args) use ($className, $method) { return forward_static_call_array(array($className, $method), $args); }; } } src/Go/Aop/Framework/ConstructorAfterInterceptor.php000064400000003000144760114140016606 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAfter; use Go\Aop\Intercept\ConstructorInvocation; use Go\Aop\Intercept\ConstructorInterceptor; /** * @package go */ class ConstructorAfterInterceptor extends BaseInterceptor implements ConstructorInterceptor, AdviceAfter { /** * Implement this method to perform extra treatments before and * after the construction of a new object. Polite implementations * would certainly like to invoke {@link Joinpoint::proceed()}. * * @param ConstructorInvocation $invocation the construction joinpoint * @throws Exception if exception was thrown in constructor * * @return mixed the newly created object, which is also the result of * the call to {@link Joinpoint::proceed()}, might be replaced by * the interceptor. */ final public function construct(ConstructorInvocation $invocation) { $result = null; try { $result = $invocation->proceed(); } catch (Exception $invocationException) { // this is need for finally emulation in PHP } $adviceMethod = $this->adviceMethod; $adviceMethod($invocation); if (isset($invocationException)) { throw $invocationException; } return $result; } } src/Go/Aop/Framework/ConstructorBeforeInterceptor.php000064400000002222144760114140016754 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\AdviceBefore; use Go\Aop\Intercept\ConstructorInvocation; use Go\Aop\Intercept\ConstructorInterceptor; /** * @package go */ class ConstructorBeforeInterceptor extends BaseInterceptor implements ConstructorInterceptor, AdviceBefore { /** * Implement this method to perform extra treatments before and * after the construction of a new object. Polite implementations * would certainly like to invoke {@link Joinpoint::proceed()}. * * @param ConstructorInvocation $invocation the construction joinpoint * @return mixed the newly created object, which is also the result of * the call to {@link Joinpoint::proceed()}, might be replaced by * the interceptor. */ final public function construct(ConstructorInvocation $invocation) { $adviceMethod = $this->adviceMethod; $adviceMethod($invocation); return $invocation->proceed(); } } src/Go/Aop/Framework/DeclareErrorInterceptor.php000064400000006417144760114140015667 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\MethodInterceptor; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Pointcut; /** * @package go */ class DeclareErrorInterceptor extends BaseInterceptor implements MethodInterceptor { /** * Error message to show for this interceptor * * @var string */ private $message = ''; /** * Default level of error * * @var int */ private $level = E_USER_NOTICE; /** * Default constructor for interceptor * * @param string $message Text message for error * @param int $level Level of error * @param Pointcut $pointcut Pointcut instance where interceptor should be called */ public function __construct($message, $level, Pointcut $pointcut = null) { $adviceMethod = $this->getDeclareErrorAdvice(); $this->message = $message; $this->level = $level; parent::__construct($adviceMethod, -256, $pointcut); } /** * Serializes an interceptor into string representation * * @return string the string representation of the object or null */ public function serialize() { $vars = array_filter(get_object_vars($this)); unset($vars['adviceMethod']); return serialize($vars); } /** * Unserialize an interceptor from the string * * @param string $serialized The string representation of the object. * @return void */ public function unserialize($serialized) { $vars = unserialize($serialized); $vars['adviceMethod'] = $this->getDeclareErrorAdvice(); foreach ($vars as $key=>$value) { $this->$key = $value; } } /** * Returns an advice * * @return callable */ private function getDeclareErrorAdvice() { static $adviceMethod = null; if (!$adviceMethod) { $adviceMethod = function ($object, $reflector, $message, $level = E_USER_NOTICE) { $class = is_string($object) ? $object : get_class($object); $name = $reflector->getName(); $message = vsprintf('[AOP Declare Error]: %s has an error: "%s"', array( $class . '->' . $name, $message )); trigger_error($message, $level); }; } return $adviceMethod; } /** * Implement this method to perform extra treatments before and * after the invocation. Polite implementations would certainly * like to invoke {@link Joinpoint::proceed()}. * * @param MethodInvocation $invocation the method invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()}, * might be intercepted by the interceptor. */ public function invoke(MethodInvocation $invocation) { $this->adviceMethod->__invoke( $invocation->getThis(), $invocation->getMethod(), $this->message, $this->level ); return $invocation->proceed(); } } src/Go/Aop/Framework/DynamicMethodMatcherInterceptor.php000064400000004301144760114140017335 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Intercept\MethodInterceptor; use Go\Aop\Support\DynamicMethodMatcher; /** * Dynamic method matcher combines a pointcut and interceptor. * * For each invocation interceptor asks the pointcut if it matches the invocation. * Matcher will receive reflection point, object instance and invocation arguments to make a decision * * @package go */ class DynamicMethodMatcherInterceptor extends BaseInterceptor implements MethodInterceptor { /** * Dynamic matcher constructor * * @param DynamicMethodMatcher $matcher Instance of dynamic matcher * @param MethodInterceptor $interceptor Instance of method interceptor to invoke */ public function __construct(DynamicMethodMatcher $matcher, MethodInterceptor $interceptor) { $this->pointcut = $matcher; $this->adviceMethod = $interceptor; } /** * Serializes an interceptor into string representation * * @return string the string representation of the object or null */ public function serialize() { return serialize(array($this->pointcut, $this->adviceMethod)); } /** * Unserialize an interceptor from the string * * @param string $serialized The string representation of the object. * @return void */ public function unserialize($serialized) { list($this->pointcut, $this->adviceMethod) = unserialize(($serialized)); } /** * Method invoker * * @param $invocation MethodInvocation the method invocation joinpoint * @return mixed the result of the call to {@see Joinpoint->proceed()} */ final public function invoke(MethodInvocation $invocation) { if ($this->pointcut->matches($invocation->getMethod(), $invocation->getThis(), $invocation->getArguments())) { return $this->adviceMethod->invoke($invocation); } else { return $invocation->proceed(); } } } src/Go/Aop/Framework/FieldAfterInterceptor.php000064400000003300144760114140015307 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\AdviceAfter; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\FieldInterceptor; /** * @package go */ class FieldAfterInterceptor extends BaseInterceptor implements FieldInterceptor, AdviceAfter { /** * Do the stuff you want to do before and after the * field is getted. * *

Polite implementations would certainly like to call * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldRead the joinpoint that corresponds to the field read * @return mixed the result of the field read {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function get(FieldAccess $fieldRead) { $value = $fieldRead->proceed(); $adviceMethod = $this->adviceMethod; $adviceMethod($fieldRead); return $value; } /** * Do the stuff you want to do before and after the * field is setted. * *

Polite implementations would certainly like to implement * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldWrite the joinpoint that corresponds to the field write * @return mixed the result of the field set {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function set(FieldAccess $fieldWrite) { $value = $fieldWrite->proceed(); $adviceMethod = $this->adviceMethod; $adviceMethod($fieldWrite); return $value; } } src/Go/Aop/Framework/FieldAroundInterceptor.php000064400000003116144760114140015503 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\AdviceAround; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\FieldInterceptor; /** * @package go */ class FieldAroundInterceptor extends BaseInterceptor implements FieldInterceptor, AdviceAround { /** * Do the stuff you want to do before and after the * field is getted. * *

Polite implementations would certainly like to call * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldRead the joinpoint that corresponds to the field read * @return mixed the result of the field read {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function get(FieldAccess $fieldRead) { $adviceMethod = $this->adviceMethod; return $adviceMethod($fieldRead); } /** * Do the stuff you want to do before and after the * field is setted. * *

Polite implementations would certainly like to implement * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldWrite the joinpoint that corresponds to the field write * @return mixed the result of the field set {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function set(FieldAccess $fieldWrite) { $adviceMethod = $this->adviceMethod; return $adviceMethod($fieldWrite); } } src/Go/Aop/Framework/FieldBeforeInterceptor.php000064400000003217144760114140015457 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\AdviceBefore; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\FieldInterceptor; /** * @package go */ class FieldBeforeInterceptor extends BaseInterceptor implements FieldInterceptor, AdviceBefore { /** * Do the stuff you want to do before and after the * field is getted. * *

Polite implementations would certainly like to call * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldRead the joinpoint that corresponds to the field read * @return mixed the result of the field read {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function get(FieldAccess $fieldRead) { $adviceMethod = $this->adviceMethod; $adviceMethod($fieldRead); return $fieldRead->proceed(); } /** * Do the stuff you want to do before and after the * field is setted. * *

Polite implementations would certainly like to implement * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldWrite the joinpoint that corresponds to the field write * @return mixed the result of the field set {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function set(FieldAccess $fieldWrite) { $adviceMethod = $this->adviceMethod; $adviceMethod($fieldWrite); return $fieldWrite->proceed(); } } src/Go/Aop/Framework/FunctionAroundInterceptor.php000064400000001567144760114140016255 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAround; use Go\Aop\Intercept\FunctionInvocation; use Go\Aop\Intercept\FunctionInterceptor; /** * @package go */ class FunctionAroundInterceptor extends BaseInterceptor implements FunctionInterceptor, AdviceAround { /** * Around invoker * * @param $invocation FunctionInvocation the function invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()} * @throws Exception */ final public function invoke(FunctionInvocation $invocation) { $adviceMethod = $this->adviceMethod; return $adviceMethod($invocation); } } src/Go/Aop/Framework/MethodAfterInterceptor.php000064400000002237144760114140015514 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAfter; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Intercept\MethodInterceptor; /** * @package go */ class MethodAfterInterceptor extends BaseInterceptor implements MethodInterceptor, AdviceAfter { /** * After invoker * * @param $invocation MethodInvocation the method invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()} * @throws Exception */ final public function invoke(MethodInvocation $invocation) { $result = null; try { $result = $invocation->proceed(); } catch (Exception $invocationException) { // this is need for finally emulation in PHP } $adviceMethod = $this->adviceMethod; $adviceMethod($invocation); if (isset($invocationException)) { throw $invocationException; } return $result; } } src/Go/Aop/Framework/MethodAfterThrowingInterceptor.php000064400000002123144760114140017230 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAfter; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Intercept\MethodInterceptor; /** * @package go */ class MethodAfterThrowingInterceptor extends BaseInterceptor implements MethodInterceptor, AdviceAfter { /** * After throwing exception invoker * * @param $invocation MethodInvocation the method invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()} * @throws Exception */ final public function invoke(MethodInvocation $invocation) { $result = null; try { $result = $invocation->proceed(); } catch (Exception $invocationException) { $adviceMethod = $this->adviceMethod; $adviceMethod($invocation); throw $invocationException; } return $result; } } src/Go/Aop/Framework/MethodAroundInterceptor.php000064400000001551144760114140015701 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAround; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Intercept\MethodInterceptor; /** * @package go */ class MethodAroundInterceptor extends BaseInterceptor implements MethodInterceptor, AdviceAround { /** * Around invoker * * @param $invocation MethodInvocation the method invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()} * @throws Exception */ final public function invoke(MethodInvocation $invocation) { $adviceMethod = $this->adviceMethod; return $adviceMethod($invocation); } } src/Go/Aop/Framework/MethodBeforeInterceptor.php000064400000001541144760114140015652 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\AdviceBefore; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Intercept\MethodInterceptor; /** * @package go */ class MethodBeforeInterceptor extends BaseInterceptor implements MethodInterceptor, AdviceBefore { /** * Before invoker * * @param MethodInvocation $invocation the method invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()}, */ final public function invoke(MethodInvocation $invocation) { $adviceMethod = $this->adviceMethod; $adviceMethod($invocation); return $invocation->proceed(); } } src/Go/Aop/Framework/OrderedAdvice.php000064400000001015144760114140013564 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Advice; /** * Ordered advice can have a custom order to implement sorting * * @author Lissachenko Alexander */ interface OrderedAdvice extends Advice { /** * Returns the advice order * * @return int */ public function getAdviceOrder(); } src/Go/Aop/Framework/ReflectionConstructorInvocation.php000064400000007123144760114140017464 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use ReflectionClass; use ReflectionMethod; use Go\Aop\Intercept\ConstructorInvocation; use Go\Aop\Intercept\ConstructorInterceptor; /** * Reflection constructor invocation implementation * * @see Go\Aop\Intercept\ConstructorInvocation * @package go */ class ReflectionConstructorInvocation extends AbstractInvocation implements ConstructorInvocation { /** * Reflection class * * @var ReflectionClass */ protected $class = null; /** * Instance of reflection constructor for class * * @var null|ReflectionMethod */ private $constructor = null; /** * Constructor for constructor invocation :) * * @param string $className Class name * @param $advices array List of advices for this invocation */ public function __construct($className, array $advices) { $this->class = new ReflectionClass($className); parent::__construct($advices); } /** * Proceed to the next interceptor in the Chain * * Typically this method is called inside previous closure, as instance of Joinpoint is passed to callback * Do not call this method directly, only inside callback closures. * * @return mixed */ final public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor ConstructorInterceptor */ $currentInterceptor = $this->advices[$this->current]; $this->current++; return $currentInterceptor->construct($this); } return $this->constructOriginal(); } /** * Gets the constructor being called. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionMethod the constructor being called. */ public function getConstructor() { if (!$this->constructor) { $this->constructor = $this->class->getConstructor(); // Give an access to call protected constructor if ($this->constructor->isProtected()) { $this->constructor->setAccessible(true); } } return $this->constructor; } /** * Returns the object that holds the current joinpoint's static * part. * *

For instance, the target object for an invocation. * * @return object|null the object (can be null if the accessible object is * static). */ public function getThis() { return null; } /** * Returns the static part of this joinpoint. * *

The static part is an accessible object on which a chain of * interceptors are installed. * @return object */ public function getStaticPart() { return $this->getConstructor(); } /** * Invokes current constructor invocation with all interceptors * * @return mixed */ final public function __invoke() { // TODO: add support for recursion in constructors $this->current = 0; $this->arguments = func_get_args(); return $this->proceed(); } /** * Invokes original constructor and return result from it * * @return mixed */ protected function constructOriginal() { return $this->class->newInstanceArgs($this->arguments); } } src/Go/Aop/Framework/ReflectionFunctionInvocation.php000064400000006161144760114140016725 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use ReflectionFunction; use Go\Aop\Intercept\FunctionInvocation; use Go\Aop\Intercept\FunctionInterceptor; /** * Function invocation implementation * * @see Go\Aop\Intercept\FunctionInvocation * @package go */ class ReflectionFunctionInvocation extends AbstractInvocation implements FunctionInvocation { /** * Instance of reflection function * * @var null|ReflectionFunction */ protected $reflectionFunction = null; /** * Constructor for function invocation * * @param string $functionName Function to invoke * @param $advices array List of advices for this invocation */ public function __construct($functionName, array $advices) { parent::__construct($advices); $this->reflectionFunction = new ReflectionFunction($functionName); } /** * Invokes original function and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor FunctionInterceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->invoke($this); } return $this->reflectionFunction->invokeArgs($this->arguments); } /** * Gets the function being called. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionFunction the method being called. */ public function getFunction() { return $this->reflectionFunction; } /** * Returns the object that holds the current joinpoint's static * part. * *

For instance, the target object for an invocation. * * @return object|null the object (can be null if the accessible object is * static). */ public function getThis() { return null; } /** * Returns the static part of this joinpoint. * *

The static part is an accessible object on which a chain of * interceptors are installed. * @return object */ public function getStaticPart() { return $this->reflectionFunction; } /** * Invokes current function invocation with all interceptors * * @param array $arguments Arguments for the invocation * * @return mixed */ final public function __invoke(array $arguments = array()) { if ($this->level) { array_push($this->stackFrames, array($this->arguments, $this->current)); } ++$this->level; $this->current = 0; $this->arguments = $arguments; $result = $this->proceed(); --$this->level; if ($this->level) { list($this->arguments, $this->current) = array_pop($this->stackFrames); } return $result; } } src/Go/Aop/Framework/ReflectionMethodInvocation.php000064400000002012144760114140016347 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\MethodInterceptor; /** * @package go */ class ReflectionMethodInvocation extends AbstractMethodInvocation { /** * Invokes original method and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor MethodInterceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->invoke($this); } // Due to bug https://bugs.php.net/bug.php?id=60968 instance shouldn't be a string $instance = ($this->instance !== (object) $this->instance) ? null : $this->instance; return $this->reflectionMethod->invokeArgs($instance, $this->arguments); } } src/Go/Aop/Framework/TraitIntroductionInfo.php000064400000002546144760114140015377 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\IntroductionInfo; /** * @package go */ class TraitIntroductionInfo implements IntroductionInfo { /** * Name of the interface to introduce * * @var string */ private $introducedInterface; /** * Name of the class with implementation (trait) * * @var string */ private $implementationClass; /** * Create a DefaultIntroductionAdvisor for the given advice. */ public function __construct($interfaceType, $implementationClass) { $this->introducedInterface = $interfaceType; $this->implementationClass = $implementationClass; } /** * Return the additional interfaces introduced by this Advisor or Advice. * * @return array|string[] the introduced interfaces */ public function getInterfaces() { return (array) $this->introducedInterface; } /** * Return the list of traits with realization of introduced interfaces * * @return array|string[] the implementations */ public function getTraits() { return (array) $this->implementationClass; } } src/Go/Aop/Intercept/ConstructorInterceptor.php000064400000002776144760114140015647 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; /** * Intercepts the construction of a new object. * *

The user should implement the {@link construct(ConstructorInvocation)} * method to modify the original behavior. E.g. the following class implements a singleton * interceptor (allows only one unique instance for the intercepted * class): * *

 * class DebuggingInterceptor implements ConstructorInterceptor {
 *   protected $instance=null;
 *
 *   public function construct(ConstructorInvocation $i) {
 *     if($this->instance==null) {
 *       return $this->instance=$i->proceed();
 *     } else {
 *       throw new \Exception("singleton does not allow multiple instance");
 *     }
 *   }
 * }
 * 
*/ interface ConstructorInterceptor extends Interceptor { /** * Implement this method to perform extra treatments before and * after the construction of a new object. Polite implementations * would certainly like to invoke {@link Joinpoint::proceed()}. * * @param ConstructorInvocation $invocation the construction joinpoint * @return mixed the newly created object, which is also the result of * the call to {@link Joinpoint::proceed()}, might be replaced by * the interceptor. */ public function construct(ConstructorInvocation $invocation); } src/Go/Aop/Intercept/ConstructorInvocation.php000064400000001532144760114140015447 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; use ReflectionMethod; /** * Description of an invocation to a constructor, given to an * interceptor upon constructor-call. * *

A constructor invocation is a joinpoint and can be intercepted * by a constructor interceptor. * * @see ConstructorInterceptor */ interface ConstructorInvocation extends Invocation { /** * Gets the constructor being called. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionMethod the constructor being called. */ public function getConstructor(); } src/Go/Aop/Intercept/FieldAccess.php000064400000002443144760114140013237 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; use ReflectionProperty; /** * This interface represents a field access in the program. * *

A field access is a joinpoint and can be intercepted by a field * interceptor. * * @see FieldInterceptor */ interface FieldAccess extends Joinpoint { /** * The read access type (see {@link getAccessType()}). */ const READ = 0; /** * The write access type (see {@link getAccessType()}). */ const WRITE = 1; /** * Gets the field being accessed. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionProperty the field being accessed. */ public function getField(); /** * Gets the value that must be set to the field. * *

This value can be intercepted and changed by a field * interceptor. * * @return mixed */ public function getValueToSet(); /** * Returns the access type. * * @return integer */ public function getAccessType(); } src/Go/Aop/Intercept/FieldInterceptor.php000064400000004176144760114140014341 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; /** * Intercepts field access on a target object. * *

The user should implement the {@link set(FieldAccess)} and * {@link get(FieldAccess)} methods to modify the original * behavior. E.g. the following class implements a tracing interceptor * (traces the accesses to the intercepted field(s)): * *

 * class TracingInterceptor implements FieldInterceptor {
 *
 *   public function set(FieldAccess $fa) {
 *     print("field ".$fa->getField()." is set with value ".
 *                        $fa->getValueToSet());
 *     $ret=$fa->proceed();
 *     print("field ".$fa->getField()." was set to value ".$ret);
 *     return $ret;
 *   }
 *
 *   public function get(FieldAccess $fa) {
 *     print("field ".$fa->getField()." is about to be read");
 *     $ret=$fa->proceed();
 *     print("field ".$fa->getField()." was read; value is ".$ret);
 *     return $ret;
 *   }
 * }
 * 
*/ interface FieldInterceptor extends Interceptor { /** * Do the stuff you want to do before and after the * field is getted. * *

Polite implementations would certainly like to call * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldRead the joinpoint that corresponds to the field read * * @return mixed the result of the field read {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function get(FieldAccess $fieldRead); /** * Do the stuff you want to do before and after the * field is setted. * *

Polite implementations would certainly like to implement * {@link Joinpoint::proceed()}. * * @param FieldAccess $fieldWrite the joinpoint that corresponds to the field write * * @return mixed the result of the field set {@link Joinpoint::proceed()}, might be intercepted by the * interceptor. */ public function set(FieldAccess $fieldWrite); }src/Go/Aop/Intercept/FunctionInterceptor.php000064400000002740144760114140015076 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; /** * Intercepts calls on an interface on its way to the target. These * are nested "on top" of the target. * *

The user should implement the {@link invoke(FunctionInvocation)} * method to modify the original behavior. E.g. the following class * implements a tracing interceptor (traces all the calls on the * intercepted method(s)): * *

 * class TracingInterceptor implements FunctionInterceptor {
 *   public function invoke(FunctionInvocation $i) {
 *     print("method ".$i->getFunction()." is called".
 *                        " with args ".$i->getArguments());
 *     $ret=$i->proceed();
 *     print("function ".$i->getFunction()." returns ".$ret);
 *     return $ret;
 *   }
 * }
 * 
*/ interface FunctionInterceptor extends Interceptor { /** * Implement this method to perform extra treatments before and * after the invocation. Polite implementations would certainly * like to invoke {@link Joinpoint::proceed()}. * * @param FunctionInvocation $invocation the function invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()}, * might be intercepted by the interceptor. */ public function invoke(FunctionInvocation $invocation); }src/Go/Aop/Intercept/FunctionInvocation.php000064400000001726144760114140014714 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; use ReflectionFunction; /** * Description of an invocation to a function, given to an interceptor * upon function-call. * *

A function invocation is a joinpoint and can be intercepted by a function * interceptor. * * @see FunctionInterceptor */ interface FunctionInvocation extends Invocation { /** * Gets the function being called. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionFunction the function being called. */ public function getFunction(); /** * Invokes current function invocation with all interceptors * * @return mixed */ public function __invoke(); } src/Go/Aop/Intercept/Interceptor.php000064400000003267144760114140013375 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; use Go\Aop\Advice; /** * This interface represents a generic interceptor. * *

A generic interceptor can intercept runtime events that occur * within a base program. Those events are materialized by (reified * in) joinpoints. Runtime joinpoints can be invocations, field * access, exceptions... * *

This interface is not used directly. Use the the sub-interfaces * to intercept specific events. For instance, the following class * implements some specific interceptors in order to implement a * debugger: * *

 * class DebuggingInterceptor implements MethodInterceptor,
 *     ConstructorInterceptor, FieldInterceptor {
 *
 *   public function invoke(MethodInvocation $i) {
 *       $this->debug($i->getMethod(), $i->getThis(), $i->getArgs());
 *       return $i->proceed();
 *   }
 *
 *   public function construct(ConstructorInvocation $i) {
 *       $this->debug($i->getConstructor(), $i->getThis(), $i->getArgs());
 *       return $i->proceed();
 *   }
 *
 *   public function get(FieldAccess $fa) {
 *       $this->debug($fa->getField(), $fa->getThis(), null);
 *       return $fa->proceed();
 *   }
 *
 *   public function set(FieldAccess $fa) {
 *       $this->debug($fa->getField(), $fa->getThis(), $fa->getValueToSet());
 *       return $fa->proceed();
 *   }
 *
 *   protected function debug($accessibleObject, $object, $value) {
 *       ...
 *   }
 * }
 * 
* * @see Joinpoint */ interface Interceptor extends Advice { }src/Go/Aop/Intercept/Invocation.php000064400000001343144760114140013201 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; /** * This interface represents an invocation in the program * *

An invocation is a joinpoint and can be intercepted by an * interceptor * * @author Rod Johnson * @author Lissachenko Alexander */ interface Invocation extends Joinpoint { /** * Get the arguments as an array object. * It is possible to change element values within this array to change the arguments * * @return array the arguments of the invocation */ public function getArguments(); }src/Go/Aop/Intercept/Joinpoint.php000064400000003512144760114140013041 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; /** * This interface represents a generic runtime joinpoint (in the AOP * terminology). * *

A runtime joinpoint is an event that occurs on a static * joinpoint (i.e. a location in a the program). For instance, an * invocation is the runtime joinpoint on a method (static joinpoint). * The static part of a given joinpoint can be generically retrieved * using the {@link getStaticPart()} method. * *

In the context of an interception framework, a runtime joinpoint * is then the reification of an access to an accessible object (a * method, a constructor, a field), i.e. the static part of the * joinpoint. It is passed to the interceptors that are installed on * the static joinpoint. * * @see Interceptor */ interface Joinpoint { /** * Proceeds to the next interceptor in the chain. * *

The implementation and the semantics of this method depends * on the actual joinpoint type (see the children interfaces). * * @return mixed see the children interfaces' proceed definition. */ public function proceed(); /** * Returns the object that holds the current joinpoint's static * part. * *

For instance, the target object for an invocation. * * @return object|null the object (can be null if the accessible object is * static). */ public function getThis(); /** * Returns the static part of this joinpoint. * *

The static part is an accessible object on which a chain of * interceptors are installed. * @return object */ public function getStaticPart(); } src/Go/Aop/Intercept/MethodInterceptor.php000064400000002736144760114140014536 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; /** * Intercepts calls on an interface on its way to the target. These * are nested "on top" of the target. * *

The user should implement the {@link invoke(MethodInvocation)} * method to modify the original behavior. E.g. the following class * implements a tracing interceptor (traces all the calls on the * intercepted method(s)): * *

 * class TracingInterceptor implements MethodInterceptor {
 *   public function invoke(MethodInvocation $i) {
 *     print("method ".$i->getMethod()." is called on ".
 *                        $i->getThis()." with args ".$i->getArguments());
 *     $ret=$i->proceed();
 *     print("method ".$i->getMethod()." returns ".$ret);
 *     return $ret;
 *   }
 * }
 * 
*/ interface MethodInterceptor extends Interceptor { /** * Implement this method to perform extra treatments before and * after the invocation. Polite implementations would certainly * like to invoke {@link Joinpoint::proceed()}. * * @param MethodInvocation $invocation the method invocation joinpoint * @return mixed the result of the call to {@link Joinpoint::proceed()}, * might be intercepted by the interceptor. */ public function invoke(MethodInvocation $invocation); }src/Go/Aop/Intercept/MethodInvocation.php000064400000001676144760114140014353 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Intercept; use ReflectionMethod; /** * Description of an invocation to a method, given to an interceptor * upon method-call. * *

A method invocation is a joinpoint and can be intercepted by a method * interceptor. * * @see MethodInterceptor */ interface MethodInvocation extends Invocation { /** * Gets the method being called. * *

This method is a friendly implementation of the * {@link Joinpoint::getStaticPart()} method (same result). * * @return ReflectionMethod the method being called. */ public function getMethod(); /** * Invokes current method invocation with all interceptors * * @return mixed */ public function __invoke(); } src/Go/Aop/IntroductionAdvisor.php000064400000002364144760114140013150 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Superinterface for advisors that perform one or more AOP introductions. * * This interface cannot be implemented directly; subinterfaces must provide the advice type * implementing the introduction. * * Introduction is the implementation of additional interfaces (not implemented by a target) via AOP advice. */ interface IntroductionAdvisor extends Advisor { /** * Return the filter determining which target classes this introduction should apply to. * * This represents the class part of a pointcut. Note that method matching doesn't make sense to introductions. * * @return PointFilter The class filter */ public function getClassFilter(); /** * Can the advised interfaces be implemented by the introduction advice? * * Invoked before adding an IntroductionAdvisor. * * @return void * @throws \InvalidArgumentException if the advised interfaces can't be implemented by the introduction advice */ public function validateInterfaces(); } src/Go/Aop/IntroductionInfo.php000064400000001740144760114140012431 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Interface supplying the information necessary to describe an introduction of trait. * * If an Advice implements this, it may be used as an introduction without an IntroductionAdvisor. * In this case, the advice is self-describing, providing not only the necessary behavior, * but describing the interfaces it introduces. */ interface IntroductionInfo extends Advice { /** * Return the additional interfaces introduced by this Advisor or Advice. * * @return array|string[] the introduced interfaces */ public function getInterfaces(); /** * Return the list of traits with realization of introduced interfaces * * @return array|string[] the implementations */ public function getTraits(); } src/Go/Aop/MethodMatcher.php000064400000003367144760114140011667 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Part of a Pointcut: Checks whether the target method is eligible for advice. * * A MethodMatcher may be evaluated statically or at runtime (dynamically). * Static matching involves method and (possibly) method attributes. Dynamic matching also makes arguments for * a particular call available, and any effects of running previous advice applying to the joinpoint. * * If an implementation returns false from its isRuntime() method, evaluation can be performed statically, * and the result will be the same for all invocations of this method, whatever their arguments. * This means that if the isRuntime() method returns false, the 3-arg matches() method will never be invoked. * * If an implementation returns true from its 2-arg matches() method and its isRuntime() method returns true, * the 3-arg matches() method will be invoked immediately before each potential execution of the related advice, * to decide whether the advice should run. All previous advice, such as earlier interceptors in an interceptor chain, * will have run, so any state changes they have produced in parameters will be available at the time of evaluation. */ interface MethodMatcher extends PointFilter { /** * Is this MethodMatcher dynamic or static * * Can be invoked when an AOP proxy is created, and need not be invoked again before each method invocation * * @return bool whether or not a runtime match via the 3-arg matches() method is required if static matching passed */ public function isRuntime(); } src/Go/Aop/PointFilter.php000064400000001547144760114140011400 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Filter that restricts matching of a pointcut or introduction to a given set of reflection points. */ interface PointFilter { const KIND_METHOD = 1; const KIND_PROPERTY = 2; const KIND_CLASS = 4; const KIND_TRAIT = 8; const KIND_FUNCTION = 16; const KIND_ALL = 31; /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point); /** * Returns the kind of point filter * * @return integer */ public function getKind(); } src/Go/Aop/Pointcut.php000064400000001355144760114140010743 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Pointcut realization for PHP * * Pointcuts are defined as a predicate over the syntax-tree of the program, and define an interface that constrains * which elements of the base program are exposed by the pointcut. A pointcut picks out certain join points and values * at those points * * @package go * @subpackage aop */ interface Pointcut extends PointFilter { /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter(); } src/Go/Aop/Pointcut/AndPointcut.php000064400000004652144760114140013176 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\AndPointFilter; /** * Signature method pointcut checks method signature (modifiers and name) to match it */ class AndPointcut implements Pointcut { /** * @var Pointcut */ protected $first; /** * @var Pointcut */ protected $second; /** * Combined class filter * * @var PointFilter|null */ protected $classFilter = null; /** * Returns pointcut kind * * @var int */ protected $kind = 0; /** * Signature method matcher constructor * * @param Pointcut $first First filter * @param Pointcut $second Second filter */ public function __construct(Pointcut $first, Pointcut $second) { $this->first = $first; $this->second = $second; $this->kind = $first->getKind() & $second->getKind(); $this->classFilter = new AndPointFilter($first->getClassFilter(), $second->getClassFilter()); } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { return $this->isMatchesPointcut($point, $this->first) && $this->isMatchesPointcut($point, $this->second); } /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter() { return $this->classFilter; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->kind; } /** * Checks if point filter matches the point * * @param \ReflectionMethod|\ReflectionProperty $point * @param Pointcut $pointcut Pointcut part * * @return bool */ protected function isMatchesPointcut($point, Pointcut $pointcut) { $preFilter = method_exists($point, 'getDeclaringClass') ? $point->getDeclaringClass() : $point->getNamespaceName(); return $pointcut->matches($point) && $pointcut->getClassFilter()->matches($preFilter); } } src/Go/Aop/Pointcut/AnnotationMethodPointcut.php000064400000003555144760114140015750 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use ReflectionMethod; use Go\Instrument\RawAnnotationReader; use TokenReflection\ReflectionMethod as ParsedReflectionMethod; /** * Annotation method pointcut checks method annotation */ class AnnotationMethodPointcut extends StaticMethodMatcherPointcut { /** * Annotation class to match * * @var string */ protected $annotationName = ''; /** * Annotation reader * * @var null|RawAnnotationReader */ protected $annotationReader = null; /** * Annotation method matcher constructor * * @param RawAnnotationReader $reader Annotation reader * @param string $annotationName Name of the annotation class to match */ public function __construct(RawAnnotationReader $reader, $annotationName) { $this->annotationName = $annotationName; $this->annotationReader = $reader; } /** * Performs matching of point of code * * @param $method ReflectionMethod|ParsedReflectionMethod Specific part of code, can be any Reflection class * * @return bool */ public function matches($method) { /** @var $method ReflectionMethod|ParsedReflectionMethod */ if (!$method instanceof ReflectionMethod && !$method instanceof ParsedReflectionMethod) { return false; } if ($method instanceof ParsedReflectionMethod) { $imports = $method->getNamespaceAliases(); $this->annotationReader->setImports($imports); } $annotation = $this->annotationReader->getMethodAnnotation($method, $this->annotationName); return (bool) $annotation; } } src/Go/Aop/Pointcut/AnnotationPropertyPointcut.php000064400000005264144760114140016353 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\TruePointFilter; use ReflectionProperty; use Go\Instrument\RawAnnotationReader; use TokenReflection\ReflectionProperty as ParsedReflectionProperty; /** * Annotation property pointcut checks property annotation */ class AnnotationPropertyPointcut implements Pointcut { /** * Filter for class * * @var null|PointFilter */ private $classFilter = null; /** * Annotation class to match * * @var string */ protected $annotationName = ''; /** * Annotation reader * * @var null|RawAnnotationReader */ protected $annotationReader = null; /** * Annotation property matcher constructor * * @param RawAnnotationReader $reader Annotation reader * @param string $annotationName Name of the annotation class to match */ public function __construct(RawAnnotationReader $reader, $annotationName) { $this->annotationName = $annotationName; $this->annotationReader = $reader; } /** * Performs matching of point of code * * @param $property ReflectionProperty|ParsedReflectionProperty Specific part of code, can be any Reflection class * * @return bool */ public function matches($property) { if (!$property instanceof ReflectionProperty && !$property instanceof ParsedReflectionProperty) { return false; } if ($property instanceof ParsedReflectionProperty) { $imports = $property->getNamespaceAliases(); $this->annotationReader->setImports($imports); } $annotation = $this->annotationReader->getPropertyAnnotation($property, $this->annotationName); return (bool) $annotation; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_PROPERTY; } /** * Set the ClassFilter to use for this pointcut. * * @param PointFilter $classFilter */ public function setClassFilter(PointFilter $classFilter) { $this->classFilter = $classFilter; } /** * Return the ClassFilter for this pointcut. * * @return PointFilter */ public function getClassFilter() { if (!$this->classFilter) { $this->classFilter = TruePointFilter::getInstance(); } return $this->classFilter; } } src/Go/Aop/Pointcut/CFlowBelowMethodPointcut.php000064400000004674144760114140015644 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use ReflectionClass; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use ReflectionMethod; /** * Flow pointcut is a dynamic checker that verifies stack trace to understand is it matches or not */ class CFlowBelowMethodPointcut extends DynamicMethodMatcherPointcut { /** * Filter for the class * * @var null|PointFilter */ protected $internalClassFilter = null; /** * Filter for the points * * @var null|PointFilter */ protected $internalPointFilter; /** * Control flow below constructor * * @param Pointcut $pointcut Instance of pointcut, that will be used for matching * @throws \InvalidArgumentException if filter doesn't support methods */ public function __construct(Pointcut $pointcut) { $this->internalClassFilter = $pointcut->getClassFilter(); $this->internalPointFilter = $pointcut; if (!($this->internalPointFilter->getKind() & PointFilter::KIND_METHOD)) { throw new \InvalidArgumentException("Only method filters are valid for control flow"); } } /** * Performs matching of point of code * * @param mixed $method Specific part of code, can be any Reflection class * @param null|string|object $instance Invocation instance or string for static calls * @param null|array $arguments Dynamic arguments for method * * @return bool */ public function matches($method, $instance = null, array $arguments = null) { // With single parameter (statically) always matches if (!$instance) { return true; } $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); foreach ($trace as $stackFrame) { if (!isset($stackFrame['class'])) { continue; } $refClass = new ReflectionClass($stackFrame['class']); if (!$this->internalClassFilter->matches($refClass)) { continue; } $refMethod = new ReflectionMethod($stackFrame['class'], $stackFrame['function']); if ($this->internalPointFilter->matches($refMethod)) { return true; } } return false; } } src/Go/Aop/Pointcut/DynamicMethodMatcherPointcut.php000064400000002500144760114140016513 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\TruePointFilter; use Go\Aop\Support\DynamicMethodMatcher; /** * Convenient superclass when we want to force subclasses to implement MethodMatcher interface, * but subclasses will want to be pointcuts. * * The getClassFilter() method can be overriden to customize ClassFilter behaviour as well. */ abstract class DynamicMethodMatcherPointcut extends DynamicMethodMatcher implements Pointcut { /** * Filter for class * * @var null|PointFilter */ private $classFilter = null; /** * Set the ClassFilter to use for this pointcut. * * @param PointFilter $classFilter */ public function setClassFilter(PointFilter $classFilter) { $this->classFilter = $classFilter; } /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter() { if (!$this->classFilter) { $this->classFilter = TruePointFilter::getInstance(); } return $this->classFilter; } } src/Go/Aop/Pointcut/FunctionPointcut.php000064400000004262144760114140014256 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use ReflectionFunction; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use TokenReflection\ReflectionFunction as ParsedReflectionFunction; /** * Signature method pointcut checks method signature (modifiers and name) to match it */ class FunctionPointcut implements Pointcut { /** * @var PointFilter */ protected $nsFilter; /** * Function name to match, can contain wildcards *,? * * @var string */ protected $functionName = ''; /** * Regular expression for matching * * @var string */ protected $regexp; /** * Function matcher constructor * * @param string $functionName Name of the function to match or glob pattern */ public function __construct($functionName) { $this->functionName = $functionName; $this->regexp = strtr(preg_quote($this->functionName, '/'), array( '\\*' => '.*?', '\\?' => '.' )); } /** * Performs matching of point of code * * @param mixed $function Specific part of code, can be any Reflection class * * @return bool */ public function matches($function) { /** @var $function ReflectionFunction|ParsedReflectionFunction */ if (!$function instanceof ReflectionFunction && !$function instanceof ParsedReflectionFunction) { return false; } return ($function->name === $this->functionName) || (bool) preg_match("/^{$this->regexp}$/", $function->name); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_FUNCTION; } /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter() { return $this->nsFilter; } public function setNamespaceFilter($nsFilter) { $this->nsFilter = $nsFilter; } } src/Go/Aop/Pointcut/NotPointcut.php000064400000003535144760114140013233 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\TruePointFilter; /** * Signature method pointcut checks method signature (modifiers and name) to match it */ class NotPointcut implements Pointcut { /** * @var Pointcut */ protected $pointcut; /** * Kind of pointcut * * @var int */ protected $kind = 0; /** * Inverse pointcut matcher * * @param Pointcut $pointcut Pointcut expression */ public function __construct(Pointcut $pointcut) { $this->pointcut = $pointcut; $this->kind = $pointcut->getKind(); } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { $preFilter = method_exists($point, 'getDeclaringClass') ? $point->getDeclaringClass() : $point->getNamespaceName(); $isMatchesPre = $this->pointcut->getClassFilter()->matches($preFilter); if (!$isMatchesPre) { return true; } $isMatchesPoint = $this->pointcut->matches($point); if (!$isMatchesPoint) { return true; } return false; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->kind; } /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter() { return TruePointFilter::getInstance(); } } src/Go/Aop/Pointcut/OrPointcut.php000064400000002317144760114140013050 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\Support\OrPointFilter; /** * Signature method pointcut checks method signature (modifiers and name) to match it */ class OrPointcut extends AndPointcut { /** * Signature method matcher constructor * * @param Pointcut $first First filter * @param Pointcut $second Second filter */ public function __construct(Pointcut $first, Pointcut $second) { $this->first = $first; $this->second = $second; $this->kind = $first->getKind() | $second->getKind(); $this->classFilter = new OrPointFilter($first->getClassFilter(), $second->getClassFilter()); } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { return $this->isMatchesPointcut($point, $this->first) || $this->isMatchesPointcut($point, $this->second); } }src/Go/Aop/Pointcut/PointcutGrammar.php000064400000021312144760114140014052 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\InheritanceClassFilter; use Go\Aop\Support\ModifierMatcherFilter; use Go\Aop\Support\SimpleNamespaceFilter; use Go\Aop\Support\TruePointFilter; use Go\Aop\Support\SimpleClassFilter; use Go\Core\AspectContainer; use Go\Instrument\RawAnnotationReader; use Dissect\Parser\Grammar; /** * Pointcut grammar defines general structure of pointcuts and rules of parsing * * @package Go\Aop\Pointcut */ class PointcutGrammar extends Grammar { /** * Constructs a pointcut grammar with AST */ public function __construct(AspectContainer $container, RawAnnotationReader $annotationReader) { $this('Empty') ->is(/* empty */); $this('Pointcut') ->is('Pointcut', '||', 'Pointcut') ->call(function($first, $_, $second) { return new OrPointcut($first, $second); }) ->is('Pointcut', '&&', 'Pointcut') ->call(function($first, $_, $second) { return new AndPointcut($first, $second); }) ->is('(', 'Pointcut', ')') ->call(function($_, $pointcut) { return $pointcut; }) ->is('!', 'Pointcut') ->call(function($_, $first) { return new NotPointcut($first); }) ->is('cflowbelow', '(', 'Pointcut', ')') ->call(function($_, $_, $pointcut) { return new CFlowBelowMethodPointcut($pointcut); }) ->is('SinglePointcut'); $this('SinglePointcut') ->is( 'execution', '(' , 'MemberModifiers', 'ClassFilter', 'MethodCall', 'NamePattern', '(', 'Arguments', ')', ')' ) ->call(function( $_, // execution node $_, // ( ModifierMatcherFilter $memberModifiers, PointFilter $classFilter, $methodCallType, $methodNamePattern, $_ // ) ) { if ($methodCallType === '::') { $memberModifiers->andMatch(\ReflectionMethod::IS_STATIC); } else { $memberModifiers->notMatch(\ReflectionMethod::IS_STATIC); } $pointcut = new SignatureMethodPointcut($methodNamePattern, $memberModifiers); $pointcut->setClassFilter($classFilter); return $pointcut; }) ->is( 'execution', '(', 'NamespacePattern', '(', 'Arguments', ')', ')' ) ->call(function( $_, // function node $_, // ( $namespacePattern ) { $lastNsPos = strrpos($namespacePattern, '\\'); $namespace = substr($namespacePattern, 0, $lastNsPos); $funcPattern = substr($namespacePattern, $lastNsPos+1); $nsFilter = new SimpleNamespaceFilter($namespace); $pointcut = new FunctionPointcut($funcPattern); $pointcut->setNamespaceFilter($nsFilter); return $pointcut; }) ->is('access', '(', 'MemberModifiers', 'ClassFilter', '->', 'NamePattern', ')') ->call(function( $_, $_, ModifierMatcherFilter $memberModifiers, PointFilter $classFilter, $_, $propertyNamePattern, $_ // ) ) { $pointcut = new SignaturePropertyPointcut($propertyNamePattern, $memberModifiers); $pointcut->setClassFilter($classFilter); return $pointcut; }) ->is('@access', '(', 'NamespacePattern', ')') ->call(function ($_, $_, $annotationClassName, $_) use ($annotationReader) { return new AnnotationPropertyPointcut($annotationReader, $annotationClassName); }) ->is('@annotation', '(', 'NamespacePattern', ')') ->call(function ($_, $_, $annotationClassName, $_) use ($annotationReader) { return new AnnotationMethodPointcut($annotationReader, $annotationClassName); }) ->is('within', '(', 'ClassFilter', ')') ->call(function ($_, $_, $classFilter, $_) { $pointcut = new TrueMethodPointcut(); $pointcut->setClassFilter($classFilter); return $pointcut; }) ->is('class', '(', 'ClassFilter', ')') ->call(function ($_, $_, $classFilter, $_) { $pointcut = new TruePointcut(); $pointcut->setClassFilter($classFilter); return $pointcut; }) ->is('PointcutReference') ->call(function ($pointcutName) use ($container) { return $container->getPointcut($pointcutName); }); $this('Arguments') ->is('Empty') ->is('*'); $stringConverter = $this->getNodeToStringConverter(); $this('PointcutReference') ->is('NamespacePattern', 'MethodCall', 'NamePart') ->call($stringConverter); // stable $this('MethodCall') ->is('::')->call($stringConverter) ->is('->')->call($stringConverter); $this('ClassFilter') ->is('NamespacePattern') ->call(function($pattern) { return $pattern === '**' ? TruePointFilter::getInstance() : new SimpleClassFilter($pattern); }) ->is('NamespacePattern', '+') ->call(function($parentClassName, $_) { return new InheritanceClassFilter($parentClassName); }) ; // stable $this('NamespacePattern') ->is('NamePattern') ->is('**')->call($stringConverter) ->is('NamespacePattern', 'NsSeparator', 'NamespacePattern')->call($stringConverter); // stable $this('NamePattern') ->is('NamePattern', '*')->call($stringConverter) ->is('NamePattern', 'NamePart')->call($stringConverter) ->is('NamePart')->call($stringConverter) ->is('*')->call($stringConverter); // stable $this('MemberModifiers') ->is('*') ->call(function() { $matcher = new ModifierMatcherFilter(); return $matcher->orMatch(-1); }) ->is('NonEmptyMemberModifiers'); // stable $this('NonEmptyMemberModifiers') ->is('MemberModifier') ->call(function($modifier) { return new ModifierMatcherFilter($modifier); }) ->is('NonEmptyMemberModifiers','|','MemberModifier') ->call(function(ModifierMatcherFilter $matcher, $_, $modifier) { return $matcher->orMatch($modifier); }) ->is('NonEmptyMemberModifiers', 'MemberModifier') ->call(function(ModifierMatcherFilter $matcher, $modifier) { return $matcher->andMatch($modifier); }); // stable $converter = $this->getModifierConverter(); $this('MemberModifier') ->is('public')->call($converter) ->is('protected')->call($converter) ->is('private')->call(function() { throw new \RuntimeException("Private modifier is not supported"); }) ->is('final')->call($converter); $this->resolve(Grammar::ALL); $this->start('Pointcut'); } /** * Returns callable for converting node(s) to the string * * @return callable */ private function getNodeToStringConverter() { return function () { $value = ''; foreach (func_get_args() as $node) { if (is_scalar($node)) { $value .= $node; } else { $value .= $node->getValue(); } } return $value; }; } /** * Returns callable for converting node value for modifiers to the constant value * * @return callable */ private function getModifierConverter() { return function($node) { $name = strtoupper($node->getValue()); return constant("ReflectionMethod::IS_{$name}"); }; } }src/Go/Aop/Pointcut/PointcutLexer.php000064400000003350144760114140013545 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Dissect\Lexer\SimpleLexer; /** * This class defines a lexer for pointcut expression * * @package Go\Aop\Pointcut */ class PointcutLexer extends SimpleLexer { /** * Lexer token definitions */ public function __construct() { // General tokens $this->token('execution'); $this->token('within'); $this->token('class'); $this->token('access'); $this->token('@access'); $this->token('cflowbelow'); $this->token('function'); $this->token('@annotation'); // Parenthesis $this->token('('); $this->token(')'); // Member modifiers $this->token('public'); $this->token('protected'); $this->token('private'); $this->token('final'); // Access type (dynamic or static) $this->token('->'); $this->token('::'); // Logic tokens $this->token('!'); $this->token('&'); $this->token('&&'); $this->token('|'); $this->token('||'); // Regex for class name $this->regex('NamePart', '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/'); // NS separator $this->token('NsSeparator', '\\'); // Special wildcard tokens $this->token('+'); $this->token('*'); $this->token('**'); // White spaces $this->regex('WSP', "/^[ \r\n\t]+/"); // Comments $this->regex('CMT', "|^//.*|"); $this->skip('CMT', 'WSP'); } } src/Go/Aop/Pointcut/PointcutParseTable.php000064400000015404144760114140014513 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ /** * This table was generated for production use, do not touch it */ return array('action'=>array(0=>array('('=>2,'!'=>3,'cflowbelow'=>4,'execution'=>6,'access'=>7,'@access'=>8,'@annotation'=>9,'within'=>10,'class'=>11,'**'=>15,'NamePart'=>16,'*'=>17,),1=>array('||'=>18,'&&'=>19,'$eof'=>0,),2=>array('('=>2,'!'=>3,'cflowbelow'=>4,'execution'=>6,'access'=>7,'@access'=>8,'@annotation'=>9,'within'=>10,'class'=>11,'**'=>15,'NamePart'=>16,'*'=>17,),3=>array('('=>2,'!'=>3,'cflowbelow'=>4,'execution'=>6,'access'=>7,'@access'=>8,'@annotation'=>9,'within'=>10,'class'=>11,'**'=>15,'NamePart'=>16,'*'=>17,),4=>array('('=>22,),6=>array('('=>23,),7=>array('('=>24,),8=>array('('=>25,),9=>array('('=>26,),10=>array('('=>27,),11=>array('('=>28,),13=>array('NsSeparator'=>30,'::'=>31,'->'=>32,),14=>array('*'=>33,'NamePart'=>34,'::'=>-23,'->'=>-23,'NsSeparator'=>-23,'('=>-23,')'=>-23,'+'=>-23,),18=>array('('=>2,'!'=>3,'cflowbelow'=>4,'execution'=>6,'access'=>7,'@access'=>8,'@annotation'=>9,'within'=>10,'class'=>11,'**'=>15,'NamePart'=>16,'*'=>17,),19=>array('('=>2,'!'=>3,'cflowbelow'=>4,'execution'=>6,'access'=>7,'@access'=>8,'@annotation'=>9,'within'=>10,'class'=>11,'**'=>15,'NamePart'=>16,'*'=>17,),20=>array(')'=>37,'||'=>18,'&&'=>19,),21=>array('||'=>18,'&&'=>19,'$eof'=>-5,')'=>-5,),22=>array('('=>2,'!'=>3,'cflowbelow'=>4,'execution'=>6,'access'=>7,'@access'=>8,'@annotation'=>9,'within'=>10,'class'=>11,'**'=>15,'NamePart'=>16,'*'=>17,),23=>array('*'=>41,'**'=>15,'NamePart'=>16,'public'=>44,'protected'=>45,'private'=>46,'final'=>47,),24=>array('*'=>49,'public'=>44,'protected'=>45,'private'=>46,'final'=>47,),25=>array('**'=>15,'NamePart'=>16,'*'=>17,),26=>array('**'=>15,'NamePart'=>16,'*'=>17,),27=>array('**'=>15,'NamePart'=>16,'*'=>17,),28=>array('**'=>15,'NamePart'=>16,'*'=>17,),29=>array('NamePart'=>55,),30=>array('**'=>15,'NamePart'=>16,'*'=>17,),35=>array('||'=>18,'&&'=>19,'$eof'=>-2,')'=>-2,),36=>array('||'=>18,'&&'=>19,'$eof'=>-3,')'=>-3,),38=>array(')'=>57,'||'=>18,'&&'=>19,),39=>array('**'=>15,'NamePart'=>16,'*'=>17,),40=>array('('=>59,'NsSeparator'=>30,),42=>array('|'=>60,'public'=>44,'protected'=>45,'private'=>46,'final'=>47,'**'=>-31,'NamePart'=>-31,'*'=>-31,),48=>array('**'=>15,'NamePart'=>16,'*'=>17,),50=>array(')'=>63,'NsSeparator'=>30,),51=>array(')'=>64,'NsSeparator'=>30,),52=>array(')'=>65,),53=>array('+'=>66,'NsSeparator'=>30,')'=>-21,'::'=>-21,'->'=>-21,),54=>array(')'=>67,),56=>array('NsSeparator'=>30,'::'=>-25,'->'=>-25,'('=>-25,')'=>-25,'+'=>-25,),58=>array('::'=>31,'->'=>32,),59=>array('*'=>71,')'=>-1,),60=>array('public'=>44,'protected'=>45,'private'=>46,'final'=>47,),62=>array('->'=>73,),68=>array('NamePart'=>16,'*'=>17,),69=>array(')'=>75,),73=>array('NamePart'=>16,'*'=>17,),74=>array('('=>77,'*'=>33,'NamePart'=>34,),75=>array(')'=>78,),76=>array(')'=>79,'*'=>33,'NamePart'=>34,),77=>array('*'=>71,')'=>-1,),80=>array(')'=>81,),81=>array(')'=>82,),5=>array('$eof'=>-7,'||'=>-7,'&&'=>-7,')'=>-7,),12=>array('$eof'=>-15,'||'=>-15,'&&'=>-15,')'=>-15,),15=>array('::'=>-24,'->'=>-24,'NsSeparator'=>-24,'('=>-24,')'=>-24,'+'=>-24,),16=>array('::'=>-28,'->'=>-28,'NsSeparator'=>-28,'*'=>-28,'NamePart'=>-28,'('=>-28,')'=>-28,'+'=>-28,),17=>array('::'=>-29,'->'=>-29,'NsSeparator'=>-29,'*'=>-29,'NamePart'=>-29,'('=>-29,')'=>-29,'+'=>-29,),31=>array('NamePart'=>-19,'*'=>-19,),32=>array('NamePart'=>-20,'*'=>-20,),33=>array('::'=>-26,'->'=>-26,'NsSeparator'=>-26,'*'=>-26,'NamePart'=>-26,'('=>-26,')'=>-26,'+'=>-26,),34=>array('::'=>-27,'->'=>-27,'NsSeparator'=>-27,'*'=>-27,'NamePart'=>-27,'('=>-27,')'=>-27,'+'=>-27,),37=>array('$eof'=>-4,'||'=>-4,'&&'=>-4,')'=>-4,),41=>array('**'=>-30,'NamePart'=>-29,'*'=>-29,'('=>-29,'NsSeparator'=>-29,),43=>array('**'=>-32,'NamePart'=>-32,'*'=>-32,'|'=>-32,'public'=>-32,'protected'=>-32,'private'=>-32,'final'=>-32,),44=>array('**'=>-35,'NamePart'=>-35,'*'=>-35,'|'=>-35,'public'=>-35,'protected'=>-35,'private'=>-35,'final'=>-35,),45=>array('**'=>-36,'NamePart'=>-36,'*'=>-36,'|'=>-36,'public'=>-36,'protected'=>-36,'private'=>-36,'final'=>-36,),46=>array('**'=>-37,'NamePart'=>-37,'*'=>-37,'|'=>-37,'public'=>-37,'protected'=>-37,'private'=>-37,'final'=>-37,),47=>array('**'=>-38,'NamePart'=>-38,'*'=>-38,'|'=>-38,'public'=>-38,'protected'=>-38,'private'=>-38,'final'=>-38,),49=>array('**'=>-30,'NamePart'=>-30,'*'=>-30,),55=>array('$eof'=>-18,'||'=>-18,'&&'=>-18,')'=>-18,),57=>array('$eof'=>-6,'||'=>-6,'&&'=>-6,')'=>-6,),61=>array('**'=>-34,'NamePart'=>-34,'*'=>-34,'|'=>-34,'public'=>-34,'protected'=>-34,'private'=>-34,'final'=>-34,),63=>array('$eof'=>-11,'||'=>-11,'&&'=>-11,')'=>-11,),64=>array('$eof'=>-12,'||'=>-12,'&&'=>-12,')'=>-12,),65=>array('$eof'=>-13,'||'=>-13,'&&'=>-13,')'=>-13,),66=>array(')'=>-22,'::'=>-22,'->'=>-22,),67=>array('$eof'=>-14,'||'=>-14,'&&'=>-14,')'=>-14,),70=>array(')'=>-16,),71=>array(')'=>-17,),72=>array('**'=>-33,'NamePart'=>-33,'*'=>-33,'|'=>-33,'public'=>-33,'protected'=>-33,'private'=>-33,'final'=>-33,),78=>array('$eof'=>-9,'||'=>-9,'&&'=>-9,')'=>-9,),79=>array('$eof'=>-10,'||'=>-10,'&&'=>-10,')'=>-10,),82=>array('$eof'=>-8,'||'=>-8,'&&'=>-8,')'=>-8,),),'goto'=>array(0=>array('Pointcut'=>1,'SinglePointcut'=>5,'PointcutReference'=>12,'NamespacePattern'=>13,'NamePattern'=>14,),2=>array('Pointcut'=>20,'SinglePointcut'=>5,'PointcutReference'=>12,'NamespacePattern'=>13,'NamePattern'=>14,),3=>array('Pointcut'=>21,'SinglePointcut'=>5,'PointcutReference'=>12,'NamespacePattern'=>13,'NamePattern'=>14,),13=>array('MethodCall'=>29,),18=>array('Pointcut'=>35,'SinglePointcut'=>5,'PointcutReference'=>12,'NamespacePattern'=>13,'NamePattern'=>14,),19=>array('Pointcut'=>36,'SinglePointcut'=>5,'PointcutReference'=>12,'NamespacePattern'=>13,'NamePattern'=>14,),22=>array('Pointcut'=>38,'SinglePointcut'=>5,'PointcutReference'=>12,'NamespacePattern'=>13,'NamePattern'=>14,),23=>array('MemberModifiers'=>39,'NamespacePattern'=>40,'NonEmptyMemberModifiers'=>42,'NamePattern'=>14,'MemberModifier'=>43,),24=>array('MemberModifiers'=>48,'NonEmptyMemberModifiers'=>42,'MemberModifier'=>43,),25=>array('NamespacePattern'=>50,'NamePattern'=>14,),26=>array('NamespacePattern'=>51,'NamePattern'=>14,),27=>array('ClassFilter'=>52,'NamespacePattern'=>53,'NamePattern'=>14,),28=>array('ClassFilter'=>54,'NamespacePattern'=>53,'NamePattern'=>14,),30=>array('NamespacePattern'=>56,'NamePattern'=>14,),39=>array('ClassFilter'=>58,'NamespacePattern'=>53,'NamePattern'=>14,),42=>array('MemberModifier'=>61,),48=>array('ClassFilter'=>62,'NamespacePattern'=>53,'NamePattern'=>14,),58=>array('MethodCall'=>68,),59=>array('Arguments'=>69,'Empty'=>70,),60=>array('MemberModifier'=>72,),68=>array('NamePattern'=>74,),73=>array('NamePattern'=>76,),77=>array('Arguments'=>80,'Empty'=>70,),)); src/Go/Aop/Pointcut/PointcutParser.php000064400000001171144760114140013721 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Dissect\Parser\LALR1\Parser; /** * Pointcut parser extends the default parser with parse table and strict typehint for grammar */ class PointcutParser extends Parser { /** * {@inheritDoc} */ public function __construct(PointcutGrammar $grammar) { $parseTable = include 'PointcutParseTable.php'; parent::__construct($grammar, $parseTable); } }src/Go/Aop/Pointcut/SignatureMethodPointcut.php000064400000003760144760114140015575 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use ReflectionMethod; use Go\Aop\PointFilter; use TokenReflection\ReflectionMethod as ParsedReflectionMethod; /** * Signature method pointcut checks method signature (modifiers and name) to match it */ class SignatureMethodPointcut extends StaticMethodMatcherPointcut { /** * Method name to match, can contain wildcards *,? * * @var string */ protected $methodName = ''; /** * Modifier filter for method * * @var PointFilter */ protected $modifierFilter; /** * Regular expression for matching * * @var string */ protected $regexp; /** * Signature method matcher constructor * * @param string $methodName Name of the method to match or glob pattern * @param PointFilter $modifierFilter Method modifier filter */ public function __construct($methodName, PointFilter $modifierFilter) { $this->methodName = $methodName; $this->regexp = strtr(preg_quote($this->methodName, '/'), array( '\\*' => '.*?', '\\?' => '.' )); $this->modifierFilter = $modifierFilter; } /** * Performs matching of point of code * * @param mixed $method Specific part of code, can be any Reflection class * * @return bool */ public function matches($method) { /** @var $method ReflectionMethod|ParsedReflectionMethod */ if (!$method instanceof ReflectionMethod && !$method instanceof ParsedReflectionMethod) { return false; } if (!$this->modifierFilter->matches($method)) { return false; } return ($method->name === $this->methodName) || (bool) preg_match("/^{$this->regexp}$/", $method->name); } } src/Go/Aop/Pointcut/SignaturePropertyPointcut.php000064400000005504144760114140016177 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use ReflectionProperty; use TokenReflection\ReflectionProperty as ParsedReflectionProperty; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\TruePointFilter; /** * Signature property pointcut checks the property signature (modifiers and name) to match it */ class SignaturePropertyPointcut implements Pointcut { /** * Filter for class * * @var null|PointFilter */ private $classFilter = null; /** * Modifier filter for method * * @var PointFilter */ protected $modifierFilter; /** * Property name to match, can contain wildcards *,? * * @var string */ protected $propertyName = ''; /** * Regular expression for matching * * @var string */ protected $regexp; /** * Signature property matcher constructor * * @param string $propertyName Name of the property to match or glob pattern * @param PointFilter $modifierFilter Property modifier filter */ public function __construct($propertyName, PointFilter $modifierFilter) { $this->propertyName = $propertyName; $this->regexp = strtr(preg_quote($this->propertyName, '/'), array( '\\*' => '.*?', '\\?' => '.' )); $this->modifierFilter = $modifierFilter; } /** * Set the ClassFilter to use for this pointcut. * * @param PointFilter $classFilter */ public function setClassFilter(PointFilter $classFilter) { $this->classFilter = $classFilter; } /** * Return the ClassFilter for this pointcut. * * @return PointFilter */ public function getClassFilter() { if (!$this->classFilter) { $this->classFilter = TruePointFilter::getInstance(); } return $this->classFilter; } /** * Performs matching of point of code * * @param mixed $property * * @return bool */ public function matches($property) { /** @var $property ReflectionProperty|ParsedReflectionProperty */ if (!$property instanceof ReflectionProperty && !$property instanceof ParsedReflectionProperty) { return false; } if (!$this->modifierFilter->matches($property)) { return false; } return ($property->name === $this->propertyName) || (bool) preg_match("/^{$this->regexp}$/", $property->name); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_PROPERTY; } } src/Go/Aop/Pointcut/StaticMethodMatcherPointcut.php000064400000002412144760114140016360 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\TruePointFilter; use Go\Aop\Support\StaticMethodMatcher; /** * Convenient abstract superclass for static method matchers, which don't care about arguments at runtime. * * The "classFilter" property can be set to customize ClassFilter behavior. */ abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut { /** * Filter for class * * @var null|PointFilter */ private $classFilter = null; /** * Set the ClassFilter to use for this pointcut. * * @param PointFilter $classFilter */ public function setClassFilter(PointFilter $classFilter) { $this->classFilter = $classFilter; } /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter() { if (!$this->classFilter) { $this->classFilter = TruePointFilter::getInstance(); } return $this->classFilter; } } src/Go/Aop/Pointcut/TrueMethodPointcut.php000064400000001615144760114140014550 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use ReflectionMethod; use TokenReflection\ReflectionMethod as ParsedReflectionMethod; /** * True method pointcut matches all methods in specific class */ class TrueMethodPointcut extends StaticMethodMatcherPointcut { /** * Performs matching of point of code * * @param mixed $method Specific part of code, can be any Reflection class * * @return bool */ public function matches($method) { /** @var $method ReflectionMethod|ParsedReflectionMethod */ if (!$method instanceof ReflectionMethod && !$method instanceof ParsedReflectionMethod) { return false; } return true; } } src/Go/Aop/Pointcut/TruePointcut.php000064400000002733144760114140013411 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Pointcut; use Go\Aop\Support\TruePointFilter; /** * Canonical Pointcut instance that always matches. * * @package go * @subpackage aop */ class TruePointcut implements Pointcut { /** * Filter for class * * @var null|PointFilter */ private $classFilter = null; /** * Set the ClassFilter to use for this pointcut. * * @param PointFilter $classFilter */ public function setClassFilter(PointFilter $classFilter) { $this->classFilter = $classFilter; } /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter() { if (!$this->classFilter) { $this->classFilter = TruePointFilter::getInstance(); } return $this->classFilter; } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { return true; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_ALL; } } src/Go/Aop/PointcutAdvisor.php000064400000001146144760114140012271 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Super-interface for all Advisors that are driven by a pointcut. * * This covers nearly all advisors except introduction advisors, for which method-level matching doesn't apply. */ interface PointcutAdvisor extends Advisor { /** * Get the Pointcut that drives this advisor. * * @return Pointcut The pointcut */ public function getPointcut(); } src/Go/Aop/Proxy.php000064400000000600144760114140010247 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop; /** * Marker interface implemented by all AOP proxies. * * Used to detect whether or not objects are Go-generated proxies. */ interface Proxy { } src/Go/Aop/Support/AbstractGenericPointcutAdvisor.php000064400000003030144760114140016720 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Go\Aop\Advice; use Go\Aop\PointcutAdvisor; /** * Abstract generic PointcutAdvisor that allows for any Advice to be configured. */ abstract class AbstractGenericPointcutAdvisor implements PointcutAdvisor { /** * @var null|Advice */ private $advice = null; /** * Return whether this advice is associated with a particular instance or shared with all instances * of the advised class * * NB: This method was moved from AbstractPointcutAdvisor to simplify hierarchy * * @return bool Whether this advice is associated with a particular target instance */ public function isPerInstance() { return true; } /** * Specify the advice that this advisor should apply. * * @param Advice $advice Advice to apply */ public function setAdvice(Advice $advice) { $this->advice = $advice; } /** * Returns an advice to apply * * @return Advice|null */ public function getAdvice() { return $this->advice; } /** * Return string representation of object * * @return string */ public function __toString() { $adviceClass = get_class($this->getAdvice()); return get_called_class() . ": advice [{$adviceClass}]"; } } src/Go/Aop/Support/AndPointFilter.php000064400000002714144760114140013474 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Go\Aop\PointFilter; /** * Logical and filter. */ class AndPointFilter implements PointFilter { /** * Kind of filter * * @var int */ private $kind = 0; /** * First part of filter * * @var PointFilter|null */ private $first = null; /** * Second part of filter * * @var PointFilter|null */ private $second = null; /** * And constructor * * @param PointFilter $first First part of filter * @param PointFilter $second Second part of filter to match */ public function __construct(PointFilter $first, PointFilter $second) { $this->kind = $first->getKind() & $second->getKind(); $this->first = $first; $this->second = $second; } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { return $this->first->matches($point) && $this->second->matches($point); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->kind; } } src/Go/Aop/Support/DeclareParentsAdvisor.php000064400000006706144760114140015043 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use InvalidArgumentException; use ReflectionClass; use Go\Aop\Advice; use Go\Aop\PointFilter; use Go\Aop\IntroductionInfo; use Go\Aop\IntroductionAdvisor; /** * Introduction advisor delegating to the given object. */ class DeclareParentsAdvisor implements IntroductionAdvisor { /** * @var null|IntroductionInfo */ private $advice = null; /** * Type pattern the introduction is restricted to * * @var PointFilter */ private $classFilter; /** * Create a DefaultIntroductionAdvisor for the given advice. */ public function __construct(PointFilter $classFilter, IntroductionInfo $info) { $this->classFilter = $classFilter; $this->advice = $info; } /** * Can the advised interfaces be implemented by the introduction advice? * * Invoked before adding an IntroductionAdvisor. * * @return void * @throws \InvalidArgumentException if the advised interfaces can't be implemented by the introduction advice */ public function validateInterfaces() { $refInterface = new ReflectionClass(reset($this->advice->getInterfaces())); $refImplementation = new ReflectionClass(reset($this->advice->getTraits())); if (!$refInterface->isInterface()) { throw new \InvalidArgumentException("Only interface can be introduced"); } if (!$refImplementation->isTrait()) { throw new \InvalidArgumentException("Only trait can be used as implementation"); } foreach($refInterface->getMethods() as $interfaceMethod) { if (!$refImplementation->hasMethod($interfaceMethod->name)) { throw new \DomainException("Implementation requires method {$interfaceMethod->name}"); } } } /** * Returns an advice to apply * * @return Advice|IntroductionInfo|null */ public function getAdvice() { return $this->advice; } /** * Return whether this advice is associated with a particular instance or shared with all instances * of the advised class * * @return bool Whether this advice is associated with a particular target instance */ public function isPerInstance() { return false; } /** * Return the filter determining which target classes this introduction should apply to. * * This represents the class part of a pointcut. Note that method matching doesn't make sense to introductions. * * @return PointFilter The class filter */ public function getClassFilter() { return $this->classFilter; } /** * Set the class filter for advisor * * @param PointFilter $classFilter Filter for classes */ public function setClassFilter(PointFilter $classFilter) { $this->classFilter = $classFilter; } /** * Return string representation of object * * @return string */ public function __toString() { $adviceClass = get_class($this->advice); $interfaceClasses = join(',', $this->advice->getInterfaces()); return get_called_class() . ": advice [{$adviceClass}]; interfaces [{$interfaceClasses}] "; } } src/Go/Aop/Support/DefaultPointcutAdvisor.php000064400000003544144760114140015256 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Go\Aop\Advice; use Go\Aop\Pointcut; /** * Convenient Pointcut-driven Advisor implementation. * * This is the most commonly used Advisor implementation. It can be used with any pointcut and advice type, * except for introductions. There is normally no need to subclass this class, or to implement custom Advisors. */ class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor { /** * Pointcut instance * * @var Pointcut */ private $pointcut; /** * Create a DefaultPointcutAdvisor, specifying Pointcut and Advice. * * @param Pointcut $pointcut The Pointcut targeting the Advice * @param Advice $advice The Advice to run when Pointcut matches */ public function __construct(Pointcut $pointcut, Advice $advice) { $this->pointcut = $pointcut; $this->setAdvice($advice); } /** * Get the Pointcut that drives this advisor. * * @return Pointcut The pointcut */ public function getPointcut() { return $this->pointcut; } /** * Specify the pointcut targeting the advice. * * @param Pointcut $pointcut The Pointcut targeting the Advice */ public function setPointcut(Pointcut $pointcut) { $this->pointcut = $pointcut; } /** * Return string representation of object * * @return string */ public function __toString() { $pointcutClass = get_class($this->getPointcut()); $adviceClass = get_class($this->getAdvice()); return get_called_class() . ": pointcut [{$pointcutClass}]; advice [{$adviceClass}]"; } } src/Go/Aop/Support/DynamicMethodMatcher.php000064400000002005144760114140014634 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Reflector; use ReflectionMethod; use Go\Aop\MethodMatcher; /** * Convenient abstract superclass for dynamic method matchers, which do care about arguments at runtime. */ abstract class DynamicMethodMatcher implements MethodMatcher { /** * Is this MethodMatcher dynamic or static * * Can be invoked when an AOP proxy is created, and need not be invoked again before each method invocation * * @return bool whether or not a runtime match via the 3-arg matches() method is required if static matching passed */ public final function isRuntime() { return true; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_METHOD; } } src/Go/Aop/Support/InheritanceClassFilter.php000064400000002621144760114140015174 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use ReflectionClass; use Go\Aop\PointFilter; use TokenReflection\ReflectionClass as ParsedReflectionClass; /** * Inheritance class matcher that match single class name or any subclass */ class InheritanceClassFilter implements PointFilter { /** * Instance class name to match * * @var string */ protected $parentClass = ''; /** * Inheritance class matcher constructor * * @param string $parentClassName Name of the parent class or interface to match */ public function __construct($parentClassName) { $this->parentClass = $parentClassName; } /** * {@inheritdoc} */ public function matches($class) { /** @var $point ReflectionClass|ParsedReflectionClass */ if (!$class instanceof ReflectionClass && !$class instanceof ParsedReflectionClass) { return false; } return $class->isSubclassOf($this->parentClass) || in_array($this->parentClass, $class->getInterfaceNames()); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_CLASS; } } src/Go/Aop/Support/ModifierMatcherFilter.php000064400000004273144760114140015024 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Go\Aop\PointFilter; /** * ModifierMatcherFilter performs checks on modifiers for reflection point * * @package Go\Aop\Pointcut */ class ModifierMatcherFilter implements PointFilter { /** * Bit mask, that should be always match * * @var int */ protected $andMask = 0; /** * Bit mask, that can be used for additional check * * @var int */ protected $orMask = 0; /** * Bit mask to exclude specific value from matching, for example, !public * * @var int */ protected $notMask = 0; /** * Initialize default filter with "and" mask * * @param int $initialMask Default mask for "and" */ public function __construct($initialMask = 0) { $this->andMask = $initialMask; } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { $modifiers = $point->getModifiers(); return !($this->notMask & $modifiers) && (($this->andMask === ($this->andMask & $modifiers)) || ($this->orMask & $modifiers)); } /** * Add "and" or mask * * @param integer $bitMask * @return $this */ public function andMatch($bitMask) { $this->andMask |= $bitMask; return $this; } /** * Add "or" mask * * @param integer $bitMask * @return $this */ public function orMatch($bitMask) { $this->orMask |= $bitMask; return $this; } /** * Add "not" mask * * @param integer $bitMask * @return $this */ public function notMatch($bitMask) { $this->notMask |= $bitMask; return $this; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_ALL; } }src/Go/Aop/Support/NamespacedReflectionFunction.php000064400000001740144760114140016371 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use ReflectionFunction; /** * Namespaced version of global functions */ class NamespacedReflectionFunction extends ReflectionFunction { /** * Custom namespace name * * @var string */ private $namespace = ''; /** * Extends the logic with passing the namespace name * * @param string $namespace Name of the namespace * {@inheritDoc} */ public function __construct($name, $namespace = '') { $this->namespace = $namespace; parent::__construct($name); } /** * {@inheritDoc} */ public function getNamespaceName() { if ($this->namespace) { return $this->namespace; } return parent::getNamespaceName(); } } src/Go/Aop/Support/OrPointFilter.php000064400000002711144760114140013347 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Go\Aop\PointFilter; /** * Logical or filter. */ class OrPointFilter implements PointFilter { /** * Kind of filter * * @var int */ private $kind = 0; /** * First part of filter * * @var PointFilter|null */ private $first = null; /** * Second part of filter * * @var PointFilter|null */ private $second = null; /** * Or constructor * * @param PointFilter $first First part of filter * @param PointFilter $second Second part of filter to match */ public function __construct(PointFilter $first, PointFilter $second) { $this->kind = $first->getKind() | $second->getKind(); $this->first = $first; $this->second = $second; } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { return $this->first->matches($point) || $this->second->matches($point); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->kind; } } src/Go/Aop/Support/SimpleClassFilter.php000064400000003123144760114140014172 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use ReflectionClass; use Go\Aop\PointFilter; use TokenReflection\ReflectionClass as ParsedReflectionClass; /** * Simple class matcher that match only single class name * * Class name can contain wildcards '*', '**' and '?' */ class SimpleClassFilter implements PointFilter { /** * Class name to match, can contain wildcards *,? * * @var string */ protected $className = ''; /** * Class name matcher constructor * * @param string $className Name of the class to match or glob pattern */ public function __construct($className) { $this->className = $className; $this->regexp = strtr(preg_quote($this->className, '/'), array( '\\*' => '[^\\\\]+', '\\*\\*' => '.+', '\\?' => '.' )); } /** * {@inheritdoc} */ public function matches($class) { /** @var $point ReflectionClass|ParsedReflectionClass */ if (!$class instanceof ReflectionClass && !$class instanceof ParsedReflectionClass) { return false; } return ($class->name === $this->className) || (bool) preg_match("/^{$this->regexp}$/", $class->name); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_CLASS; } } src/Go/Aop/Support/SimpleNamespaceFilter.php000064400000003421144760114140015022 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use ReflectionClass; use Go\Aop\PointFilter; use TokenReflection\ReflectionFileNamespace as ParsedReflectionFileNamespace; /** * Simple namespace matcher that match only specific namespace name * * Namespace name can contain wildcards '*', '**' and '?' */ class SimpleNamespaceFilter implements PointFilter { /** * Namespace name to match, can contain wildcards *,? * * @var string */ protected $nsName = ''; /** * Namespace name matcher constructor * * @param string $namespaceName Name of the namespace to match or glob pattern */ public function __construct($namespaceName) { $namespaceName = trim($namespaceName, '\\'); $this->nsName = $namespaceName; $this->regexp = strtr(preg_quote($this->nsName, '/'), array( '\\*' => '[^\\\\]+', '\\*\\*' => '.+', '\\?' => '.' )); } /** * {@inheritdoc} * @param ParsedReflectionFileNamespace|string $ns */ public function matches($ns) { $isNamespaceIsObject = ($ns === (object) $ns); if ($isNamespaceIsObject && !$ns instanceof ParsedReflectionFileNamespace) { return false; } $nsName = $isNamespaceIsObject ? $ns->getName() : $ns; return ($nsName === $this->nsName) || (bool) preg_match("/^{$this->regexp}$/", $nsName); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return 0; //self::KIND_CLASS; } } src/Go/Aop/Support/StaticMethodMatcher.php000064400000001742144760114140014506 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Go\Aop\MethodMatcher; /** * Convenient abstract superclass for static method matchers, which don't care about arguments at runtime. */ abstract class StaticMethodMatcher implements MethodMatcher { /** * Is this MethodMatcher dynamic or static * * Can be invoked when an AOP proxy is created, and need not be invoked again before each method invocation * * @return bool whether or not a runtime match via the 3-arg matches() method is required if static matching passed */ public final function isRuntime() { return false; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_METHOD; } } src/Go/Aop/Support/TruePointFilter.php000064400000002154144760114140013707 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; use Go\Aop\PointFilter; /** * Canonical PointFilter instance that matches all points. */ class TruePointFilter implements PointFilter { /** * Private class constructor */ private function __construct() { } /** * Singleton pattern * * @return self */ public static function getInstance() { static $instance = null; if (!$instance) { $instance = new self(); } return $instance; } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * * @return bool */ public function matches($point) { return true; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return self::KIND_ALL; } } src/Go/Core/AbstractAspectLoaderExtension.php000064400000010634144760114140015236 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use Dissect\Lexer\Exception\RecognitionException; use Dissect\Lexer\TokenStream\TokenStream; use Dissect\Parser\Exception\UnexpectedTokenException; use Go\Aop\Aspect; use Go\Aop\Pointcut; use Go\Lang\Annotation; use ReflectionMethod; use ReflectionProperty; /** * Abstract aspect loader */ abstract class AbstractAspectLoaderExtension implements AspectLoaderExtension { /** * General method for parsing pointcuts * * @param AspectContainer $container Container * @param Aspect $aspect Instance of current aspect * @param Annotation\BaseAnnotation|Annotation\BaseInterceptor $metaInformation * @param mixed|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point * * @throws \UnexpectedValueException if there was an error during parsing * @return Pointcut */ protected function parsePointcut(AspectContainer $container, Aspect $aspect, $reflection, $metaInformation) { $stream = $this->makeLexicalAnalyze($container, $aspect, $reflection, $metaInformation); return $this->parseTokenStream($container, $reflection, $metaInformation, $stream); } /** * Performs lexical analyze of pointcut * * @param AspectContainer $container Container instance * @param Aspect $aspect Instance of aspect * @param ReflectionMethod|ReflectionProperty $reflection * @param $metaInformation * * @return TokenStream * @throws \UnexpectedValueException */ protected function makeLexicalAnalyze(AspectContainer $container, Aspect $aspect, $reflection, $metaInformation) { /** @var $lexer \Dissect\Lexer\Lexer */ $lexer = $container->get('aspect.pointcut.lexer'); try { $resolvedThisPointcut = str_replace('$this', get_class($aspect), $metaInformation->value); $stream = $lexer->lex($resolvedThisPointcut); } catch (RecognitionException $e) { $message = "Can not recognize the lexical structure `%s` before %s, defined in %s:%d"; $message = sprintf( $message, $metaInformation->value, (isset($reflection->class) ? $reflection->class . '->' : '') . $reflection->name, method_exists($reflection, 'getFileName') ? $reflection->getFileName() : $reflection->getDeclaringClass()->getFileName(), method_exists($reflection, 'getStartLine') ? $reflection->getStartLine() : 0 ); throw new \UnexpectedValueException($message, 0, $e); } return $stream; } /** * Performs parsing of pointcut * * @param AspectContainer $container Instance of container * @param ReflectionMethod|ReflectionProperty $reflection * @param $metaInformation * @param TokenStream $stream * @return Pointcut * * @throws \UnexpectedValueException */ protected function parseTokenStream(AspectContainer $container, $reflection, $metaInformation, $stream) { /** @var $parser \Dissect\Parser\Parser */ $parser = $container->get('aspect.pointcut.parser'); try { $pointcut = $parser->parse($stream); } catch (UnexpectedTokenException $e) { /** @var \Dissect\Lexer\Token $token */ $token = $e->getToken(); $message = "Unexpected token %s in the `%s` before %s, defined in %s:%d." . PHP_EOL; $message .= "Expected one of: %s"; $message = sprintf( $message, $token->getValue(), $metaInformation->value, (isset($reflection->class) ? $reflection->class . '->' : '') . $reflection->name, method_exists($reflection, 'getFileName') ? $reflection->getFileName() : $reflection->getDeclaringClass()->getFileName(), method_exists($reflection, 'getStartLine') ? $reflection->getStartLine() : 0, join(', ', $e->getExpected()) ); throw new \UnexpectedValueException($message, 0, $e); } return $pointcut; } }src/Go/Core/AdviceMatcher.php000064400000020770144760114140012010 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use Go\Aop; use Go\Aop\Support\NamespacedReflectionFunction; use Doctrine\Common\Annotations\AnnotationReader; use TokenReflection\ReflectionClass as ParsedReflectionClass; use TokenReflection\ReflectionFileNamespace; /** * Advice matcher returns the list of advices for the specific point of code */ class AdviceMatcher { /** * Loader of aspects * * @var AspectLoader */ protected $loader; /** * Instance of container for aspect * * @var AspectContainer */ protected $container; /** * List of resources that was loaded * * @var array */ protected $loadedResources = array(); /** * Flag to enable/disable support of global function interception * * @var bool */ private $isInterceptFunctions = false; /** * Constructor * * @param AspectLoader $loader Instance of aspect loader * @param AspectContainer $container Container * @param bool $isInterceptFunctions Optional flag to enable function interception */ public function __construct(AspectLoader $loader, AspectContainer $container, $isInterceptFunctions = false) { $this->loader = $loader; $this->container = $container; $this->isInterceptFunctions = $isInterceptFunctions; } /** * * Returns list of function advices for namespace * * @param ReflectionFileNamespace $namespace * * @return array */ public function getAdvicesForFunctions($namespace) { // TODO: remove after stabilization of functionality if (!$this->isInterceptFunctions || $namespace->getName() == 'no-namespace') { return array(); } $advices = array(); if ($this->loadedResources != $this->container->getResources()) { $this->loadAdvisorsAndPointcuts(); } foreach ($this->container->getByTag('advisor') as $advisor) { if ($advisor instanceof Aop\PointcutAdvisor) { $pointcut = $advisor->getPointcut(); $isFunctionAdvisor = $pointcut->getKind() & Aop\PointFilter::KIND_FUNCTION; if ($isFunctionAdvisor && $pointcut->getClassFilter()->matches($namespace)) { $advices = array_merge_recursive( $advices, $this->getFunctionAdvicesFromAdvisor($namespace, $advisor, $pointcut) ); } } } return $advices; } /** * Return list of advices for class * * @param string|ReflectionClass|ParsedReflectionClass $class Class to advise * * @return array|Aop\Advice[] List of advices for class */ public function getAdvicesForClass($class) { if ($this->loadedResources != $this->container->getResources()) { $this->loadAdvisorsAndPointcuts(); } $classAdvices = array(); if (!$class instanceof ReflectionClass && !$class instanceof ParsedReflectionClass) { $class = new ReflectionClass($class); } $parentClass = $class->getParentClass(); if ($parentClass && preg_match('/' . AspectContainer::AOP_PROXIED_SUFFIX . '$/', $parentClass->name)) { $originalClass = $parentClass; } else { $originalClass = $class; } foreach ($this->container->getByTag('advisor') as $advisor) { if ($advisor instanceof Aop\PointcutAdvisor) { $pointcut = $advisor->getPointcut(); if ($pointcut->getClassFilter()->matches($class)) { $classAdvices = array_merge_recursive( $classAdvices, $this->getAdvicesFromAdvisor($originalClass, $advisor, $pointcut) ); } } if ($advisor instanceof Aop\IntroductionAdvisor) { if ($advisor->getClassFilter()->matches($class)) { $classAdvices = array_merge_recursive( $classAdvices, $this->getIntroductionFromAdvisor($originalClass, $advisor) ); } } } return $classAdvices; } /** * Returns list of advices from advisor and point filter * * @param ReflectionClass|ParsedReflectionClass $class Class to inject advices * @param Aop\PointcutAdvisor $advisor Advisor for class * @param Aop\PointFilter $filter Filter for points * * @return array */ private function getAdvicesFromAdvisor($class, Aop\PointcutAdvisor $advisor, Aop\PointFilter $filter) { $classAdvices = array(); // Check methods in class only for method filters if ($filter->getKind() & Aop\PointFilter::KIND_METHOD) { $mask = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED; foreach ($class->getMethods($mask) as $method) { /** @var $method ReflectionMethod| */ if ($method->getDeclaringClass()->name == $class->name && $filter->matches($method)) { $prefix = $method->isStatic() ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX; $classAdvices[$prefix][$method->name][] = $advisor->getAdvice(); } } } // Check properties in class only for property filters if ($filter->getKind() & Aop\PointFilter::KIND_PROPERTY) { $mask = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED; foreach ($class->getProperties($mask) as $property) { /** @var $property ReflectionProperty */ if ($filter->matches($property)) { $classAdvices[AspectContainer::PROPERTY_PREFIX][$property->name][] = $advisor->getAdvice(); } } } return $classAdvices; } /** * Returns list of introduction advices from advisor * * @param ReflectionClass|ParsedReflectionClass $class Class to inject advices * @param Aop\IntroductionAdvisor $advisor Advisor for class * * @return array */ private function getIntroductionFromAdvisor($class, $advisor) { // Do not make introduction for traits if ($class->isTrait()) { return array(); } /** @var $advice Aop\IntroductionInfo */ $advice = $advisor->getAdvice(); return array( AspectContainer::INTRODUCTION_TRAIT_PREFIX => array($advice) ); } /** * Load pointcuts into container * * There is no need to always load pointcuts, so we delay loading */ private function loadAdvisorsAndPointcuts() { $containerResources = $this->container->getResources(); $resourcesToLoad = array_diff($containerResources, $this->loadedResources); // TODO: maybe this is a task for the AspectLoader? foreach ($this->container->getByTag('aspect') as $aspect) { $ref = new ReflectionClass($aspect); if (in_array($ref->getFileName(), $resourcesToLoad)) { $this->loader->load($aspect); } } $this->loadedResources = $containerResources; } /** * Returns list of function advices for specific namespace * * @param ReflectionFileNamespace $namespace * @param Aop\PointcutAdvisor $advisor Advisor for class * @param Aop\PointFilter $pointcut Filter for points * * @return array */ private function getFunctionAdvicesFromAdvisor($namespace, $advisor, $pointcut) { $functions = array(); $advices = array(); if (!$functions) { $listOfGlobalFunctions = get_defined_functions(); foreach ($listOfGlobalFunctions['internal'] as $functionName) { $functions[$functionName] = new NamespacedReflectionFunction($functionName, $namespace->getName()); } } foreach ($functions as $functionName=>$function) { if ($pointcut->matches($function)) { $advices[AspectContainer::FUNCTION_PREFIX][$functionName][] = $advisor->getAdvice(); } } return $advices; } } src/Go/Core/AspectContainer.php000064400000005115144760114140012367 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use Go\Aop; /** * Aspect container interface */ interface AspectContainer { /** * Prefix for function interceptor */ const FUNCTION_PREFIX = "func"; /** * Prefix for properties interceptor */ const PROPERTY_PREFIX = "prop"; /** * Prefix for method interceptor */ const METHOD_PREFIX = "method"; /** * Prefix for static method interceptor */ const STATIC_METHOD_PREFIX = "static"; /** * Trait introduction prefix */ const INTRODUCTION_TRAIT_PREFIX = "introduction"; /** * Suffix, that will be added to all proxied class names */ const AOP_PROXIED_SUFFIX = '__AopProxied'; /** * Return list of service tagged with marker * * @param string $tag Tag to select * @return array */ public function getByTag($tag); /** * Returns a pointcut by identifier * * @param string $id Pointcut identifier * * @return Aop\Pointcut */ public function getPointcut($id); /** * Store the pointcut in the container * * @param Aop\Pointcut $pointcut Instance * @param string $id Key for pointcut */ public function registerPointcut(Aop\Pointcut $pointcut, $id); /** * Store the advisor in the container * * @param Aop\Advisor $advisor Instance * @param string $id Key for advisor */ public function registerAdvisor(Aop\Advisor $advisor, $id); /** * Register an aspect in the container * * @param Aop\Aspect $aspect Instance of concrete aspect */ public function registerAspect(Aop\Aspect $aspect); /** * Returns an aspect by id or class name * * @param string $aspectName Aspect name * * @return Aop\Aspect */ public function getAspect($aspectName); /** * Add an AOP resource to the container * * @param string $resource Path to the resource * Resources is used to check the freshness of AOP cache */ public function addResource($resource); /** * Returns the list of AOP resources * * @return array */ public function getResources(); /** * Checks the freshness of AOP cache * * @param integer $timestamp * * @return bool Whether or not concrete file is fresh */ public function isFresh($timestamp); }src/Go/Core/AspectKernel.php000064400000013327144760114140011671 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use Go\Instrument\ClassLoading\SourceTransformingLoader; use Go\Instrument\Transformer\SourceTransformer; use Go\Instrument\Transformer\WeavingTransformer; use Go\Instrument\Transformer\CachingTransformer; use Go\Instrument\Transformer\FilterInjectorTransformer; use Go\Instrument\Transformer\MagicConstantTransformer; use Doctrine\Common\Annotations\AnnotationRegistry; use TokenReflection; /** * Whether or not we have a modern PHP */ define('IS_MODERN_PHP', version_compare(PHP_VERSION, '5.4.0') >= 0); /** * Abstract aspect kernel is used to prepare an application to work with aspects. */ abstract class AspectKernel { /** * Kernel options * * @var array */ protected $options = array(); /** * Single instance of kernel * * @var null|static */ protected static $instance = null; /** * Default class name for container, can be redefined in children * * @var string */ protected static $containerClass = 'Go\Core\GoAspectContainer'; /** * Aspect container instance * * @var null|AspectContainer */ protected $container = null; /** * Protected constructor is used to prevent direct creation, but allows customization if needed */ protected function __construct() {} /** * Returns the single instance of kernel * * @return AspectKernel */ public static function getInstance() { if (!self::$instance) { self::$instance = new static(); } return self::$instance; } /** * Init the kernel and make adjustments * * @param array $options Associative array of options for kernel */ public function init(array $options = array()) { $this->options = array_replace_recursive($this->getDefaultOptions(), $options); /** @var $container AspectContainer */ $container = $this->container = new $this->options['containerClass']; $container->set('kernel', $this); $container->set('kernel.interceptFunctions', $this->options['interceptFunctions']); $sourceLoaderFilter = new SourceTransformingLoader(); $sourceLoaderFilter->register(); foreach ($this->registerTransformers($sourceLoaderFilter) as $sourceTransformer) { $sourceLoaderFilter->addTransformer($sourceTransformer); } // Register kernel resources in the container for debug mode if ($this->options['debug']) { $this->addKernelResourcesToContainer($container); } // Register all AOP configuration in the container $this->configureAop($container); } /** * Returns an aspect container * * @return null|AspectContainer */ public function getContainer() { return $this->container; } /** * Returns list of kernel options * * @return array */ public function getOptions() { return $this->options; } /** * Returns default options for kernel. Available options: * * debug - boolean Determines whether or not kernel is in debug mode * appDir - string Path to the application root directory. * cacheDir - string Path to the cache directory where compiled classes will be stored * includePaths - array Whitelist of directories where aspects should be applied. Empty for everywhere. * excludePaths - array Blacklist of directories or files where aspects shouldn't be applied. * interceptFunctions - boolean Enable support for interception of global functions (experimental) * * @return array */ protected function getDefaultOptions() { return array( 'debug' => false, 'appDir' => __DIR__ . '/../../../../../../', 'cacheDir' => null, 'interceptFunctions' => false, 'includePaths' => array(), 'excludePaths' => array(), 'containerClass' => static::$containerClass, ); } /** * Configure an AspectContainer with advisors, aspects and pointcuts * * @param AspectContainer $container * * @return void */ abstract protected function configureAop(AspectContainer $container); /** * Returns list of source transformers, that will be applied to the PHP source * * @param SourceTransformingLoader $sourceLoader Instance of source loader for information * * @return array|SourceTransformer[] */ protected function registerTransformers(SourceTransformingLoader $sourceLoader) { $sourceTransformers = array( new FilterInjectorTransformer($this->options, $sourceLoader->getId()), new MagicConstantTransformer($this), new WeavingTransformer( $this, new TokenReflection\Broker( new TokenReflection\Broker\Backend\Memory() ), $this->container->get('aspect.advice_matcher') ) ); return array( new CachingTransformer($this, $sourceTransformers) ); } /** * Add resources for kernel * * @param AspectContainer $container */ protected function addKernelResourcesToContainer(AspectContainer $container) { $trace = debug_backtrace(); $refClass = new \ReflectionObject($this); $container->addResource($trace[1]['file']); $container->addResource($refClass->getFileName()); } } src/Go/Core/AspectLoader.php000064400000011337144760114140011656 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use Go\Aop\Aspect; /** * Loader of aspects into the container */ class AspectLoader { /** * Aspect container instance * * @var null|AspectContainer */ protected $container = null; /** * List of aspect loaders * * @var array */ protected $loaders = array(); /** * Loader constructor */ public function __construct(AspectContainer $container) { $this->container = $container; } /** * Register an aspect loader extension * * This method allows to extend the logic of aspect loading by registering an extension for loader. * * @param AspectLoaderExtension $loader Loader to register */ public function registerLoaderExtension(AspectLoaderExtension $loader) { $targets = (array) $loader->getTarget(); foreach ($targets as $target) { $this->loaders[$target][] = $loader; } } /** * Loads an aspect into the container with the help of aspect loaders * * @param \Go\Aop\Aspect $aspect */ final public function load(Aspect $aspect) { $refAspect = new \ReflectionClass($aspect); if (!empty($this->loaders[AspectLoaderExtension::TARGET_CLASS])) { $this->loadFrom($aspect, $refAspect, $this->loaders[AspectLoaderExtension::TARGET_CLASS]); } if (!empty($this->loaders[AspectLoaderExtension::TARGET_METHOD])) { $refMethods = $refAspect->getMethods(); foreach ($refMethods as $refMethod) { $this->loadFrom($aspect, $refMethod, $this->loaders[AspectLoaderExtension::TARGET_METHOD]); } } if (!empty($this->loaders[AspectLoaderExtension::TARGET_PROPERTY])) { $refProperties = $refAspect->getProperties(); foreach ($refProperties as $refProperty) { $this->loadFrom($aspect, $refProperty, $this->loaders[AspectLoaderExtension::TARGET_PROPERTY]); } } } /** * Load definitions from specific aspect part into the aspect container * * @param Aspect $aspect Aspect instance * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $refPoint Reflection instance * @param array|AspectLoaderExtension[] $loaders List of loaders that can produce advisors from aspect class * * @throws \InvalidArgumentException If kind of loader isn't supported */ protected function loadFrom(Aspect $aspect, $refPoint, array $loaders) { foreach ($loaders as $loader) { $loaderKind = $loader->getKind(); switch ($loaderKind) { case AspectLoaderExtension::KIND_REFLECTION: if ($loader->supports($aspect, $refPoint)) { $loader->load($this->container, $aspect, $refPoint); } break; case AspectLoaderExtension::KIND_ANNOTATION: $annotations = $this->getAnnotations($refPoint); foreach ($annotations as $annotation) { if ($loader->supports($aspect, $refPoint, $annotation)) { $loader->load($this->container, $aspect, $refPoint, $annotation); } } break; default: throw new \InvalidArgumentException("Unsupported loader kind {$loaderKind}"); } } } /** * Return list of annotations for reflection point * * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $refPoint Reflection instance * * @return array list of annotations * @throws \InvalidArgumentException if $refPoint is unsupported */ protected function getAnnotations($refPoint) { /** @var $annotationReader \Doctrine\Common\Annotations\Reader */ $annotationReader = $this->container->get('aspect.annotation.reader'); switch (true) { case ($refPoint instanceof \ReflectionClass): return $annotationReader->getClassAnnotations($refPoint); case ($refPoint instanceof \ReflectionMethod): return $annotationReader->getMethodAnnotations($refPoint); case ($refPoint instanceof \ReflectionProperty): return $annotationReader->getPropertyAnnotations($refPoint); default: throw new \InvalidArgumentException("Unsupported reflection point " . get_class($refPoint)); } } } src/Go/Core/AspectLoaderExtension.php000064400000004611144760114140013550 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use Go\Aop\Aspect; /** * Extension interface that defines an API for aspect loaders */ interface AspectLoaderExtension { /** * Loader works with class-level definition of aspect */ const TARGET_CLASS = 'class'; /** * Loader works with method definitions of aspect */ const TARGET_METHOD = 'method'; /** * Loader works with property definitions of aspect */ const TARGET_PROPERTY = 'property'; /** * Loader works only with reflections of aspect class, method or property */ const KIND_REFLECTION = 'reflection'; /** * Loader works with each annotation defined for aspect class, method or property. */ const KIND_ANNOTATION = 'annotation'; /** * Return kind of loader, can be one of KIND_REFLECTION or KIND_ANNOTATION * * For loader that works with annotations additional metaInformation will be passed * * @return string */ public function getKind(); /** * Returns one or more target for loader, see TARGET_XXX constants * * @return string|array */ public function getTarget(); /** * Checks if loader is able to handle specific point of aspect * * @param Aspect $aspect Instance of aspect * @param mixed|\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point * @param mixed|null $metaInformation Additional meta-information, e.g. annotation for method * * @return boolean true if extension is able to create an advisor from reflection and metaInformation */ public function supports(Aspect $aspect, $reflection, $metaInformation = null); /** * Loads definition from specific point of aspect into the container * * @param AspectContainer $container Instance of container * @param Aspect $aspect Instance of aspect * @param mixed|\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point * @param mixed|null $metaInformation Additional meta-information, e.g. annotation for method */ public function load(AspectContainer $container, Aspect $aspect, $reflection, $metaInformation = null); } src/Go/Core/Container.php000064400000005045144760114140011231 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; /** * DI-container */ class Container { /** * List of services in the container * * @var array */ protected $values = array(); /** * Store identifiers os services by tags * * @var array */ protected $tags = array(); /** * Set a service into the container * * @param string $id Identifier * @param mixed $value Value to store * @param array $tags Additional tags */ public function set($id, $value, array $tags = array()) { $this->values[$id] = $value; foreach ($tags as $tag) { $this->tags[$tag][] = $id; } } /** * Set a shared value in the container * * @param string $id Identifier * @param callable $value Value to store * @param array $tags Additional tags * * @throws \InvalidArgumentException if value is not callable */ public function share($id, $value, array $tags = array()) { if (!is_callable($value)) { throw new \InvalidArgumentException("Only callable values can be shared in the container"); } $value = function ($container) use ($value, $id) { static $sharedValue; if (null === $sharedValue) { $sharedValue = $value($container); } return $sharedValue; }; $this->set($id, $value, $tags); } /** * Return a service or value from the container * * @param string $id Identifier * * @return mixed * @throws \OutOfBoundsException if service was not found */ public function get($id) { if (!isset($this->values[$id])) { throw new \OutOfBoundsException("Value {$id} is not defined in the container"); } if (is_callable($this->values[$id])) { return $this->values[$id]($this); } else { return $this->values[$id]; } } /** * Return list of service tagged with marker * * @param string $tag Tag to select * @return array */ public function getByTag($tag) { $result = array(); if (isset($this->tags[$tag])) { foreach ($this->tags[$tag] as $id) { $result[$id] = $this->get($id); } } return $result; } } src/Go/Core/GeneralAspectLoaderExtension.php000064400000016177144760114140015060 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use ReflectionMethod; use ReflectionProperty; use Go\Aop\Aspect; use Go\Aop\Framework; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support; use Go\Aop\Support\DefaultPointcutAdvisor; use Go\Lang\Annotation; use Dissect\Lexer\Exception\RecognitionException; use Dissect\Parser\Exception\UnexpectedTokenException; /** * General aspect loader add common support for general advices, declared as annotations */ class GeneralAspectLoaderExtension extends AbstractAspectLoaderExtension { /** * General aspect loader works with annotations from aspect * * For extension that works with annotations additional metaInformation will be passed * * @return string */ public function getKind() { return self::KIND_ANNOTATION; } /** * General aspect loader works only with methods of aspect * * @return string|array */ public function getTarget() { return self::TARGET_METHOD; } /** * Checks if loader is able to handle specific point of aspect * * @param Aspect $aspect Instance of aspect * @param mixed|\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point * @param mixed|null $metaInformation Additional meta-information, e.g. annotation for method * * @return boolean true if extension is able to create an advisor from reflection and metaInformation */ public function supports(Aspect $aspect, $reflection, $metaInformation = null) { return $metaInformation instanceof Annotation\Interceptor || $metaInformation instanceof Annotation\Pointcut; } /** * Loads definition from specific point of aspect into the container * * @param AspectContainer $container Instance of container * @param Aspect $aspect Instance of aspect * @param mixed|\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point * @param mixed|null $metaInformation Additional meta-information, e.g. annotation for method */ public function load(AspectContainer $container, Aspect $aspect, $reflection, $metaInformation = null) { /** @var $pointcut Pointcut|PointFilter */ $pointcut = $this->parsePointcut($container, $aspect, $reflection, $metaInformation); $methodId = sprintf("%s->%s", get_class($aspect), $reflection->name); $adviceCallback = Framework\BaseAdvice::fromAspectReflection($aspect, $reflection); if (isset($metaInformation->scope) && $metaInformation->scope !== 'aspect') { $scope = $metaInformation->scope; $adviceCallback = Framework\BaseAdvice::createScopeCallback($adviceCallback, $scope); } $isPointFilter = $pointcut instanceof PointFilter; switch (true) { // Register a pointcut by its name case ($metaInformation instanceof Annotation\Pointcut): $container->registerPointcut($pointcut, $methodId); break; case ($isPointFilter && ($pointcut->getKind() & PointFilter::KIND_METHOD)): $advice = $this->getMethodInterceptor($metaInformation, $adviceCallback); if ($pointcut instanceof Support\DynamicMethodMatcher) { $advice = new Framework\DynamicMethodMatcherInterceptor( $pointcut, $advice ); } $container->registerAdvisor(new DefaultPointcutAdvisor($pointcut, $advice), $methodId); break; case ($isPointFilter && ($pointcut->getKind() & PointFilter::KIND_PROPERTY)): $advice = $this->getPropertyInterceptor($metaInformation, $adviceCallback); $container->registerAdvisor(new DefaultPointcutAdvisor($pointcut, $advice), $methodId); break; case ($isPointFilter && ($pointcut->getKind() & PointFilter::KIND_FUNCTION)): $advice = $this->getFunctionInterceptor($metaInformation, $adviceCallback); $container->registerAdvisor(new DefaultPointcutAdvisor($pointcut, $advice), $methodId); break; default: throw new \UnexpectedValueException("Unsupported pointcut class: " . get_class($pointcut)); } } /** * @param $metaInformation * @param $adviceCallback * @return \Go\Aop\Intercept\MethodInterceptor * @throws \UnexpectedValueException */ protected function getMethodInterceptor($metaInformation, $adviceCallback) { switch (true) { case ($metaInformation instanceof Annotation\Before): return new Framework\MethodBeforeInterceptor($adviceCallback, $metaInformation->order); case ($metaInformation instanceof Annotation\After): return new Framework\MethodAfterInterceptor($adviceCallback, $metaInformation->order); case ($metaInformation instanceof Annotation\Around): return new Framework\MethodAroundInterceptor($adviceCallback, $metaInformation->order); case ($metaInformation instanceof Annotation\AfterThrowing): return new Framework\MethodAfterThrowingInterceptor($adviceCallback, $metaInformation->order); default: throw new \UnexpectedValueException("Unsupported method meta class: " . get_class($metaInformation)); } } /** * @param $metaInformation * @param $adviceCallback * @return \Go\Aop\Intercept\MethodInterceptor * @throws \UnexpectedValueException */ protected function getFunctionInterceptor($metaInformation, $adviceCallback) { switch (true) { case ($metaInformation instanceof Annotation\Around): return new Framework\FunctionAroundInterceptor($adviceCallback, $metaInformation->order); default: throw new \UnexpectedValueException("Unsupported method meta class: " . get_class($metaInformation)); } } /** * @param $metaInformation * @param $adviceCallback * @return \Go\Aop\Intercept\FieldAccess * @throws \UnexpectedValueException */ protected function getPropertyInterceptor($metaInformation, $adviceCallback) { switch (true) { case ($metaInformation instanceof Annotation\Before): return new Framework\FieldBeforeInterceptor($adviceCallback, $metaInformation->order); case ($metaInformation instanceof Annotation\After): return new Framework\FieldAfterInterceptor($adviceCallback, $metaInformation->order); case ($metaInformation instanceof Annotation\Around): return new Framework\FieldAroundInterceptor($adviceCallback, $metaInformation->order); default: throw new \UnexpectedValueException("Unsupported method meta class: " . get_class($metaInformation)); } } }src/Go/Core/GoAspectContainer.php000064400000011565144760114140012663 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use Go\Aop; use Go\Aop\Pointcut\PointcutLexer; use Go\Aop\Pointcut\PointcutGrammar; use Go\Aop\Pointcut\PointcutParser; use Go\Instrument\RawAnnotationReader; use Doctrine\Common\Annotations\AnnotationReader; use TokenReflection\ReflectionClass as ParsedReflectionClass; /** * Aspect container contains list of all pointcuts and advisors */ class GoAspectContainer extends Container implements AspectContainer { /** * List of resources for application * * @var array */ protected $resources = array(); /** * Cached timestamp for resources * * @var integer */ protected $maxTimestamp = 0; /** * Constructor for container */ public function __construct() { // Register all services in the container $this->share('aspect.loader', function ($container) { $aspectLoader = new AspectLoader($container); // Register general aspect loader extension $aspectLoader->registerLoaderExtension(new GeneralAspectLoaderExtension()); $aspectLoader->registerLoaderExtension(new IntroductionAspectExtension()); return $aspectLoader; }); // Kernel flag to enable support of function interception $this->set('kernel.interceptFunctions', false); $this->share('aspect.advice_matcher', function ($container) { return new AdviceMatcher( $container->get('aspect.loader'), $container, $container->get('kernel.interceptFunctions') ); }); // TODO: use cached annotation reader $this->share('aspect.annotation.reader', function () { return new AnnotationReader(); }); $this->share('aspect.annotation.raw.reader', function () { return new RawAnnotationReader(); }); // Pointcut services $this->share('aspect.pointcut.lexer', function () { return new PointcutLexer(); }); $this->share('aspect.pointcut.parser', function ($container) { return new PointcutParser( new PointcutGrammar( $container, $container->get('aspect.annotation.raw.reader') ) ); }); } /** * Returns a pointcut by identifier * * @param string $id Pointcut identifier * * @return Aop\Pointcut */ public function getPointcut($id) { return $this->get("pointcut.{$id}"); } /** * Store the pointcut in the container * * @param Aop\Pointcut $pointcut Instance * @param string $id Key for pointcut */ public function registerPointcut(Aop\Pointcut $pointcut, $id) { $this->set("pointcut.{$id}", $pointcut, array('pointcut')); } /** * Store the advisor in the container * * @param Aop\Advisor $advisor Instance * @param string $id Key for advisor */ public function registerAdvisor(Aop\Advisor $advisor, $id) { $this->set("advisor.{$id}", $advisor, array('advisor')); } /** * Returns an aspect by id or class name * * @param string $aspectName Aspect name * * @return Aop\Aspect */ public function getAspect($aspectName) { return $this->get("aspect.{$aspectName}"); } /** * Register an aspect in the container * * @param Aop\Aspect $aspect Instance of concrete aspect */ public function registerAspect(Aop\Aspect $aspect) { $refAspect = new ReflectionClass($aspect); $this->set("aspect.{$refAspect->name}", $aspect, array('aspect')); $this->addResource($refAspect->getFileName()); } /** * Add an AOP resource to the container * * TODO: use symfony/config component for creating the cache * * Resources is used to check the freshness of AOP cache */ public function addResource($resource) { $this->resources[] = $resource; $this->maxTimestamp = 0; } /** * Returns list of AOP resources * * @return array */ public function getResources() { return $this->resources; } /** * Checks the freshness of AOP cache * * @param integer $timestamp * * @return bool Whether or not concrete file is fresh */ public function isFresh($timestamp) { if (!$this->maxTimestamp && $this->resources) { $this->maxTimestamp = max(array_map('filemtime', $this->resources)); } return $this->maxTimestamp < $timestamp; } } src/Go/Core/IntroductionAspectExtension.php000064400000006711144760114140015026 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Core; use ReflectionMethod; use ReflectionProperty; use Go\Aop\Aspect; use Go\Aop\Framework; use Go\Aop\Support; use Go\Lang\Annotation; /** * Introduction aspect extension */ class IntroductionAspectExtension extends AbstractAspectLoaderExtension { /** * Introduction aspect loader works with annotations from aspect * * For extension that works with annotations additional metaInformation will be passed * * @return string */ public function getKind() { return self::KIND_ANNOTATION; } /** * Introduction aspect loader works only with properties of aspect * * @return string|array */ public function getTarget() { return self::TARGET_PROPERTY; } /** * Checks if loader is able to handle specific point of aspect * * @param Aspect $aspect Instance of aspect * @param mixed|\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point * @param mixed|null $metaInformation Additional meta-information, e.g. annotation for method * * @return boolean true if extension is able to create an advisor from reflection and metaInformation */ public function supports(Aspect $aspect, $reflection, $metaInformation = null) { return ($metaInformation instanceof Annotation\DeclareParents && IS_MODERN_PHP) || ($metaInformation instanceof Annotation\DeclareError); } /** * Loads definition from specific point of aspect into the container * * @param AspectContainer $container Instance of container * @param Aspect $aspect Instance of aspect * @param mixed|\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection Reflection of point * @param mixed|null $metaInformation Additional meta-information * * @throws \UnexpectedValueException */ public function load(AspectContainer $container, Aspect $aspect, $reflection, $metaInformation = null) { $pointcut = $this->parsePointcut($container, $aspect, $reflection, $metaInformation); $propertyId = sprintf("%s->%s", $reflection->class, $reflection->name); switch (true) { case ($metaInformation instanceof Annotation\DeclareParents): $interface = $metaInformation->interface; $implement = $metaInformation->defaultImpl; $advice = new Framework\TraitIntroductionInfo($interface, $implement); $advisor = new Support\DeclareParentsAdvisor($pointcut->getClassFilter(), $advice); $container->registerAdvisor($advisor, $propertyId); break; case ($metaInformation instanceof Annotation\DeclareError): $reflection->setAccessible(true); $message = $reflection->getValue($aspect); $level = $metaInformation->level; $advice = new Framework\DeclareErrorInterceptor($message, $level); $container->registerAdvisor(new Support\DefaultPointcutAdvisor($pointcut, $advice), $propertyId); break; default: throw new \UnexpectedValueException("Unsupported pointcut class: " . get_class($pointcut)); } } }src/Go/Instrument/ClassLoading/AopComposerLoader.php000064400000005426144760114140016513 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\ClassLoading; use Go\Instrument\Transformer\FilterInjectorTransformer; use Composer\Autoload\ClassLoader; use Doctrine\Common\Annotations\AnnotationRegistry; /** * AopComposerLoader class is responsible to use a weaver for classes instead of original one * * @package Go\Instrument\ClassLoading */ class AopComposerLoader { /** * Instance of original autoloader * * @var ClassLoader */ protected $original = null; /** * List of internal dependencies that should not be analyzed by AOP * * @var array */ protected $internalNamespaces = array( 'Go', 'Dissect', 'Doctrine\\Common', 'TokenReflection', ); /** * Constructs an wrapper for the composer loader * * @param ClassLoader $original Instance of current loader */ public function __construct(ClassLoader $original) { $this->original = $original; } /** * Initialize aspect autoloader * * Replaces original composer autoloader with wrapper */ public static function init() { $loaders = spl_autoload_functions(); foreach ($loaders as &$loader) { $loaderToUnregister = $loader; if (is_array($loader) && ($loader[0] instanceof ClassLoader)) { $originalLoader = $loader[0]; // Configure library loader for doctrine annotation loader AnnotationRegistry::registerLoader(function($class) use ($originalLoader) { $originalLoader->loadClass($class); return class_exists($class, false); }); $loader[0] = new AopComposerLoader($loader[0]); } spl_autoload_unregister($loaderToUnregister); } unset($loader); foreach ($loaders as $loader) { spl_autoload_register($loader); } } /** * Autoload a class by it's name */ public function loadClass($class) { if ($file = $this->original->findFile($class)) { $isInternal = false; foreach ($this->internalNamespaces as $ns) { if (strpos($class, $ns) === 0) { $isInternal = true; break; } } include ($isInternal ? $file : FilterInjectorTransformer::rewrite($file)); } } /** * {@inheritDoc} */ public function findFile($class) { return $this->original->findFile($class); } } AopComposerLoader::init(); src/Go/Instrument/ClassLoading/LoadTimeWeaver.php000064400000001203144760114140015772 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\ClassLoading; use Go\Instrument\Transformer\SourceTransformer; /** * @package go * @subpackage instrument */ interface LoadTimeWeaver { /** * Adds a SourceTransformer to be applied by this LoadTimeWeaver. * * @param $transformer SourceTransformer Transformer for source code * * @return void */ public function addTransformer(SourceTransformer $transformer); } src/Go/Instrument/ClassLoading/SourceTransformingLoader.php000064400000007721144760114140020116 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\ClassLoading; use php_user_filter as PhpStreamFilter; use Go\Instrument\Transformer\StreamMetaData; use Go\Instrument\Transformer\SourceTransformer; use Go\Instrument\Transformer\FilterInjectorTransformer; /** * Php class loader filter for processing php code */ class SourceTransformingLoader extends PhpStreamFilter implements LoadTimeWeaver { /** * Php filter definition */ const PHP_FILTER_READ = 'php://filter/read='; /** * Default PHP filter name for registration */ const FILTER_IDENTIFIER = 'go.source.transforming.loader'; /** * String buffer * * @var string */ protected $data = ''; /** * Filter bucket resource * * @var null|resource */ protected $bucket = null; /** * List of transformers * * @var array|SourceTransformer[] */ protected static $transformers = array(); /** * Identifier of filter * * @var string */ protected static $filterId; /** * Register current loader as stream filter in PHP * * @param string $filterId Identifier for the filter * @throws \RuntimeException If registration was failed */ public function register($filterId = self::FILTER_IDENTIFIER) { if (!empty(self::$filterId)) { throw new \RuntimeException('Stream filter already registered'); } $result = stream_filter_register($filterId, __CLASS__); if (!$result) { throw new \RuntimeException('Stream filter was not registered'); } self::$filterId = $filterId; } /** * Returns the name of registered filter * * @throws \RuntimeException if filter was not registered * @return string */ public function getId() { if (empty(self::$filterId)) { throw new \RuntimeException('Stream filter was not registered'); } return self::$filterId; } /** * {@inheritdoc} */ public function filter($in, $out, &$consumed, $closing) { while ($bucket = stream_bucket_make_writeable($in)) { $this->data .= $bucket->data; $this->bucket = $bucket; $consumed = 0; } if ($closing) { $consumed += strlen($this->data); // $this->stream contains pointer to the source $metadata = new StreamMetaData($this->stream); $metadata->source = $this->data; $this->transformCode($metadata); $this->bucket->data = $metadata->source; $this->bucket->datalen = strlen($this->bucket->data); if (!empty($this->bucket->data)) { stream_bucket_append($out, $this->bucket); } return PSFS_PASS_ON; } return PSFS_FEED_ME; } /** * Adds a SourceTransformer to be applied by this LoadTimeWeaver. * * @param $transformer SourceTransformer Transformer for source code * * @return void */ public function addTransformer(SourceTransformer $transformer) { self::$transformers[] = $transformer; } /** * Load source file with transformation * * @param string $source Original source name * * @return mixed */ public function load($source) { return include FilterInjectorTransformer::rewrite($source); } /** * Transforms source code by passing it through all transformers * * @param StreamMetaData|null $metadata Metadata from stream * @return void */ protected function transformCode(StreamMetaData $metadata) { foreach (self::$transformers as $transformer) { $transformer->transform($metadata); } } } src/Go/Instrument/RawAnnotationReader.php000064400000012143144760114140014473 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use TokenReflection\ReflectionClass as ParsedReflectionClass; use TokenReflection\ReflectionMethod as ParsedReflectionMethod; use TokenReflection\ReflectionProperty as ParsedReflectionProperty; use Doctrine\Common\Annotations\DocParser; use Doctrine\Common\Annotations\Annotation\Target; /** * Simple Raw Annotation Reader. * * This annotation reader is intended to be used in projects where you have * full-control over all annotations that are available. * * @since 2.2 * @author Johannes M. Schmitt * @author Fabio B. Silva */ class RawAnnotationReader { /** * @var DocParser */ private $parser; /** * Constructor. * * Initializes a new SimpleAnnotationReader. */ public function __construct() { $this->parser = new DocParser(); $this->parser->setIgnoreNotImportedAnnotations(true); } /** * Set imports for annotations * * @param array $imports */ public function setImports(array $imports) { $convertedImports = array(); foreach ($imports as $aliasName=>$fullName) { $convertedImports[strtolower($aliasName)] = $fullName; } $this->parser->setImports($convertedImports); } /** * Adds a namespace in which we will look for annotations. * * @param string $namespace */ public function addNamespace($namespace) { $this->parser->addNamespace($namespace); } /** * Gets the annotations applied to a class. * * @param ReflectionClass|ParsedReflectionClass $class The ReflectionClass of the class from which * the class annotations should be read. * @return array An array of Annotations. */ public function getClassAnnotations($class) { $this->parser->setTarget(Target::TARGET_CLASS); return $this->parser->parse($class->getDocComment(), 'class '.$class->getName()); } /** * Gets the annotations applied to a method. * * @param ReflectionMethod|ParsedReflectionMethod $method The ReflectionMethod of the method from which * the annotations should be read. * @return array An array of Annotations. */ public function getMethodAnnotations($method) { $this->parser->setTarget(Target::TARGET_METHOD); return $this->parser->parse($method->getDocComment(), 'method '.$method->getDeclaringClass()->name.'::'.$method->getName().'()'); } /** * Gets the annotations applied to a property. * * @param ReflectionProperty|ParsedReflectionProperty $property The ReflectionProperty of the property * from which the annotations should be read. * @return array An array of Annotations. */ public function getPropertyAnnotations($property) { $this->parser->setTarget(Target::TARGET_PROPERTY); return $this->parser->parse($property->getDocComment(), 'property '.$property->getDeclaringClass()->name.'::$'.$property->getName()); } /** * Gets a class annotation. * * @param ReflectionClass|ParsedReflectionClass $class The ReflectionClass of the class from which * the class annotations should be read. * @param string $annotationName The name of the annotation. * @return The Annotation or NULL, if the requested annotation does not exist. */ public function getClassAnnotation($class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * Gets a method annotation. * * @param ReflectionMethod|ParsedReflectionMethod $method * @param string $annotationName The name of the annotation. * @return The Annotation or NULL, if the requested annotation does not exist. */ public function getMethodAnnotation($method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * Gets a property annotation. * * @param ReflectionProperty|ParsedReflectionProperty $property * @param string $annotationName The name of the annotation. * @return The Annotation or NULL, if the requested annotation does not exist. */ public function getPropertyAnnotation($property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } }src/Go/Instrument/Transformer/BaseSourceTransformer.php000064400000002141144760114140017341 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\Transformer; use Go\Core\AspectKernel; use Go\Core\AspectContainer; /** * @package go * @subpackage instrument */ abstract class BaseSourceTransformer implements SourceTransformer { /** * Transformer options * * @var array */ protected $options = array(); /** * @var AspectKernel|null */ protected $kernel = null; /** * @var AspectContainer|null */ protected $container = null; /** * Default constructor for transformer * * @param AspectKernel $kernel Instance of aspect kernel * @param array $options Custom options or kernel options */ public function __construct(AspectKernel $kernel, array $options = array()) { $this->kernel = $kernel; $this->container = $kernel->getContainer(); $this->options = $options ?: $kernel->getOptions(); } }src/Go/Instrument/Transformer/CachingTransformer.php000064400000006553144760114140016655 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\Transformer; use Go\Core\AspectKernel; use Go\Core\AspectContainer; /** * Take transformed source from the cache * * @package go * @subpackage instrument */ class CachingTransformer extends BaseSourceTransformer { /** * Root path of application * * @var string */ protected $rootPath = ''; /** * Cache directory * * @var string */ protected $cachePath = ''; /** * @var array|SourceTransformer[] */ protected $transformers = array(); /** * Class constructor * * @param AspectKernel $kernel Instance of aspect kernel * @param array $transformers Source transformers */ public function __construct(AspectKernel $kernel, array $transformers) { parent::__construct($kernel); $cacheDir = $this->options['cacheDir']; if ($cacheDir) { if (!is_dir($cacheDir)) { $cacheRootDir = dirname($cacheDir); if (!is_writable($cacheRootDir) || !is_dir($cacheRootDir)) { throw new \InvalidArgumentException( "Can not create a directory {$cacheDir} for the cache. Parent directory {$cacheRootDir} is not writable or not exist."); } mkdir($cacheDir, 0770); } if (!is_writable($cacheDir)) { throw new \InvalidArgumentException("Cache directory {$cacheDir} is not writable"); } $this->cachePath = realpath($cacheDir); } $this->rootPath = realpath($this->options['appDir']); $this->transformers = $transformers; } /** * This method may transform the supplied source and return a new replacement for it * * @param StreamMetaData $metadata Metadata for source * @return void */ public function transform(StreamMetaData $metadata) { // Do not create a cache if (!$this->cachePath) { $this->processTransformers($metadata); return; } $originalUri = $metadata->uri; $cacheUri = str_replace($this->rootPath, $this->cachePath, $originalUri); $lastModified = filemtime($originalUri); $cacheModified = file_exists($cacheUri) ? filemtime($cacheUri) : 0; // TODO: add more accurate cache invalidation, like in Symfony2 if ($cacheModified < $lastModified || !$this->container->isFresh($cacheModified)) { $parentCacheDir = dirname($cacheUri); if (!is_dir($parentCacheDir)) { mkdir($parentCacheDir, 0770, true); } $this->processTransformers($metadata); file_put_contents($cacheUri, $metadata->source); return; } $metadata->source = file_get_contents($cacheUri); } /** * Iterates over transformers * * @param StreamMetaData $metadata Metadata for source code * @return void */ private function processTransformers(StreamMetaData $metadata) { foreach ($this->transformers as $transformer) { $transformer->transform($metadata); } } } src/Go/Instrument/Transformer/FilterInjectorTransformer.php000064400000013376144760114140020245 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\Transformer; /** * Injects source filter for require and include operations * * @package go * @subpackage instrument */ class FilterInjectorTransformer implements SourceTransformer { /** * Php filter definition */ const PHP_FILTER_READ = 'php://filter/read='; /** * Root path of application * * This path will be replaced with rewriteToPath * * @var string */ protected static $rootPath; /** * Path to rewrite for absolute address * * @var string */ protected static $rewriteToPath = null; /** * Name of the filter to inject * * @var string */ protected static $filterName; /** * Flag of configuration * * @var bool */ protected static $configured = false; /** * Debug mode * * @var bool */ protected static $debug = false; /** * Class constructor * * @param array $options Configuration options from kernel * @param string $filterName Name of the filter to inject */ public function __construct(array $options, $filterName) { self::configure($options, $filterName); } /** * Static configurator for filter * * @param array $options Kernel options * @param string $filterName Name of the filter to inject * * @throws \RuntimeException if filter was configured early */ protected static function configure(array $options, $filterName) { if (self::$configured) { throw new \RuntimeException("Filter injector can be configured only once."); } $rewriteToPath = $options['cacheDir']; if ($rewriteToPath) { self::$rewriteToPath = realpath($rewriteToPath); } self::$rootPath = realpath($options['appDir']); self::$filterName = $filterName; self::$debug = $options['debug']; self::$configured = true; } /** * Replace source path with correct one * * This operation can check for cache, can rewrite paths, add additional filters and much more * * @param string $resource Initial resource to include * @param string $originalDir Path to the directory from where include was called for resolving relative resources * * @return string Transformed path to the resource */ public static function rewrite($resource, $originalDir = '') { $resource = (string) $resource; if ($resource['0'] !== '/' && $resource[1] !== ':') { $resource = stream_resolve_include_path($resource) ?: stream_resolve_include_path("{$originalDir}/{$resource}"); } // If the cache is disabled, then use on-fly method if (!self::$rewriteToPath || self::$debug) { return self::PHP_FILTER_READ . self::$filterName . "/resource=" . $resource; } $newResource = str_replace( array('/', self::$rootPath), array(DIRECTORY_SEPARATOR, self::$rewriteToPath), $resource ); // Trigger creation of cache, this will create a cache file with $newResource name if (!file_exists($newResource)) { file_get_contents(self::PHP_FILTER_READ . self::$filterName . "/resource=" . $resource); } return $newResource; } /** * Wrap all includes into rewrite filter * * @param StreamMetaData $metadata Metadata for source * @return void */ public function transform(StreamMetaData $metadata) { if ((strpos($metadata->source, 'include')===false) && (strpos($metadata->source, 'require')===false)) { return; } static $lookFor = array( T_INCLUDE => true, T_INCLUDE_ONCE => true, T_REQUIRE => true, T_REQUIRE_ONCE => true ); $tokenStream = token_get_all($metadata->source); $transformedSource = ''; $isWaitingEnd = false; $insideBracesCount = 0; $isBracesFinished = false; $isTernaryOperator = false; foreach ($tokenStream as $token) { if ($isWaitingEnd && $token === '(') { if ($isWaitingEnd) { $insideBracesCount++; } } elseif ($isWaitingEnd && $token === ')') { if ($insideBracesCount > 0) { $insideBracesCount--; } else { $isBracesFinished = true; } } $lastBrace = ($isBracesFinished && $token === ')'); if ($isWaitingEnd && $token === '?') { $isTernaryOperator = true; } if ($isTernaryOperator && ($token === ';' || $lastBrace)) { $isTernaryOperator = false; } if ($isWaitingEnd && !$isTernaryOperator && $insideBracesCount == 0 && ($token === ';' || $token === ',' || $token === ':' || $lastBrace) ) { $isWaitingEnd = false; $transformedSource .= ', __DIR__)'; } list ($token, $value) = (array) $token + array(1 => $token); $transformedSource .= $value; if (!$isWaitingEnd && isset($lookFor[$token])) { $isWaitingEnd = true; $isBracesFinished = $isTernaryOperator = false; $transformedSource .= ' \\' . __CLASS__ . '::rewrite('; } } $metadata->source = $transformedSource; } } src/Go/Instrument/Transformer/MagicConstantTransformer.php000064400000006536144760114140020054 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\Transformer; use Go\Core\AspectKernel; /** * Replace magic directory and file constants in the source code * * @package go * @subpackage instrument */ class MagicConstantTransformer extends BaseSourceTransformer { /** * Root path of application * * @var string */ protected static $rootPath = ''; /** * Path to rewrite to (cache directory) * * @var string */ protected static $rewriteToPath = ''; /** * Class constructor * * @param AspectKernel $kernel Instance of kernel */ public function __construct(AspectKernel $kernel) { parent::__construct($kernel); self::$rootPath = realpath($this->options['appDir']); $rewriteToPath = $this->options['cacheDir']; if ($rewriteToPath) { self::$rewriteToPath = realpath($rewriteToPath); } } /** * This method may transform the supplied source and return a new replacement for it * * @param StreamMetaData $metadata Metadata for source * @return void */ public function transform(StreamMetaData $metadata) { // Make the job only when we use cache directory if (!self::$rewriteToPath) { return; } $hasReflectionFilename = strpos($metadata->source, 'getFileName') !== false; $hasMagicConstants = (strpos($metadata->source, '__DIR__') !== false) || (strpos($metadata->source, '__FILE__') !== false); if (!$hasMagicConstants && !$hasReflectionFilename) { return; } // Resolve magic constants if ($hasMagicConstants) { $this->replaceMagicConstants($metadata); } if ($hasReflectionFilename) { // TODO: need to make more reliable solution $metadata->source = preg_replace( '/\$([\w\$\-\>\:\(\)]*?getFileName\(\))/S', '\\' . __CLASS__ . '::resolveFileName(\$\1)', $metadata->source ); } } /** * Resolves file name from the cache directory to the real application root dir * * @param string $fileName Absolute file name * * @return string Resolved file name */ public static function resolveFileName($fileName) { return str_replace(self::$rewriteToPath, self::$rootPath, $fileName); } /** * Replace only magic constants in the code * * @param StreamMetaData $metadata */ private function replaceMagicConstants(StreamMetaData $metadata) { $originalUri = $metadata->uri; $replacement = array( T_FILE => $originalUri, T_DIR => dirname($originalUri) ); $tokenStream = token_get_all($metadata->source); $transformedSource = ''; foreach ($tokenStream as $token) { list ($token, $value) = (array) $token + array(1 => $token); if (isset($replacement[$token])) { $value = "'" . $replacement[$token] . "'"; } $transformedSource .= $value; } $metadata->source = $transformedSource; } } src/Go/Instrument/Transformer/SourceTransformer.php000064400000001106144760114140016546 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\Transformer; /** * @package go * @subpackage instrument */ interface SourceTransformer { /** * This method may transform the supplied source and return a new replacement for it * * @param StreamMetaData $metadata Metadata for source * @return void */ public function transform(StreamMetaData $metadata); } src/Go/Instrument/Transformer/StreamMetaData.php000064400000004165144760114140015727 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\Transformer; use ArrayObject; use InvalidArgumentException; /** * Stream metadata object * * @property bool timed_out TRUE if the stream timed out while waiting for data on the last call to fread() or fgets(). * @property bool blocked TRUE if the stream is in blocking IO mode * @property bool eof TRUE if the stream has reached end-of-file. * @property int unread_bytes the number of bytes currently contained in the PHP's own internal buffer. * @property string stream_type a label describing the underlying implementation of the stream. * @property string wrapper_type a label describing the protocol wrapper implementation layered over the stream. * @property mixed wrapper_data wrapper specific data attached to this stream. * @property array filters array containing the names of any filters that have been stacked onto this stream. * @property string mode the type of access required for this stream * @property bool seekable whether the current stream can be seeked. * @property string uri the URI/filename associated with this stream. * @property string source of the stream. */ class StreamMetaData extends ArrayObject { /** * Creates metadata object from stream * * @param resource $stream Instance of stream * @param string $source Source code or null * @throws \InvalidArgumentException for invalid stream */ public function __construct($stream, $source = null) { if (!is_resource($stream)) { throw new InvalidArgumentException("Stream should be valid resource"); } $metadata = stream_get_meta_data($stream); if ($source) { $metadata['source'] = $source; } if (preg_match('/resource=(.+)$/', $metadata['uri'], $matches)) { $metadata['uri'] = stream_resolve_include_path($matches[1]); } parent::__construct($metadata, ArrayObject::ARRAY_AS_PROPS); } } src/Go/Instrument/Transformer/WeavingTransformer.php000064400000020604144760114140016712 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Instrument\Transformer; use Go\Aop\Framework\AbstractJoinpoint; use Go\Core\AspectContainer; use Go\Core\AdviceMatcher; use Go\Core\AspectKernel; use Go\Proxy\ClassProxy; use Go\Proxy\FunctionProxy; use Go\Proxy\TraitProxy; use TokenReflection\Broker; use TokenReflection\Exception\FileProcessingException; use TokenReflection\ReflectionClass as ParsedClass; use TokenReflection\ReflectionFileNamespace as ParsedFileNamespace; /** * @package go */ class WeavingTransformer extends BaseSourceTransformer { /** * Reflection broker instance * * @var Broker */ protected $broker; /** * @var AdviceMatcher */ protected $adviceMatcher; /** * List of include paths to process * * @var array */ protected $includePaths = array(); /** * List of exclude paths to process * * @var array */ protected $excludePaths = array(); /** * Constructs a weaving transformer * * @param AspectKernel $kernel Instance of aspect kernel * @param Broker $broker Instance of reflection broker to use * @param AdviceMatcher $adviceMatcher Advice matcher for class */ public function __construct(AspectKernel $kernel, Broker $broker, AdviceMatcher $adviceMatcher) { parent::__construct($kernel); $this->broker = $broker; $this->adviceMatcher = $adviceMatcher; $this->includePaths = array_map('realpath', $this->options['includePaths']); $this->excludePaths = array_map('realpath', $this->options['excludePaths']); } /** * This method may transform the supplied source and return a new replacement for it * * @param StreamMetaData $metadata Metadata for source * @return void */ public function transform(StreamMetaData $metadata) { $fileName = $metadata->uri; if (!$this->isAllowedToTransform($fileName)) { return; } try { $parsedSource = $this->broker->processString($metadata->source, $fileName, true); } catch (FileProcessingException $e) { // TODO: collect this exception and make a record in the modified source // TODO: Maybe just ask a developer to add this file into exclude list? return; } /** @var $namespaces ParsedFileNamespace[] */ $namespaces = $parsedSource->getNamespaces(); assert('count($namespaces) < 2; /* Only one namespace per file is supported */'); $lineOffset = 0; foreach ($namespaces as $namespace) { /** @var $classes ParsedClass[] */ $classes = $namespace->getClasses(); foreach ($classes as $class) { // Skip interfaces and aspects if ($class->isInterface() || in_array('Go\Aop\Aspect', $class->getInterfaceNames())) { continue; } $this->processSingleClass($metadata, $class, $lineOffset); } $this->processFunctions($metadata, $namespace); } } /** * Performs weaving of single class if needed * * @param StreamMetaData $metadata Source stream information * @param ParsedClass $class Instance of class to analyze * @param integer $lineOffset Current offset, will be updated to store the last position */ private function processSingleClass(StreamMetaData $metadata, $class, &$lineOffset) { $advices = $this->adviceMatcher->getAdvicesForClass($class); if (!$advices) { // Fast return if there aren't any advices for that class return; } // Sort advices in advance to keep the correct order in cache foreach ($advices as &$typeAdvices) { foreach ($typeAdvices as &$joinpointAdvices) { if (is_array($joinpointAdvices)) { $joinpointAdvices = AbstractJoinpoint::sortAdvices($joinpointAdvices); } } } // Prepare new parent name $newParentName = $class->getShortName() . AspectContainer::AOP_PROXIED_SUFFIX; // Replace original class name with new $metadata->source = $this->adjustOriginalClass($class, $metadata->source, $newParentName); // Prepare child Aop proxy $child = (IS_MODERN_PHP && $class->isTrait()) ? TraitProxy::generate($class, $advices) : ClassProxy::generate($class, $advices); // Set new parent name instead of original $child->setParentName($newParentName); // Add child to source $tokenCount = $class->getBroker()->getFileTokens($class->getFileName())->count(); if ($tokenCount - $class->getEndPosition() < 3) { // If it's the last class in a file, just add child source $metadata->source .= $child . PHP_EOL; } else { $lastLine = $class->getEndLine() + $lineOffset; // returns the last line of class $dataArray = explode("\n", $metadata->source); $currentClassArray = array_splice($dataArray, 0, $lastLine); $childClassArray = explode("\n", $child); $lineOffset += count($childClassArray) + 2; // returns LoC for child class + 2 blank lines $dataArray = array_merge($currentClassArray, array(''), $childClassArray, array(''), $dataArray); $metadata->source = implode("\n", $dataArray); } } /** * Adjust definition of original class source to enable extending * * @param ParsedClass $class Instance of class reflection * @param string $source Source code * @param string $newParentName New name for the parent class * * @return string Replaced code for class */ private function adjustOriginalClass($class, $source, $newParentName) { $type = (IS_MODERN_PHP && $class->isTrait()) ? 'trait' : 'class'; $source = preg_replace( "/{$type}\s+(" . $class->getShortName() . ')(\b)/iS', "{$type} {$newParentName}$2", $source ); if ($class->isFinal()) { // Remove final from class, child will be final instead $source = str_replace("final {$type}", $type, $source); } return $source; } /** * Verifies if file should be transformed or not * * @param string $fileName Name of the file to transform * @return bool */ private function isAllowedToTransform($fileName) { if ($this->includePaths) { $found = false; foreach ($this->includePaths as $includePath) { if (strpos($fileName, $includePath) === 0) { $found = true; break; } } if (!$found) { return false; } } foreach ($this->excludePaths as $excludePath) { if (strpos($fileName, $excludePath) === 0) { return false; } } return true; } /** * Performs weaving of functions in the current namespace * * @param StreamMetaData $metadata Source stream information * @param ParsedFileNamespace $namespace Current namespace for file */ private function processFunctions(StreamMetaData $metadata, $namespace) { $functionAdvices = $this->adviceMatcher->getAdvicesForFunctions($namespace); if ($functionAdvices && $this->options['cacheDir']) { $cacheDir = $this->options['cacheDir'] . '/_functions/'; $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace->getName()) . '.php'; $functionFileName = $cacheDir . $fileName; if (!file_exists($functionFileName) || !$this->container->isFresh(filemtime($functionFileName))) { $dirname = dirname($functionFileName); if (!file_exists($dirname)) { mkdir($dirname, 0770, true); } $source = FunctionProxy::generate($namespace, $functionAdvices); file_put_contents($functionFileName, $source); } $metadata->source .= 'include_once ' . var_export($functionFileName, true) . ';' . PHP_EOL; } } } src/Go/Lang/Annotation/After.php000064400000000570144760114140012451 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * After advice annotation * * @Annotation * @Target({ "METHOD", "PROPERTY" }) */ class After extends BaseInterceptor { } src/Go/Lang/Annotation/AfterThrowing.php000064400000000571144760114140014174 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * After throwing advice annotation * * @Annotation * @Target("METHOD") */ class AfterThrowing extends BaseInterceptor { } src/Go/Lang/Annotation/Around.php000064400000000572144760114140012642 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Around advice annotation * * @Annotation * @Target({ "METHOD", "PROPERTY" }) */ class Around extends BaseInterceptor { } src/Go/Lang/Annotation/Aspect.php000064400000000562144760114140012630 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Annotation marker class for aspect * * @Annotation * @Target("CLASS") */ class Aspect extends BaseAnnotation { } src/Go/Lang/Annotation/BaseAnnotation.php000064400000002602144760114140014313 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * @Annotation */ abstract class BaseAnnotation { /** * Value property. Common among all derived classes. * * @var string */ public $value; /** * Constructor * * @param array $data Key-value for properties to be defined in this class */ public final function __construct(array $data) { foreach ($data as $key => $value) { $this->$key = $value; } } /** * Error handler for unknown property accessor in Annotation class. * * @param string $name Unknown property name */ public function __get($name) { throw new \BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) ); } /** * Error handler for unknown property mutator in Annotation class. * * @param string $name Unknown property name * @param mixed $value Property value */ public function __set($name, $value) { throw new \BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) ); } } src/Go/Lang/Annotation/BaseInterceptor.php000064400000001065144760114140014501 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Default interceptor class with common attributes */ class BaseInterceptor extends BaseAnnotation implements Interceptor { /** * Order for advice * * @var integer */ public $order = 0; /** * Interceptor scope * * @var string */ public $scope = 'aspect'; } src/Go/Lang/Annotation/Before.php000064400000000572144760114140012614 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Before advice annotation * * @Annotation * @Target({ "METHOD", "PROPERTY" }) */ class Before extends BaseInterceptor { } src/Go/Lang/Annotation/DeclareError.php000064400000001132144760114140013754 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Declare error annotation * * @Annotation * @Target("PROPERTY") * * @Attributes({ * @Attribute("value", type = "string", required=true), * @Attribute("level", type = "integer"), * }) */ class DeclareError extends BaseAnnotation { /** * Interface name to add * * @var string */ public $level = E_USER_NOTICE; } src/Go/Lang/Annotation/DeclareParents.php000064400000001400144760114140014275 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Declare parents annotation * * @Annotation * @Target("PROPERTY") * * @Attributes({ * @Attribute("value", type = "string", required=true), * @Attribute("interface", type = "array"), * @Attribute("defaultImpl", type = "array") * }) */ class DeclareParents extends BaseAnnotation { /** * Default implementation (trait name) * * @var string */ public $defaultImpl = null; /** * Interface name to add * * @var string */ public $interface = null; } src/Go/Lang/Annotation/Interceptor.php000064400000000471144760114140013706 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Interceptor annotation marker */ interface Interceptor { } src/Go/Lang/Annotation/Pointcut.php000064400000000671144760114140013217 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Lang\Annotation; /** * Pointcut annotation * * @Annotation * @Target("METHOD") * * @Attributes({ * @Attribute("value", type = "string", required=true) * }) */ class Pointcut extends BaseAnnotation { } src/Go/Proxy/AbstractProxy.php000064400000021161144760114140012342 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Proxy; use Reflection; use ReflectionClass; use ReflectionParameter as Parameter; use ReflectionMethod as Method; use TokenReflection\ReflectionClass as ParsedClass; use TokenReflection\ReflectionParameter as ParsedParameter; use TokenReflection\ReflectionMethod as ParsedMethod; abstract class AbstractProxy { /** * Indent for source code * * @var int */ protected $indent = 4; /** * Parent class reflection * * @var null|ReflectionClass|ParsedClass */ protected $class = null; /** * Name for the current class * * @var string */ protected $name = ''; /** * Parent class name, can be changed manually * * @var string */ protected $parentClassName = null; /** * Source code for methods * * @var array Name of method => source code for it */ protected $methodsCode = array(); /** * List of additional interfaces to implement * * @var array */ protected $interfaces = array(); /** * List of additional traits for using * * @var array */ protected $traits = array(); /** * Source code for properties * * @var array Name of property => source code for it */ protected $propertiesCode = array(); /** * List of advices that are used for generation of child * * @var array */ protected $advices = array(); /** * Constructs abstract child class from Reflection * * @param ReflectionClass|ParsedClass $parentClass Reflection * @param string $thisName Name of the child class * @param array $advices List of advices * * @throws \InvalidArgumentException for invalid classes */ public function __construct($parentClass, $thisName, array $advices = array()) { if (!$parentClass instanceof ReflectionClass && !$parentClass instanceof ParsedClass) { throw new \InvalidArgumentException("Invalid argument for class"); } $this->advices = $advices; $this->class = $parentClass; $this->name = $thisName; $this->parentClassName = $parentClass->getShortName(); } /** * Updates parent name for child * * @param string $newParentName New class name * * @return AbstractProxy */ public function setParentName($newParentName) { $this->parentClassName = $newParentName; return $this; } /** * Override parent method with new body * * @param string $methodName Method name to override * @param string $body New body for method * * @return AbstractProxy */ public function override($methodName, $body) { $this->methodsCode[$methodName] = $this->getOverriddenMethod($this->class->getMethod($methodName), $body); return $this; } /** * Creates a method * * @param int $methodFlags See ReflectionMethod modifiers * @param string $methodName Name of the method * @param string $body Body of method * @param string $parameters Definition of parameters * * @return AbstractProxy */ public function setMethod($methodFlags, $methodName, $body, $parameters) { $this->methodsCode[$methodName] = sprintf("%s%s function %s(%s)\n{\n%s\n}\n", "/**\n * Method was created automatically, do not change it manually\n */\n", join(' ', Reflection::getModifierNames($methodFlags)), $methodName, $parameters, $this->indent($body) ); return $this; } /** * Add an interface for child * * @param string|ReflectionClass|ParsedClass $interface * * @throws \InvalidArgumentException If object is not an interface */ public function addInterface($interface) { $interfaceName = $interface; if ($interface instanceof ReflectionClass || $interface instanceof ParsedClass) { if (!$interface->isInterface()) { throw new \InvalidArgumentException("Interface expected to add"); } $interfaceName = $interface->name; } // Use absolute namespace to prevent NS-conflicts $this->interfaces[] = '\\' . ltrim($interfaceName, '\\'); } /** * Add a trait for child * * @param string|ReflectionClass|ParsedClass $trait * * @throws \InvalidArgumentException If object is not a trait */ public function addTrait($trait) { $traitName = $trait; if ($trait instanceof ReflectionClass || $trait instanceof ParsedClass) { if (!$trait->isTrait()) { throw new \InvalidArgumentException("Trait expected to add"); } $traitName = $trait->name; } // Use absolute namespace to prevent NS-conflicts $this->traits[] = '\\' . ltrim($traitName, '\\'); } /** * Creates a property * * @param int $propFlags See ReflectionProperty modifiers * @param string $propName Name of the property * @param null|string $defaultText Default value, should be string text! * * @return AbstractProxy */ public function setProperty($propFlags, $propName, $defaultText = null) { $this->propertiesCode[$propName] = sprintf("%s%s $%s%s;\n", "/**\n *Property was created automatically, do not change it manually\n */\n", join(' ', Reflection::getModifierNames($propFlags)), $propName, is_string($defaultText) ? " = $defaultText" : '' ); return $this; } /** * Returns text representation of class * * @return string */ abstract public function __toString(); /** * Creates a method code from Reflection * * @param Method|ParsedMethod $method Reflection for method * @param string $body Body of method * * @return string */ protected function getOverriddenMethod($method, $body) { $code = sprintf("%s%s function %s%s(%s)\n{\n%s\n}\n", preg_replace('/ {4}|\t/', '', $method->getDocComment()) ."\n", join(' ', Reflection::getModifierNames($method->getModifiers())), $method->returnsReference() ? '&' : '', $method->name, join(', ', $this->getParameters($method->getParameters())), $this->indent($body) ); return $code; } /** * Indent block of code * * @param string $text Non-indented text * * @return string Indented text */ protected function indent($text) { $pad = str_pad('', $this->indent, ' '); $lines = array_map(function ($line) use ($pad) { return $pad . $line; }, explode("\n", $text)); return join("\n", $lines); } /** * Returns list of string representation of parameters * * @param array|Parameter[]|ParsedParameter[] $parameters List of parameters * * @return array */ protected function getParameters(array $parameters) { $parameterDefinitions = array(); foreach ($parameters as $parameter) { $parameterDefinitions[] = $this->getParameterCode($parameter); } return $parameterDefinitions; } /** * Return string representation of parameter * * @param Parameter|ParsedParameter $parameter Reflection parameter * * @return string */ protected function getParameterCode($parameter) { $type = ''; if ($parameter->isArray()) { $type = 'array'; } elseif ($parameter->getClass()) { $type = '\\' . $parameter->getClass()->name; } $defaultValue = null; $isDefaultValueAvailable = $parameter->isDefaultValueAvailable(); if ($isDefaultValueAvailable) { if ($parameter instanceof ParsedParameter) { $defaultValue = $parameter->getDefaultValueDefinition(); } else { $defaultValue = var_export($parameter->getDefaultValue()); } } elseif ($parameter->allowsNull()) { $defaultValue = 'null'; } $code = sprintf('%s%s$%s%s', $type ? "$type " : '', $parameter->isPassedByReference() ? '&' : '', $parameter->name, $isDefaultValueAvailable ? (" = " . $defaultValue) : '' ); return $code; } } src/Go/Proxy/ClassProxy.php000064400000031711144760114140011646 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Proxy; use Reflection; use ReflectionClass; use ReflectionMethod as Method; use ReflectionParameter as Parameter; use ReflectionProperty as Property; use UnexpectedValueException; use Go\Aop\Advice; use Go\Aop\IntroductionInfo; use Go\Aop\Intercept\Joinpoint; use Go\Aop\Framework\ClassFieldAccess; use Go\Aop\Framework\ReflectionMethodInvocation; use Go\Aop\Framework\ClosureStaticMethodInvocation; use Go\Aop\Framework\ClosureDynamicMethodInvocation; use Go\Core\AspectContainer; use Go\Core\AspectKernel; use TokenReflection\ReflectionClass as ParsedClass; use TokenReflection\ReflectionMethod as ParsedMethod; use TokenReflection\ReflectionParameter as ParsedParameter; use TokenReflection\ReflectionProperty as ParsedProperty; /** * AOP Factory that is used to generate child code from joinpoints */ class ClassProxy extends AbstractProxy { /** * Static mappings for class name for excluding if..else check * * @var null|array */ protected static $invocationClassMap = null; /** * Flag to determine if we need to add a code for property interceptors * * @var bool */ private $isFieldsIntercepted = false; /** * List of intercepted properties names * * @var array */ private $interceptedProperties = array(); /** * Generates an child code by parent class reflection and joinpoints for it * * @param ReflectionClass|ParsedClass $parent Parent class reflection * @param array|Advice[] $classAdvices List of advices for class * * @throws \InvalidArgumentException if there are unknown type of advices * @return ClassProxy */ public static function generate($parent, array $classAdvices) { $aopChild = new self($parent, $parent->getShortName(), $classAdvices); $aopChild->addInterface('\Go\Aop\Proxy'); if (empty($classAdvices)) { return $aopChild; } $aopChild->addJoinpointsProperty($aopChild); foreach ($classAdvices as $type => $typedAdvices) { switch ($type) { case AspectContainer::METHOD_PREFIX: case AspectContainer::STATIC_METHOD_PREFIX: foreach ($typedAdvices as $joinPointName => $advice) { $aopChild->overrideMethod($parent->getMethod($joinPointName)); } break; case AspectContainer::PROPERTY_PREFIX: foreach ($typedAdvices as $joinPointName => $advice) { $aopChild->interceptProperty($parent->getProperty($joinPointName)); } break; case AspectContainer::INTRODUCTION_TRAIT_PREFIX: foreach ($typedAdvices as $advice) { /** @var $advice IntroductionInfo */ foreach ($advice->getInterfaces() as $interface) { $aopChild->addInterface($interface); } foreach ($advice->getTraits() as $trait) { $aopChild->addTrait($trait); } } break; default: throw new \InvalidArgumentException("Unsupported point `$type`"); } } return $aopChild; } /** * Inject advices into given class * * NB This method will be used as a callback during source code evaluation to inject joinpoints * * @param string $className Aop child proxy class * @param array|Advice[] $advices List of advices to inject into class * * @return void */ public static function injectJoinPoints($className, array $advices = array()) { $className = new ReflectionClass($className); $joinPoints = static::wrapWithJoinPoints($advices, $className->getParentClass()->name); /** @var $prop Property */ $prop = $className->getProperty('__joinPoints'); $prop->setAccessible(true); $prop->setValue($joinPoints); } /** * Initialize static mappings to reduce the time for checking if..else IS_MODERN_PHP */ protected static function setMappings() { $dynamicMethodClass = IS_MODERN_PHP ? 'Go\Aop\Framework\ClosureDynamicMethodInvocation' : 'Go\Aop\Framework\ReflectionMethodInvocation'; $staticMethodClass = IS_MODERN_PHP ? 'Go\Aop\Framework\ClosureStaticMethodInvocation' : 'Go\Aop\Framework\ReflectionMethodInvocation'; self::$invocationClassMap = array( AspectContainer::METHOD_PREFIX => $dynamicMethodClass, AspectContainer::STATIC_METHOD_PREFIX => $staticMethodClass, AspectContainer::PROPERTY_PREFIX => 'Go\Aop\Framework\ClassFieldAccess' ); } /** * Wrap advices with joinpoint object * * @param array|Advice[] $classAdvices Advices for specific class * @param string $className Name of the original class to use * * @throws \UnexpectedValueException If joinPoint type is unknown * * @todo Extension should be responsible for wrapping advice with join point. * * @return array|Joinpoint[] returns list of joinpoint ready to use */ protected static function wrapWithJoinPoints($classAdvices, $className) { if (!isset(self::$invocationClassMap)) { self::setMappings(); } $joinPoints = array(); foreach ($classAdvices as $joinPointType => $typedAdvices) { // if not isset then we don't want to create such invocation for class if (!isset(self::$invocationClassMap[$joinPointType])) { continue; } foreach ($typedAdvices as $joinPointName => $advices) { $joinpoint = new self::$invocationClassMap[$joinPointType]($className, $joinPointName, $advices); $joinPoints["$joinPointType:$joinPointName"] = $joinpoint; } } return $joinPoints; } /** * Adds a definition for joinpoints private property in the class * * @return void */ protected function addJoinpointsProperty() { $this->setProperty( Property::IS_PRIVATE | Property::IS_STATIC, '__joinPoints', 'array()' ); } /** * Override parent method with joinpoint invocation * * @param Method|ParsedMethod $method Method reflection */ protected function overrideMethod($method) { // temporary disable override of final methods if (!$method->isFinal() && !$method->isAbstract()) { $this->override($method->name, $this->getJoinpointInvocationBody($method)); } } /** * Creates definition for method body * * @param Method|ParsedMethod $method Method reflection * * @return string new method body */ protected function getJoinpointInvocationBody($method) { $isStatic = $method->isStatic(); $scope = $isStatic ? 'get_called_class()' : '$this'; $prefix = $isStatic ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX; $args = join(', ', array_map(function ($param) { /** @var $param Parameter|ParsedParameter */ $byReference = $param->isPassedByReference() ? '&' : ''; return $byReference . '$' . $param->name; }, $method->getParameters())); $body = ''; if (strpos($method->getSource(), 'func_get_args') !== false) { $body = '$argsList = func_get_args();' . PHP_EOL; if (empty($args)) { $scope = "$scope, \$argsList"; } else { $scope = "$scope, array($args) + \$argsList"; } } elseif (!empty($args)) { $scope = "$scope, array($args)"; } $body .= "return self::\$__joinPoints['{$prefix}:{$method->name}']->__invoke($scope);"; return $body; } /** * Makes property intercepted * * @param Property|ParsedProperty $property Reflection of property to intercept */ protected function interceptProperty($property) { $this->interceptedProperties[] = is_object($property) ? $property->name : $property; $this->isFieldsIntercepted = true; } /** * {@inheritDoc} */ public function __toString() { $ctor = $this->class->getConstructor(); if ($this->isFieldsIntercepted && (!$ctor || !$ctor->isPrivate())) { $this->addFieldInterceptorsCode($ctor); } $serialized = serialize($this->advices); ksort($this->methodsCode); ksort($this->propertiesCode); $prefix = join(' ', Reflection::getModifierNames($this->class->getModifiers())); $classCode = sprintf("%s\n%sclass %s extends %s%s\n{\n%s\n\n%s\n%s\n}", $this->class->getDocComment(), $prefix ? "$prefix " : '', $this->name, $this->parentClassName, $this->interfaces ? ' implements ' . join(', ', $this->interfaces) : '', $this->traits ? $this->indent('use ' . join(', ', $this->traits) .';') : '', $this->indent(join("\n", $this->propertiesCode)), $this->indent(join("\n", $this->methodsCode)) ); return $classCode // Inject advices on call . PHP_EOL . '\\' . __CLASS__ . "::injectJoinPoints('" . $this->class->name . "'," . " unserialize(" . var_export($serialized, true) . "));"; } /** * Add code for intercepting properties * * @param null|Method|ParsedMethod $constructor Constructor reflection or null */ protected function addFieldInterceptorsCode($constructor = null) { $this->setProperty(Property::IS_PRIVATE, '__properties', 'array()'); $this->setMethod(Method::IS_PUBLIC, '__get', $this->getMagicGetterBody(), '$name'); $this->setMethod(Method::IS_PUBLIC, '__set', $this->getMagicSetterBody(), '$name, $value'); $this->isFieldsIntercepted = true; if ($constructor) { $this->override('__construct', $this->getConstructorBody($constructor, true)); } else { $this->setMethod( Method::IS_PUBLIC, '__construct', $this->getConstructorBody($constructor, false), '' ); } } /** * Returns a code for magic getter to perform interception * * @return string */ private function getMagicGetterBody() { return <<<'GETTER' if (array_key_exists($name, $this->__properties)) { return self::$__joinPoints["prop:$name"]->__invoke( $this, \Go\Aop\Intercept\FieldAccess::READ, $this->__properties[$name] ); } elseif (method_exists(get_parent_class(), __FUNCTION__)) { return parent::__get($name); } else { trigger_error("Trying to access undeclared property {$name}"); return null; } GETTER; } /** * Returns a code for magic setter to perform interception * * @return string */ private function getMagicSetterBody() { return <<<'SETTER' if (array_key_exists($name, $this->__properties)) { $this->__properties[$name] = self::$__joinPoints["prop:$name"]->__invoke( $this, \Go\Aop\Intercept\FieldAccess::WRITE, $this->__properties[$name], $value ); } elseif (method_exists(get_parent_class(), __FUNCTION__)) { parent::__set($name, $value); } else { $this->$name = $value; } SETTER; } /** * Returns constructor code * * @param ParsedMethod|Method|null $constructor Constructor reflection * @param bool $isCallParent Is there is a need to call parent code * * @return string */ private function getConstructorBody($constructor, $isCallParent) { $assocProperties = array(); $listProperties = array(); foreach ($this->interceptedProperties as $propertyName) { $assocProperties[] = "'$propertyName' => \$this->$propertyName"; $listProperties[] = "\$this->$propertyName"; } $assocProperties = $this->indent(join(',' . PHP_EOL, $assocProperties)); $listProperties = $this->indent(join(',' . PHP_EOL, $listProperties)); if (isset($this->methodsCode['__construct'])) { $parentCall = $this->getJoinpointInvocationBody($constructor); } elseif ($isCallParent) { $parentCall = "call_user_func_array(array('parent', __FUNCTION__), func_get_args());"; } else { $parentCall = ''; } return <<__properties = array( $assocProperties ); unset( $listProperties ); $parentCall CTOR; } } src/Go/Proxy/FunctionProxy.php000064400000022342144760114140012366 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Proxy; use Go\Aop\Advice; use Go\Aop\Framework\ReflectionFunctionInvocation; use Go\Aop\Intercept\FunctionInvocation; use Go\Core\AspectContainer; use ReflectionFunction; use ReflectionParameter as Parameter; use TokenReflection\ReflectionFileNamespace; use TokenReflection\ReflectionParameter as ParsedParameter; use TokenReflection\ReflectionFunction as ParsedFunction; class FunctionProxy { /** * List of advices for functions * * @var array */ protected static $functionAdvices = array(); /** * Indent for source code * * @var int */ protected $indent = 4; /** * Name for the current namespace * * @var string */ protected $namespace = ''; /** * Source code for functions * * @var array Name of the function => source code for it */ protected $functionsCode = array(); /** * List of advices that are used for generation of stubs * * @var array */ protected $advices = array(); /** * Constructs functions stub class from namespace Reflection * * @param ReflectionFileNamespace $namespace Reflection of namespace * @param array $advices List of function advices * * @throws \InvalidArgumentException for invalid classes */ protected function __construct($namespace, array $advices = array()) { if (!$namespace instanceof ReflectionFileNamespace) { throw new \InvalidArgumentException("Invalid argument for namespace"); } $this->advices = $advices; $this->namespace = $namespace; } /** * Generates an child code by parent class reflection and joinpoints for it * * @param ReflectionFileNamespace $namespace Reflection of namespace * @param array|Advice[] $advices List of function advices * * @throws \InvalidArgumentException for unsupported advice type * @return FunctionProxy */ public static function generate($namespace, array $advices) { $functions = new self($namespace, $advices); if (empty($advices[AspectContainer::FUNCTION_PREFIX])) { return $functions; } foreach ($advices[AspectContainer::FUNCTION_PREFIX] as $pointName => $value) { $function = new ReflectionFunction($pointName); $functions->override($function, $functions->getJoinpointInvocationBody($function)); } return $functions; } /** * Returns a joinpoint for specific function in the namespace * * @param string $joinPointName Special joinpoint name * @param string $namespace Name of the namespace * * @return FunctionInvocation */ public static function getJoinPoint($joinPointName, $namespace) { $advices = self::$functionAdvices[$namespace][AspectContainer::FUNCTION_PREFIX][$joinPointName]; return new ReflectionFunctionInvocation($joinPointName, $advices); } /** * Inject advices for given trait * * NB This method will be used as a callback during source code evaluation to inject joinpoints * * @param string $namespace Aop child proxy class * @param array|Advice[] $advices List of advices to inject into class * * @return void */ public static function injectJoinPoints($namespace, array $advices = array()) { self::$functionAdvices[$namespace] = $advices; } /** * Override function with new body * * @param ReflectionFunction|ParsedFunction $function Function reflection * @param string $body New body for function * * @return $this */ public function override($function, $body) { $this->functionsCode[$function->name] = $this->getOverriddenFunction($function, $body); return $this; } /** * {@inheritDoc} */ public function __toString() { $serialized = serialize($this->advices); ksort($this->functionsCode); $functionsCode = sprintf("namespace->getDocComment(), $this->namespace->getName(), join("\n", $this->functionsCode) ); return $functionsCode // Inject advices on call . PHP_EOL . '\\' . __CLASS__ . "::injectJoinPoints('" . $this->namespace->getName() . "'," . " \unserialize(" . var_export($serialized, true) . "));"; } /** * Creates a function code from Reflection * * @param ParsedFunction $function Reflection for function * @param string $body Body of function * * @return string */ protected function getOverriddenFunction($function, $body) { static $inMemoryCache = array(); $functionName = $function->getName(); if (isset($inMemoryCache[$functionName])) { return $inMemoryCache[$functionName]; } $code = sprintf("%sfunction %s%s(%s)\n{\n%s\n}\n", preg_replace('/ {4}|\t/', '', $function->getDocComment()) ."\n", $function->returnsReference() ? '&' : '', $functionName, join(', ', $this->getParameters($function->getParameters())), $this->indent($body) ); $inMemoryCache[$functionName] = $code; return $code; } /** * Creates definition for trait method body * * @param ReflectionFunction|ParsedFunction $function Method reflection * * @return string new method body */ protected function getJoinpointInvocationBody($function) { $class = '\\' . __CLASS__; $dynamicArgs = false; $hasOptionals = false; $hasReferences = false; $argValues = array_map(function ($param) use (&$dynamicArgs, &$hasOptionals, &$hasReferences) { /** @var $param Parameter|ParsedParameter */ $byReference = $param->isPassedByReference(); $dynamicArg = $param->name == '...'; $dynamicArgs = $dynamicArgs || $dynamicArg; $hasOptionals = $hasOptionals || ($param->isOptional() && !$param->isDefaultValueAvailable()); $hasReferences = $hasReferences || $byReference; return ($byReference ? '&' : '') . '$' . $param->name; }, $function->getParameters()); if ($dynamicArgs) { // Remove last '...' argument array_pop($argValues); } $args = join(', ', $argValues); if ($dynamicArgs) { $args = $hasReferences ? "array($args) + \\func_get_args()" : '\func_get_args()'; } elseif ($hasOptionals) { $args = "\\array_slice(array($args), 0, \\func_num_args())"; } else { $args = "array($args)"; } return <<name}', __NAMESPACE__); } return \$__joinPoint->__invoke($args); BODY; } /** * Indent block of code * * @param string $text Non-indented text * * @return string Indented text */ protected function indent($text) { $pad = str_pad('', $this->indent, ' '); $lines = array_map(function ($line) use ($pad) { return $pad . $line; }, explode("\n", $text)); return join("\n", $lines); } /** * Returns list of string representation of parameters * * @param array|Parameter[]|ParsedParameter[] $parameters List of parameters * * @return array */ protected function getParameters(array $parameters) { $parameterDefinitions = array(); foreach ($parameters as $parameter) { if ($parameter->name == '...') { continue; } $parameterDefinitions[] = $this->getParameterCode($parameter); } return $parameterDefinitions; } /** * Return string representation of parameter * * @param Parameter|ParsedParameter $parameter Reflection parameter * * @return string */ protected function getParameterCode($parameter) { $type = ''; if ($parameter->isArray()) { $type = 'array'; } elseif ($parameter->getClass()) { $type = '\\' . $parameter->getClass()->name; } $defaultValue = null; $isDefaultValueAvailable = $parameter->isDefaultValueAvailable(); if ($isDefaultValueAvailable) { if ($parameter instanceof ParsedParameter) { $defaultValue = $parameter->getDefaultValueDefinition(); } else { $defaultValue = var_export($parameter->getDefaultValue()); } } elseif ($parameter->allowsNull() || $parameter->isOptional()) { $defaultValue = 'null'; } $code = sprintf('%s%s$%s%s', $type ? "$type " : '', $parameter->isPassedByReference() ? '&' : '', $parameter->name, $defaultValue ? (" = " . $defaultValue) : '' ); return $code; } } src/Go/Proxy/TraitProxy.php000064400000012224144760114140011662 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Proxy; use ReflectionClass; use ReflectionMethod as Method; use ReflectionParameter as Parameter; use Go\Aop\Advice; use Go\Aop\IntroductionInfo; use Go\Core\AspectContainer; use Go\Core\AspectKernel; use TokenReflection\ReflectionClass as ParsedClass; use TokenReflection\ReflectionMethod as ParsedMethod; use TokenReflection\ReflectionParameter as ParsedParameter; /** * AOP Factory that is used to generate trait proxy from joinpoints */ class TraitProxy extends ClassProxy { /** * List of advices for traits * * @var array */ protected static $traitAdvices = array(); /** * Generates an child code by parent class reflection and joinpoints for it * * @param ReflectionClass|ParsedClass $parent Parent class reflection * @param array|Advice[] $traitAdvices List of advices for trait * * @throws \InvalidArgumentException for unsupported advice type * @return ClassProxy */ public static function generate($parent, array $traitAdvices) { $traitChild = new self($parent, $parent->getShortName(), $traitAdvices); if (empty($traitAdvices)) { return $traitChild; } foreach ($traitAdvices as $type => $typedAdvices) { switch ($type) { case AspectContainer::METHOD_PREFIX: case AspectContainer::STATIC_METHOD_PREFIX: foreach ($typedAdvices as $joinPointName => $advice) { $traitChild->overrideMethod($parent->getMethod($joinPointName)); } break; case AspectContainer::PROPERTY_PREFIX: case AspectContainer::INTRODUCTION_TRAIT_PREFIX: continue; default: throw new \InvalidArgumentException("Unsupported point `$type`"); } } return $traitChild; } /** * Inject advices for given trait * * NB This method will be used as a callback during source code evaluation to inject joinpoints * * @param string $className Aop child proxy class * @param array|\Go\Aop\Advice[] $traitAdvices List of advices to inject into class * * @return void */ public static function injectJoinPoints($className, array $traitAdvices = array()) { self::$traitAdvices[$className] = $traitAdvices; } public static function getJoinPoint($traitName, $className, $joinPointType, $pointName) { if (!isset(self::$invocationClassMap)) { self::setMappings(); } $advices = self::$traitAdvices[$traitName][$joinPointType][$pointName]; $joinpoint = new self::$invocationClassMap[$joinPointType]($className, $pointName . 'âž©', $advices); return $joinpoint; } /** * Creates definition for trait method body * * @param Method|ParsedMethod $method Method reflection * * @return string new method body */ protected function getJoinpointInvocationBody($method) { $isStatic = $method->isStatic(); $class = '\\' . __CLASS__; $scope = $isStatic ? 'get_called_class()' : '$this'; $prefix = $isStatic ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX; $args = join(', ', array_map(function ($param) { /** @var $param Parameter|ParsedParameter */ $byReference = $param->isPassedByReference() ? '&' : ''; return $byReference . '$' . $param->name; }, $method->getParameters())); $args = $scope . ($args ? ", array($args)" : ''); return <<name}'); } return \$__joinPoint->__invoke($args); BODY; } /** * {@inheritDoc} */ public function __toString() { $serialized = serialize($this->advices); ksort($this->methodsCode); $classCode = sprintf("%s\ntrait %s\n{\n%s\n\n%s\n}", $this->class->getDocComment(), $this->name, $this->indent( 'use ' . join(', ', array(-1 => $this->parentClassName) + $this->traits) . $this->getMethodAliasesCode() ), $this->indent(join("\n", $this->methodsCode)) ); return $classCode // Inject advices on call . PHP_EOL . '\\' . __CLASS__ . "::injectJoinPoints('" . $this->class->name . "'," . " unserialize(" . var_export($serialized, true) . "));"; } private function getMethodAliasesCode() { $aliasesLines = array(); foreach (array_keys($this->methodsCode) as $methodName) { $aliasesLines[] = "{$this->parentClassName}::{$methodName} as protected {$methodName}âž©;"; } return "{\n " . $this->indent(join("\n", $aliasesLines)) . "\n}"; } } tests/Go/Aop/Framework/AbstractFieldInterceptorTest.php000064400000001060144760114140017225 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; /** * @method \Go\Aop\Intercept\FieldAccess getInvocation(&$sequenceRecorder, $throwException = false) */ abstract class AbstractFieldInterceptorTest extends AbstractInterceptorTest { /** * Override the default class for method tests */ const INVOCATION_CLASS = 'Go\Aop\Intercept\FieldAccess'; } tests/Go/Aop/Framework/AbstractInterceptorTest.php000064400000003416144760114140016270 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Invocation; use PHPUnit_Framework_TestCase as TestCase; abstract class AbstractInterceptorTest extends TestCase { /** * Concrete class name for mock, should be redefined with LSB */ const INVOCATION_CLASS = 'Go\Aop\Intercept\Invocation'; /** * Returns a test advice that writes itself to the sequence * * @param array $sequenceRecorder * @return callable */ protected function getAdvice(&$sequenceRecorder) { return function () use (&$sequenceRecorder) { $sequenceRecorder[] = 'advice'; return 'advice'; }; } /** * Returns an empty invocation that can update the sequence on invocation * * @param array $sequenceRecorder * @return \PHPUnit_Framework_MockObject_MockObject|Invocation */ protected function getInvocation(&$sequenceRecorder, $throwException = false) { $invocation = $this->getMock(static::INVOCATION_CLASS); $invocation ->expects($this->any()) ->method('proceed') ->will( $this->returnCallback( function () use (&$sequenceRecorder, $throwException) { $sequenceRecorder[] = 'invocation'; if ($throwException) { throw new \RuntimeException('Expected exception'); } return 'invocation'; } ) ); return $invocation; } } tests/Go/Aop/Framework/AbstractJoinpointTest.php000064400000010006144760114140015734 0ustar00 $advice) { if (isset($order[$index])) { $this->assertInstanceOf($order[$index], $advice); } } } public function sortingTestSource() { return array( // #0 array( array( $this->getMock('Go\Aop\AdviceAfter'), $this->getMock('Go\Aop\AdviceBefore') ), array( 'Go\Aop\AdviceBefore', 'Go\Aop\AdviceAfter' ) ), // #1 array( array( $this->getMock('Go\Aop\AdviceAfter'), $this->getMock('Go\Aop\AdviceAround') ), array( 'Go\Aop\AdviceAfter', 'Go\Aop\AdviceAround' ) ), // #2 array( array( $this->getMock('Go\Aop\AdviceBefore'), $this->getMock('Go\Aop\AdviceAfter') ), array( 'Go\Aop\AdviceBefore', 'Go\Aop\AdviceAfter' ) ), // #3 array( array( $this->getMock('Go\Aop\AdviceBefore'), $this->getMock('Go\Aop\AdviceAround') ), array( 'Go\Aop\AdviceBefore', 'Go\Aop\AdviceAround' ) ), // #4 array( array( $this->getMock('Go\Aop\AdviceAround'), $this->getMock('Go\Aop\AdviceAfter') ), array( 'Go\Aop\AdviceAfter', 'Go\Aop\AdviceAround' ) ), // #5 array( array( $this->getMock('Go\Aop\AdviceAround'), $this->getMock('Go\Aop\AdviceBefore') ), array( 'Go\Aop\AdviceBefore', 'Go\Aop\AdviceAround' ) ), // #6 array( array( $this->getMock('Go\Aop\AdviceBefore'), $this->getMock('Go\Aop\AdviceAround'), $this->getMock('Go\Aop\AdviceBefore'), $this->getMock('Go\Aop\AdviceAfter'), ), array( 'Go\Aop\AdviceBefore', 'Go\Aop\AdviceBefore', 'Go\Aop\AdviceAfter', 'Go\Aop\AdviceAround', ) ), // #7 array( array( $forth = $this->getOrderedAdvice(4, 'ForthAdvice'), $first = $this->getOrderedAdvice(1, 'FirstAdvice') ), array( get_class($first), get_class($forth), ) ), ); } /** * Returns the ordered advice * * @param int $order Order * @param string $name Mock class name * @return \PHPUnit_Framework_MockObject_MockObject|OrderedAdvice */ private function getOrderedAdvice($order, $name) { $mock = $this->getMock('Go\Aop\Framework\OrderedAdvice', array(), array(), $name); $mock ->expects($this->any()) ->method('getAdviceOrder') ->will( $this->returnValue($order) ); return $mock; } } tests/Go/Aop/Framework/AbstractMethodInterceptorTest.php000064400000001073144760114140017426 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; /** * @method \Go\Aop\Intercept\MethodInvocation getInvocation(&$sequenceRecorder, $throwException = false) */ abstract class AbstractMethodInterceptorTest extends AbstractInterceptorTest { /** * Override the default class for method tests */ const INVOCATION_CLASS = 'Go\Aop\Intercept\MethodInvocation'; } tests/Go/Aop/Framework/AbstractMethodInvocationTest.php000064400000002125144760114140017240 0ustar00invocation = $this->getMockForAbstractClass( 'Go\Aop\Framework\AbstractMethodInvocation', array(__CLASS__, __FUNCTION__, array()) ); } public function testInvocationReturnsMethod() { $this->assertEquals(__CLASS__, $this->invocation->getMethod()->class); $this->assertEquals('setUp', $this->invocation->getMethod()->name); } public function testStaticPartEqualsToReflectionMethod() { $this->assertInstanceOf('ReflectionMethod', $this->invocation->getStaticPart()); } public function testCanAccessThis() { $this->assertNull($this->invocation->getThis()); $this->invocation->__invoke($this, array(1, 2, 3)); $this->assertEquals($this, $this->invocation->getThis()); $this->assertEquals(array(1, 2, 3), $this->invocation->getArguments()); } } tests/Go/Aop/Framework/BaseInterceptorTest.php000064400000006135144760114140015400 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Invocation; class BaseInterceptorTest extends AbstractInterceptorTest { /** * Concrete class name for mock, should be redefined with LSB */ const INVOCATION_CLASS = 'Go\Aop\Intercept\Invocation'; public static function setUpBeforeClass() { if (!defined('Go\Aop\Framework\IS_MODERN_PHP')) { define('Go\Aop\Framework\IS_MODERN_PHP', PHP_VERSION_ID > 50400); } } public function testReturnsRawAdvice() { $sequence = array(); $advice = $this->getAdvice($sequence); $interceptor = new BaseInterceptor($advice); $this->assertEquals($advice, $interceptor->getRawAdvice()); } public function testCanSerializeInterceptor() { $sequence = array(); $advice = $this->getAdvice($sequence); $mock = $this->getMock('Go\Aop\Framework\BaseInterceptor', array('serializeAdvice'), array($advice)); $mock ->staticExpects($this->any()) ->method('serializeAdvice') ->will( $this->returnCallback( function () use ($advice) { return array( 'scope' => 'aspect', 'method' => 'Go\Aop\Framework\{closure}', 'aspect' => 'Go\Aop\Framework\BaseInterceptorTest' ); } ) ); $mockClass = get_class($mock); $mockNameLength = strlen($mockClass); $result = serialize($mock); $expected = 'C:' . $mockNameLength . ':"' . $mockClass . '":161:{a:1:{s:12:"adviceMethod";a:3:{s:5:"scope";s:6:"aspect";s:6:"method";s:26:"Go\Aop\Framework\{closure}";s:6:"aspect";s:36:"Go\Aop\Framework\BaseInterceptorTest";}}}'; $this->assertEquals($expected, $result); } public function testCanUnserializeInterceptor() { $advice = $this->getAdvice($sequence); $mock = $this->getMock('Go\Aop\Framework\BaseInterceptor', array('unserializeAdvice'), array($advice)); $mock ->staticExpects($this->any()) ->method('unserializeAdvice') ->will( $this->returnCallback( function () use ($advice) { return $advice; } ) ); $mockClass = get_class($mock); $mockNameLength = strlen($mockClass); // Trick to mock unserialization of advice $serialized = 'C:' . $mockNameLength .':"' . $mockClass . '":161:{a:1:{s:12:"adviceMethod";a:3:{s:5:"scope";s:6:"aspect";s:6:"method";s:26:"Go\Aop\Framework\{closure}";s:6:"aspect";s:36:"Go\Aop\Framework\BaseInterceptorTest";}}}'; $result = unserialize($serialized); $this->assertEquals($advice, $result->getRawAdvice()); } } tests/Go/Aop/Framework/ClosureDynamicMethodInvocationTest.php000064400000007027144760114140020424 0ustar00markTestSkipped("Closure Method Invocation works only on PHP 5.4 and greater"); } } /** * Tests dynamic method invocations * * @dataProvider dynamicMethodsBatch */ public function testDynamicMethodInvocation($methodName, $expectedResult) { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new ClosureDynamicMethodInvocation(self::FIRST_CLASS_NAME, $methodName, array()); $result = $invocation($child); $this->assertEquals($expectedResult, $result); } public function testValueChangedByReference() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new ClosureDynamicMethodInvocation(self::FIRST_CLASS_NAME, 'passByReference', array()); $value = 'test'; $result = $invocation($child, array(&$value)); $this->assertEquals(null, $result); $this->assertEquals(null, $value); } public function testInvocationWithDynamicArguments() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new ClosureDynamicMethodInvocation(self::FIRST_CLASS_NAME, 'variableArgsTest', array()); $args = array(); $expected = ''; for ($i=0; $i<10; $i++) { $args[] = $i; $expected .= $i; $result = $invocation($child, $args); $this->assertEquals($expected, $result); } } public function testRecursionWorks() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('recursion')); $invocation = new ClosureDynamicMethodInvocation(self::FIRST_CLASS_NAME, 'recursion', array()); $child->expects($this->exactly(5))->method('recursion')->will($this->returnCallback( function ($value, $level) use ($child, $invocation) { return $invocation($child, array($value, $level)); } )); $this->assertEquals(5, $child->recursion(5,0)); $this->assertEquals(20, $child->recursion(5,3)); } public function testInterceptorIsCalledForInvocation() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $value = 'test'; $advice = $this->getMock('Go\Aop\Intercept\MethodInterceptor'); $advice->expects($this->once()) ->method('invoke') ->will($this->returnCallback(function (MethodInvocation $object) use (&$value) { $value = 'ok'; return $object->proceed(); })); $invocation = new ClosureDynamicMethodInvocation(self::FIRST_CLASS_NAME, 'publicMethod', array($advice)); $result = $invocation($child, array()); $this->assertEquals('ok', $value); $this->assertEquals(T_PUBLIC, $result); } public function dynamicMethodsBatch() { return array( array('publicMethod', T_PUBLIC), array('protectedMethod', T_PROTECTED), // array('privateMethod', T_PRIVATE), This will throw an ReflectionException, need to add use case for that ); } } tests/Go/Aop/Framework/ClosureStaticMethodInvocationTest.php000064400000010777144760114140020275 0ustar00markTestSkipped("Closure Method Invocation works only on PHP 5.4 and greater"); } } /** * Tests static method invocations with self * * @dataProvider staticSelfMethodsBatch */ public function testStaticSelfMethodInvocation($methodName, $expectedResult) { $childClass = $this->getMockClass(self::FIRST_CLASS_NAME, array('none')); $invocation = new ClosureStaticMethodInvocation($childClass, $methodName, array()); $result = $invocation($childClass); $this->assertEquals($expectedResult, $result); } /** * Tests static method invocations with self not overridden with parent * * @dataProvider staticSelfMethodsBatch */ public function testStaticSelfNotOverridden($methodName, $expectedResult) { $childClass = $this->getMockClass(self::FIRST_CLASS_NAME, array($methodName)); $invocation = new ClosureStaticMethodInvocation(self::FIRST_CLASS_NAME, $methodName, array()); $result = $invocation($childClass); $this->assertEquals($expectedResult, $result); } /** * Tests static method invocations with Late Static Binding * * @dataProvider staticLsbMethodsBatch */ public function testStaticLsbIsWorking($methodName) { $childClass = $this->getMockClass(self::FIRST_CLASS_NAME, array($methodName)); $invocation = new ClosureStaticMethodInvocation(self::FIRST_CLASS_NAME, $methodName, array()); $result = $invocation($childClass); $this->assertEquals($childClass, $result); } public function testValueChangedByReference() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new ClosureStaticMethodInvocation(self::FIRST_CLASS_NAME, 'staticPassByReference', array()); $value = 'test'; $result = $invocation($child, array(&$value)); $this->assertEquals(null, $result); $this->assertEquals(null, $value); } public function testRecursionWorks() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('staticLsbRecursion')); $invocation = new ClosureStaticMethodInvocation(self::FIRST_CLASS_NAME, 'staticLsbRecursion', array()); $child->staticExpects($this->exactly(5))->method('staticLsbRecursion')->will($this->returnCallback( function ($value, $level) use ($child, $invocation) { return $invocation($child, array($value, $level)); } )); $childClass = get_class($child); $this->assertEquals(5, $childClass::staticLsbRecursion(5,0)); $this->assertEquals(20, $childClass::staticLsbRecursion(5,3)); } public function testAdviceIsCalledForInvocation() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $value = 'test'; $advice = new MethodBeforeInterceptor(function () use (&$value) { $value = 'ok'; }); $invocation = new ClosureStaticMethodInvocation(self::FIRST_CLASS_NAME, 'staticSelfPublic', array($advice)); $result = $invocation($child, array()); $this->assertEquals('ok', $value); $this->assertEquals(T_PUBLIC, $result); } public function testInvocationWithDynamicArguments() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new ClosureStaticMethodInvocation(self::FIRST_CLASS_NAME, 'staticVariableArgsTest', array()); $args = array(); $expected = ''; for ($i=0; $i<10; $i++) { $args[] = $i; $expected .= $i; $result = $invocation($child, $args); $this->assertEquals($expected, $result); } } public function staticSelfMethodsBatch() { return array( array('staticSelfPublic', T_PUBLIC), array('staticSelfProtected', T_PROTECTED), array('staticSelfPublicAccessPrivate', T_PRIVATE), ); } public function staticLsbMethodsBatch() { return array( array('staticLsbPublic'), array('staticLsbProtected'), ); } } tests/Go/Aop/Framework/FieldAfterInterceptorTest.php000064400000002572144760114140016534 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; class FieldAfterInterceptorTest extends AbstractFieldInterceptorTest { public function testAdviceIsCalledAfterGet() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new FieldAfterInterceptor($advice); $result = $interceptor->get($invocation); $this->assertEquals('invocation', $result, "Advice should not affect the return value of invocation"); $this->assertEquals(array('invocation', 'advice'), $sequence, "After advice should be invoked after invocation"); } public function testAdviceIsCalledAfterSet() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new FieldAfterInterceptor($advice); $result = $interceptor->set($invocation); $this->assertEquals('invocation', $result, "Advice should not affect the return value of invocation"); $this->assertEquals(array('invocation', 'advice'), $sequence, "After advice should be invoked after invocation"); } } tests/Go/Aop/Framework/FieldAroundInterceptorTest.php000064400000005467144760114140016731 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\FieldAccess; class FieldAroundInterceptorTest extends AbstractFieldInterceptorTest { public function testGetIsNotCalledWithoutProceed() { $sequence = array(); $advice = $this->getAdvice($sequence); // advice will not call Invocation->proceed() $invocation = $this->getInvocation($sequence); $interceptor = new FieldAroundInterceptor($advice); $result = $interceptor->get($invocation); $this->assertEquals('advice', $result, "Advice should change the return value of invocation"); $this->assertEquals(array('advice'), $sequence, "Only advice should be invoked"); } public function testSetIsNotCalledWithoutProceed() { $sequence = array(); $advice = $this->getAdvice($sequence); // advice will not call Invocation->proceed() $invocation = $this->getInvocation($sequence); $interceptor = new FieldAroundInterceptor($advice); $result = $interceptor->set($invocation); $this->assertEquals('advice', $result, "Advice should change the return value of invocation"); $this->assertEquals(array('advice'), $sequence, "Only advice should be invoked"); } public function testGetIsCalledWithinAdvice() { $sequence = array(); $advice = function (FieldAccess $invocation) use (&$sequence) { $sequence[] = 'advice'; $result = $invocation->proceed(); $sequence[] = 'advice'; return $result; }; $invocation = $this->getInvocation($sequence); $interceptor = new FieldAroundInterceptor($advice); $result = $interceptor->get($invocation); $this->assertEquals('invocation', $result, "Advice should return an original return value"); $this->assertEquals(array('advice', 'invocation', 'advice'), $sequence, "Around logic should work"); } public function testSetIsCalledWithinAdvice() { $sequence = array(); $advice = function (FieldAccess $invocation) use (&$sequence) { $sequence[] = 'advice'; $result = $invocation->proceed(); $sequence[] = 'advice'; return $result; }; $invocation = $this->getInvocation($sequence); $interceptor = new FieldAroundInterceptor($advice); $result = $interceptor->set($invocation); $this->assertEquals('invocation', $result, "Advice should return an original return value"); $this->assertEquals(array('advice', 'invocation', 'advice'), $sequence, "Around logic should work"); } } tests/Go/Aop/Framework/FieldBeforeInterceptorTest.php000064400000002603144760114140016670 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; class FieldBeforeInterceptorTest extends AbstractFieldInterceptorTest { public function testAdviceIsCalledBeforeGet() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new FieldBeforeInterceptor($advice); $result = $interceptor->get($invocation); $this->assertEquals('invocation', $result, "Advice should not affect the return value of invocation"); $this->assertEquals(array('advice', 'invocation'), $sequence, "Before advice should be invoked before invocation"); } public function testAdviceIsCalledBeforeSet() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new FieldBeforeInterceptor($advice); $result = $interceptor->set($invocation); $this->assertEquals('invocation', $result, "Advice should not affect the return value of invocation"); $this->assertEquals(array('advice', 'invocation'), $sequence, "Before advice should be invoked before invocation"); } } tests/Go/Aop/Framework/FunctionAroundInterceptorTest.php000064400000003645144760114140017467 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\FunctionInvocation; /** * Class FunctionAroundInterceptorTest * * @method \Go\Aop\Intercept\FunctionInvocation getInvocation(&$sequenceRecorder, $throwException = false) */ class FunctionAroundInterceptorTest extends AbstractInterceptorTest { /** * Concrete class name for mock, should be redefined with LSB */ const INVOCATION_CLASS = 'Go\Aop\Intercept\FunctionInvocation'; public function testInvocationIsNotCalledWithoutProceed() { $sequence = array(); $advice = $this->getAdvice($sequence); // advice will not call Invocation->proceed() $invocation = $this->getInvocation($sequence); $interceptor = new FunctionAroundInterceptor($advice); $result = $interceptor->invoke($invocation); $this->assertEquals('advice', $result, "Advice should change the return value of invocation"); $this->assertEquals(array('advice'), $sequence, "Only advice should be invoked"); } public function testInvocationIsCalledWithinAdvice() { $sequence = array(); $advice = function (FunctionInvocation $invocation) use (&$sequence) { $sequence[] = 'advice'; $result = $invocation->proceed(); $sequence[] = 'advice'; return $result; }; $invocation = $this->getInvocation($sequence); $interceptor = new FunctionAroundInterceptor($advice); $result = $interceptor->invoke($invocation); $this->assertEquals('invocation', $result, "Advice should return an original return value"); $this->assertEquals(array('advice', 'invocation', 'advice'), $sequence, "Around logic should work"); } } tests/Go/Aop/Framework/MethodAfterInterceptorTest.php000064400000002673144760114140016733 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; class MethodAfterInterceptorTest extends AbstractMethodInterceptorTest { public function testAdviceIsCalledAfterInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new MethodAfterInterceptor($advice); $result = $interceptor->invoke($invocation); $this->assertEquals('invocation', $result, "Advice should not affect the return value of invocation"); $this->assertEquals(array('invocation', 'advice'), $sequence, "After advice should be invoked after invocation"); } public function testAdviceIsCalledAfterExceptionInInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence, true); $interceptor = new MethodAfterInterceptor($advice); $this->setExpectedException('RuntimeException'); try { $interceptor->invoke($invocation); } catch (\Exception $e) { $this->assertEquals(array('invocation', 'advice'), $sequence, "After advice should be invoked after invocation"); throw $e; } } } tests/Go/Aop/Framework/MethodAfterThrowingInterceptorTest.php000064400000002662144760114140020453 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; class MethodAfterThrowingInterceptorTest extends AbstractMethodInterceptorTest { public function testAdviceIsNotCalledAfterInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new MethodAfterThrowingInterceptor($advice); $result = $interceptor->invoke($invocation); $this->assertEquals('invocation', $result, "Advice should not affect the return value of invocation"); $this->assertEquals(array('invocation'), $sequence, "Advice should not be invoked"); } public function testAdviceIsCalledAfterExceptionInInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence, true); $interceptor = new MethodAfterThrowingInterceptor($advice); $this->setExpectedException('RuntimeException'); try { $interceptor->invoke($invocation); } catch (\Exception $e) { $this->assertEquals(array('invocation', 'advice'), $sequence, "Advice should be invoked after invocation"); throw $e; } }} tests/Go/Aop/Framework/MethodAroundInterceptorTest.php000064400000003156144760114140017117 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; use Go\Aop\Intercept\MethodInvocation; class MethodAroundInterceptorTest extends AbstractMethodInterceptorTest { public function testInvocationIsNotCalledWithoutProceed() { $sequence = array(); $advice = $this->getAdvice($sequence); // advice will not call Invocation->proceed() $invocation = $this->getInvocation($sequence); $interceptor = new MethodAroundInterceptor($advice); $result = $interceptor->invoke($invocation); $this->assertEquals('advice', $result, "Advice should change the return value of invocation"); $this->assertEquals(array('advice'), $sequence, "Only advice should be invoked"); } public function testInvocationIsCalledWithinAdvice() { $sequence = array(); $advice = function (MethodInvocation $invocation) use (&$sequence) { $sequence[] = 'advice'; $result = $invocation->proceed(); $sequence[] = 'advice'; return $result; }; $invocation = $this->getInvocation($sequence); $interceptor = new MethodAroundInterceptor($advice); $result = $interceptor->invoke($invocation); $this->assertEquals('invocation', $result, "Advice should return an original return value"); $this->assertEquals(array('advice', 'invocation', 'advice'), $sequence, "Around logic should work"); } } tests/Go/Aop/Framework/MethodBeforeInterceptorTest.php000064400000001555144760114140017072 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Framework; class MethodBeforeInterceptorTest extends AbstractMethodInterceptorTest { public function testAdviceIsCalledBeforeInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new MethodBeforeInterceptor($advice); $result = $interceptor->invoke($invocation); $this->assertEquals('invocation', $result, "Advice should not affect the return value of invocation"); $this->assertEquals(array('advice', 'invocation'), $sequence, "Before advice should be invoked before invocation"); } } tests/Go/Aop/Framework/ReflectionMethodInvocationTest.php000064400000011054144760114140017570 0ustar00getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new ReflectionMethodInvocation(self::FIRST_CLASS_NAME, $methodName, array()); $result = $invocation($child); $this->assertEquals($expectedResult, $result); } /** * Tests static method invocations with self * * @dataProvider staticSelfMethodsBatch */ public function testStaticSelfMethodInvocation($methodName, $expectedResult) { $childClass = $this->getMockClass(self::FIRST_CLASS_NAME, array('none')); $invocation = new ReflectionMethodInvocation($childClass, $methodName, array()); $result = $invocation($childClass); $this->assertEquals($expectedResult, $result); } /** * Tests static method invocations with self not overridden with parent * * @dataProvider staticSelfMethodsBatch */ public function testStaticSelfNotOverridden($methodName, $expectedResult) { $childClass = $this->getMockClass(self::FIRST_CLASS_NAME, array($methodName)); $invocation = new ReflectionMethodInvocation(self::FIRST_CLASS_NAME, $methodName, array()); $result = $invocation($childClass); $this->assertEquals($expectedResult, $result); } /** * Tests static method invocations with Late Static Binding * * @dataProvider staticLsbMethodsBatch */ public function testStaticLsbIsWorking($methodName) { $childClass = $this->getMockClass(self::FIRST_CLASS_NAME, array($methodName)); $invocation = new ReflectionMethodInvocation($childClass, $methodName, array()); // LSB is not working, so it returns parent class name :( So just check that this doesn't break anything $result = $invocation($childClass); $this->markTestSkipped("Late Static Binding is not working with Reflection"); } public function testValueChangedByReference() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new ReflectionMethodInvocation(self::FIRST_CLASS_NAME, 'passByReference', array()); $value = 'test'; $result = $invocation($child, array(&$value)); $this->assertEquals(null, $result); $this->assertEquals(null, $value); } public function testRecursionWorks() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('recursion')); $invocation = new ReflectionMethodInvocation(self::FIRST_CLASS_NAME, 'recursion', array()); $child->expects($this->exactly(5))->method('recursion')->will($this->returnCallback( function ($value, $level) use ($child, $invocation) { return $invocation($child, array($value, $level)); } )); $this->assertEquals(5, $child->recursion(5,0)); $this->assertEquals(20, $child->recursion(5,3)); } public function testAdviceIsCalledForInvocation() { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $value = 'test'; $advice = new MethodBeforeInterceptor(function ($object) use (&$value) { $value = 'ok'; }); $invocation = new ReflectionMethodInvocation(self::FIRST_CLASS_NAME, 'publicMethod', array($advice)); $result = $invocation($child, array()); $this->assertEquals('ok', $value); $this->assertEquals(T_PUBLIC, $result); } public function dynamicMethodsBatch() { return array( array('publicMethod', T_PUBLIC), array('protectedMethod', T_PROTECTED), // array('privateMethod', T_PRIVATE), This will throw an ReflectionException, need to add use case for that ); } public function staticSelfMethodsBatch() { return array( array('staticSelfPublic', T_PUBLIC), array('staticSelfProtected', T_PROTECTED), // array('staticSelfPrivate', T_PRIVATE), // This will give a Fatal Error for scope ); } public function staticLsbMethodsBatch() { return array( array('staticLsbPublic'), array('staticLsbProtected'), ); } } tests/Go/Aop/Pointcut/PointcutParserTest.php000064400000006445144760114140015145 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Pointcut; /** * Class PointcutParserTest defines common check for valid grammar parsing */ use Dissect\Lexer\Lexer; class PointcutParserTest extends \PHPUnit_Framework_TestCase { /** * @var null|Lexer */ protected $lexer = null; /** * @var null|PointcutParser */ protected $parser = null; /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); $this->lexer = new PointcutLexer(); $container = $this->getMock('Go\Core\AspectContainer'); $annotReader = $this->getMock('Go\Instrument\RawAnnotationReader'); $this->parser = new PointcutParser(new PointcutGrammar($container, $annotReader)); } /** * Tests parsing * * @dataProvider validPointcutDefinitions */ public function testParsingExpression($pointcutExpression) { $stream = $this->lexer->lex($pointcutExpression); $result = $this->parser->parse($stream); $this->assertNotNull($result); } public function validPointcutDefinitions() { return array( // Execution pointcuts array('execution(public Example->method(*))'), array('execution(final public Example\Aspect\*->method*(*))'), array('execution(protected|public **::*someStatic*Method*(*))'), array('@annotation(First\Second\Annotation\Class)'), // This will match property that has First\Second\Annotation\Class annotation array('@access(First\Second\Annotation\Class)'), // This will match anything in the class Specific\Class (both properties and methods) array('class(Specific\Class)'), // This will match all the methods in all classes of Go\Aspects\Blog\Package. array('within(Go\Aspects\Blog\Package\*)'), // This will match all the methods in all classes of Go\Aspects\Blog\Package and its sub packages. array('within(Go\Aspects\Blog\Package\**)'), // This will match all the methods in the DemoClass. array('within(Go\Aspects\Blog\Package\DemoClass)'), // This will match all the methods which are in classes which implement DemoInterface. array('within(DemoInterface+)'), // Access pointcuts array('access(* Example\Aspect\*->property*)'), array('access(protected Test\Class*->someProtected*Property)'), // Logic pointcuts array('!within(DemoInterface\Test+)'), array('within(DemoInterface+) && within(Some\Namespace\**)'), array('within(DemoInterface+) || within(Some\Namespace\**)'), // Parenthesis array('within(DemoInterface+) && ( within(**) || within(*) )'), // Control flow execution pointcuts array('cflowbelow(execution(public Example->method(*)))'), // Function pointcut array('execution(Demo\*\Test\**\*(*))'), array('execution(Demo\Namespace\array_*_er(*))'), array('execution(**\*(*))'), ); } }tests/Go/Aop/Support/TruePointFilterTest.php000064400000001716144760114140015125 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace Go\Aop\Support; /** * Generated by PHPUnit_SkeletonGenerator 1.2.0 on 2012-11-28 at 15:42:10. */ class TruePointFilterTest extends \PHPUnit_Framework_TestCase { /** * @var TruePointFilter */ protected $filter; /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. */ protected function setUp() { $this->filter = TruePointFilter::getInstance(); } /** * Test that true matcher always matches the class */ public function testMatches() { // Works correctly with ReflectionClass $class = new \ReflectionClass('stdClass'); $this->assertTrue($this->filter->matches($class)); } } tests/Go/Core/AdviceMatcherTest.php000064400000010433144760114140013216 0ustar00advisors; $advisors = array(); $this->container = $this->getMock('Go\Core\AspectContainer'); $this->container ->expects($this->any()) ->method('getByTag') ->will($this->returnValueMap(array( array('advisor', &$this->advisors), array('aspect', array()) ))); $this->container ->expects($this->any()) ->method('registerAdvisor') ->will($this->returnCallback(function ($advisor, $id) use (&$advisors) { $advisors[$id] = $advisor; })); $loader = $this->getMock('Go\Core\AspectLoader', array(), array($this->container)); $this->adviceMatcher = new AdviceMatcher($loader, $this->container); } /** * Verifies that empty result will be returned without aspects and advisors */ public function testGetEmptyAdvicesForClass() { // by class name $advices = $this->adviceMatcher->getAdvicesForClass(__CLASS__); $this->assertEmpty($advices); // by reflection $advices = $this->adviceMatcher->getAdvicesForClass(new \ReflectionClass(__CLASS__)); $this->assertEmpty($advices); } /** * Check that list of advices for method works correctly */ public function testGetSingleMethodAdviceForClassFromAdvisor() { $funcName = __FUNCTION__; $pointcut = $this->getMock('Go\Aop\Pointcut'); $pointcut ->expects($this->any()) ->method('getClassFilter') ->will($this->returnValue(TruePointFilter::getInstance())); $pointcut ->expects($this->any()) ->method('matches') ->will($this->returnCallback(function ($point) use ($funcName) { return $point->name === $funcName; })); $pointcut ->expects($this->any()) ->method('getKind') ->will($this->returnValue(Pointcut::KIND_METHOD)); $advice = $this->getMock('Go\Aop\Advice'); $advisor = new DefaultPointcutAdvisor($pointcut, $advice); $this->container->registerAdvisor($advisor, 'test'); $advices = $this->adviceMatcher->getAdvicesForClass(__CLASS__); $this->assertArrayHasKey(AspectContainer::METHOD_PREFIX, $advices); $this->assertArrayHasKey($funcName, $advices[AspectContainer::METHOD_PREFIX]); $this->assertCount(1, $advices[AspectContainer::METHOD_PREFIX]); } /** * Check that list of advices for fields works correctly */ public function testGetSinglePropertyAdviceForClassFromAdvisor() { $propName = 'container'; // $this->container; $pointcut = $this->getMock('Go\Aop\Pointcut'); $pointcut ->expects($this->any()) ->method('getClassFilter') ->will($this->returnValue(TruePointFilter::getInstance())); $pointcut ->expects($this->any()) ->method('matches') ->will($this->returnCallback(function ($point) use ($propName) { return $point->name === $propName; })); $pointcut ->expects($this->any()) ->method('getKind') ->will($this->returnValue(Pointcut::KIND_PROPERTY)); $advice = $this->getMock('Go\Aop\Advice'); $advisor = new DefaultPointcutAdvisor($pointcut, $advice); $this->container->registerAdvisor($advisor, 'test'); $advices = $this->adviceMatcher->getAdvicesForClass(__CLASS__); $this->assertArrayHasKey(AspectContainer::PROPERTY_PREFIX, $advices); $this->assertArrayHasKey($propName, $advices[AspectContainer::PROPERTY_PREFIX]); $this->assertCount(1, $advices[AspectContainer::PROPERTY_PREFIX]); } } tests/Go/Core/GoAspectContainerTest.php000064400000006163144760114140014074 0ustar00markTestIncomplete("Temporary disabled"); $this->container = new GoAspectContainer(); } /** * Tests that all services are registered * * @dataProvider internalServicesList */ public function testAllServicesAreConfigured($serviceId) { $service = $this->container->get($serviceId); $this->assertNotNull($service); } public function internalServicesList() { return array( array('aspect.loader'), array('aspect.advice_matcher'), array('aspect.annotation.reader'), array('aspect.annotation.raw.reader'), array('aspect.pointcut.lexer'), array('aspect.pointcut.parser'), ); } /** * Tests that pointcut can be registered and accessed */ public function testPointcutCanBeRegisteredAndReceived() { $pointcut = $this->getMock('Go\Aop\Pointcut'); $this->container->registerPointcut($pointcut, 'test'); $this->assertSame($pointcut, $this->container->getPointcut('test')); // Verify that tag is working $pointcuts = $this->container->getByTag('pointcut'); $this->assertSame(array('pointcut.test' => $pointcut), $pointcuts); } /** * Tests that pointcut can be registered and accessed */ public function testAdvisorCanBeRegistered() { $advisor = $this->getMock('Go\Aop\Advisor'); $this->container->registerAdvisor($advisor, 'test'); // Verify that tag is working $advisors = $this->container->getByTag('advisor'); $this->assertSame(array('advisor.test' => $advisor), $advisors); } /** * Tests that aspect can be registered and accessed */ public function testAspectCanBeRegisteredAndReceived() { $aspect = $this->getMock('Go\Aop\Aspect'); $aspectClass = get_class($aspect); $this->container->registerAspect($aspect); $this->assertSame($aspect, $this->container->getAspect($aspectClass)); // Verify that tag is working $aspects = $this->container->getByTag('aspect'); $this->assertSame(array("aspect.{$aspectClass}" => $aspect), $aspects); } /** * Tests that container resources can be added and isFresh works correctly */ public function testResourceManagement() { // Without resources this should be always true $isFresh = $this->container->isFresh(time()); $this->assertTrue($isFresh); $this->container->addResource(__FILE__); $realMtime = filemtime(__FILE__); $isFresh = $this->container->isFresh($realMtime - 3600); $this->assertFalse($isFresh); $isFresh = $this->container->isFresh($realMtime + 3600); $this->assertTrue($isFresh); } } tests/Go/Instrument/Transformer/FilterInjectorTransformerTest.php000064400000010123144760114140021443 0ustar00 null, 'appDir' => '', 'debug' => false, ), 'unit.test' ); } $stream = fopen('php://input', 'r'); $this->metadata = new StreamMetaData($stream); fclose($stream); } public function testCanTransformWithoutInclusion() { $this->metadata->source = ''; $output = $this->metadata->source; self::$transformer->transform($this->metadata); $this->assertEquals($output, $this->metadata->source); } public function testSkipTransformationQuickly() { $this->metadata->source = ''; $output = $this->metadata->source; self::$transformer->transform($this->metadata); $this->assertEquals($output, $this->metadata->source); } /** * @expectedException \RuntimeException */ public function testCanBeConfiguredOnlyOnce() { $filter = new FilterInjectorTransformer(array(), 'test'); } public function testCanTransformInclude() { $this->metadata->source = ''; self::$transformer->transform($this->metadata); $output = ''; $this->assertEquals($output, $this->metadata->source); } public function testCanTransformIncludeOnce() { $this->metadata->source = ''; self::$transformer->transform($this->metadata); $output = ''; $this->assertEquals($output, $this->metadata->source); } public function testCanTransformRequire() { $this->metadata->source = ''; self::$transformer->transform($this->metadata); $output = ''; $this->assertEquals($output, $this->metadata->source); } public function testCanTransformRequireOnce() { $this->metadata->source = ''; self::$transformer->transform($this->metadata); $output = ''; $this->assertEquals($output, $this->metadata->source); } public function testCanRewriteWithFilter() { $this->metadata->source = FilterInjectorTransformer::rewrite('/path/to/my/class.php'); $output = FilterInjectorTransformer::PHP_FILTER_READ . 'unit.test/resource=/path/to/my/class.php'; $this->assertEquals($output, $this->metadata->source); } public function testCanRewriteRelativePathsWithFilter() { $this->metadata->source = FilterInjectorTransformer::rewrite('_files/class.php', __DIR__); $output = FilterInjectorTransformer::PHP_FILTER_READ . 'unit.test/resource=' . stream_resolve_include_path('_files/class.php'); $this->assertEquals($output, $this->metadata->source); } public function testCanTransformWithBraces() { $this->metadata->source = file_get_contents(__DIR__ . '/_files/yii_style.php'); self::$transformer->transform($this->metadata); $this->assertEquals(file_get_contents(__DIR__ . '/_files/yii_style_output.php'), $this->metadata->source); } } tests/Go/Instrument/Transformer/MagicConstantTransformerTest.php000064400000006322144760114140021260 0ustar00transformer = new MagicConstantTransformer( $this->getKernelMock(array( 'cacheDir' => __DIR__, 'appDir' => dirname(__DIR__), )) ); $stream = fopen('php://filter/string.tolower/resource=' . __FILE__, 'r'); $this->metadata = new StreamMetaData($stream); fclose($stream); } /** * Returns a mock for kernel * * @return \PHPUnit_Framework_MockObject_MockObject|\Go\Core\AspectKernel */ protected function getKernelMock($options) { $mock = $this->getMockForAbstractClass( 'Go\Core\AspectKernel', array(), '', false, true, true, array('getOptions') ); $mock->expects($this->any()) ->method('getOptions') ->will( $this->returnValue($options) ); return $mock; } public function testTransformerReturnsWithoutMagicConsts() { $this->metadata->source = ''; $expected = $this->metadata->source; $this->transformer->transform($this->metadata); $this->assertSame($expected, $this->metadata->source); } public function testTransformerCanResolveDirMagicConst() { $this->metadata->source = ''; $expected = ''; $this->transformer->transform($this->metadata); $this->assertEquals($expected, $this->metadata->source); } public function testTransformerCanResolveFileMagicConst() { $this->metadata->source = ''; $expected = ''; $this->transformer->transform($this->metadata); $this->assertEquals($expected, $this->metadata->source); } public function testTransformerDoesNotReplaceStringWithConst() { $expected = ''; $this->metadata->source = $expected; $this->transformer->transform($this->metadata); $this->assertEquals($expected, $this->metadata->source); } public function testTransformerWrapsReflectionFileName() { $this->metadata->source = 'getFileName(); ?>'; $this->transformer->transform($this->metadata); $this->assertStringEndsWith('::resolveFileName($class->getFileName()); ?>', $this->metadata->source); } public function testTransformerResolvesFileName() { /** @var $class MagicConstantTransformer */ $class = get_class($this->transformer); $this->assertStringStartsWith(dirname(__DIR__), $class::resolveFileName(__FILE__)); } } tests/Go/Instrument/Transformer/WeavingTransformerTest.php000064400000021604144760114140020126 0ustar00broker = new \TokenReflection\Broker( new \TokenReflection\Broker\Backend\Memory() ); $this->adviceMatcher = $this->getAdviceMatcherMock(); $this->transformer = new WeavingTransformer( $this->getKernelMock( array( 'cacheDir' => __DIR__, 'appDir' => dirname(__DIR__), 'includePaths' => array(), 'excludePaths' => array() ), $this->getMock('Go\Core\AspectContainer') ), $this->broker, $this->adviceMatcher ); $stream = fopen('php://filter/string.tolower/resource=' . __FILE__, 'r'); $this->metadata = new StreamMetaData($stream); fclose($stream); } /** * It's a caution check that multiple namespaces are not yet supported * * @expectedException \PHPUnit_Framework_Error_Warning */ public function testMultipleNamespacesInOneFile() { $this->metadata->source = $this->loadTest('multiple-ns'); $this->transformer->transform($this->metadata); } /** * Do not make anything for code without classes */ public function testEmptyNamespaceInFile() { $source = $this->loadTest('empty-classes'); $this->metadata->source = $source; $this->transformer->transform($this->metadata); $this->assertEquals($source, $this->metadata->source); } /** * Do not make anything for interface class */ public function testInterfaceIsSkipped() { $source = $this->loadTest('interface'); $this->metadata->source = $source; $this->transformer->transform($this->metadata); $this->assertEquals($source, $this->metadata->source); } /** * Do not make anything for aspect class */ public function testAspectIsSkipped() { $source = $this->loadTest('aspect'); $this->metadata->source = $source; $this->transformer->transform($this->metadata); $this->assertEquals($source, $this->metadata->source); } /** * Main test case for class */ public function testWeaverForNormalClass() { $this->metadata->source = $this->loadTest('class'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('class-woven')); $this->assertEquals($expected, $actual); } /** * Check that weaver can work with final class */ public function testWeaverForFinalClass() { $this->metadata->source = $this->loadTest('final-class'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('final-class-woven')); $this->assertEquals($expected, $actual); } /** * Transformer verifies include paths */ public function testTransformerWithIncludePaths() { $this->transformer = new WeavingTransformer( $this->getKernelMock( array( 'cacheDir' => __DIR__, 'appDir' => dirname(__DIR__), 'includePaths' => array(__DIR__), 'excludePaths' => array() ), $this->getMock('Go\Core\AspectContainer') ), $this->broker, $this->adviceMatcher ); $this->metadata->source = $this->loadTest('class'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('class-woven')); $this->assertEquals($expected, $actual); } /** * Transformer verifies include paths */ public function testTransformerWithAnotherIncludePathSkip() { $this->transformer = new WeavingTransformer( $this->getKernelMock( array( 'cacheDir' => __DIR__, 'appDir' => dirname(__DIR__), 'includePaths' => array('/some/path'), 'excludePaths' => array() ), $this->getMock('Go\Core\AspectContainer') ), $this->broker, $this->adviceMatcher ); $this->metadata->source = $this->loadTest('class'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('class')); $this->assertEquals($expected, $actual); } /** * Transformer exclude paths for internal libraries */ public function testTransformerSkipInternalClasses() { $this->transformer = new WeavingTransformer( $this->getKernelMock( array( 'cacheDir' => __DIR__, 'appDir' => dirname(__DIR__), 'includePaths' => array(), 'excludePaths' => array(__DIR__) ), $this->getMock('Go\Core\AspectContainer') ), $this->broker, $this->adviceMatcher ); $this->metadata->source = $this->loadTest('class'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('class')); $this->assertEquals($expected, $actual); } /** * Testcase for multiple classes (@see https://github.com/lisachenko/go-aop-php/issues/71) */ public function testMultipleClasses() { $this->metadata->source = $this->loadTest('multiple-classes'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('multiple-classes-woven')); $this->assertEquals($expected, $actual); } /** * Normalizes string context * * @param string $value * @return string */ protected function normalizeWhitespaces($value) { return strtr( preg_replace('/^\s+$/m', '', $value), array( "\r\n" => PHP_EOL, "\n" => PHP_EOL, ) ); } /** * Returns a mock for kernel * * @param array $options Additional options for kernel * @param AspectContainer $container Container instance * @return \PHPUnit_Framework_MockObject_MockObject|\Go\Core\AspectKernel */ protected function getKernelMock($options, $container) { $mock = $this->getMockForAbstractClass( 'Go\Core\AspectKernel', array(), '', false, true, true, array('getOptions', 'getContainer') ); $mock->expects($this->any()) ->method('getOptions') ->will( $this->returnValue($options) ); $mock->expects($this->any()) ->method('getContainer') ->will( $this->returnValue($container) ); return $mock; } /** * Returns a mock for container * * @return \PHPUnit_Framework_MockObject_MockObject|AdviceMatcher */ protected function getAdviceMatcherMock() { $mock = $this->getMock('Go\Core\AdviceMatcher', array('getAdvicesForClass'), array(), '', false); $mock->expects($this->any()) ->method('getAdvicesForClass') ->will( $this->returnCallback(function (\TokenReflection\ReflectionClass $refClass) { $advices = array(); foreach ($refClass->getMethods() as $method) { $advices[AspectContainer::METHOD_PREFIX][$method->name] = true; } return $advices; }) ); return $mock; } private function loadTest($name) { return file_get_contents(__DIR__ . '/_files/' . $name . '.php'); } } tests/Go/Instrument/Transformer/_files/aspect.php000064400000000137144760114140016161 0ustar00__invoke($this); } protected static function protectedStaticMethod() { return self::$__joinPoints['static:protectedStaticMethod']->__invoke(get_called_class()); } public function publicMethod() { return self::$__joinPoints['method:publicMethod']->__invoke($this); } public function publicMethodDynamicArguments($a, &$b) { $argsList = func_get_args(); return self::$__joinPoints['method:publicMethodDynamicArguments']->__invoke($this, array($a, &$b) + $argsList); } public function publicMethodFixedArguments($a, $b, $c = null) { return self::$__joinPoints['method:publicMethodFixedArguments']->__invoke($this, array($a, $b, $c)); } public static function publicStaticMethod() { return self::$__joinPoints['static:publicStaticMethod']->__invoke(get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns1\TestClass', unserialize('a:1:{s:6:"method";a:6:{s:12:"publicMethod";b:1;s:15:"protectedMethod";b:1;s:18:"publicStaticMethod";b:1;s:21:"protectedStaticMethod";b:1;s:28:"publicMethodDynamicArguments";b:1;s:26:"publicMethodFixedArguments";b:1;}}')); tests/Go/Instrument/Transformer/_files/class.php000064400000000761144760114140016012 0ustar00__invoke($this); } protected static function protectedStaticMethod() { return self::$__joinPoints['static:protectedStaticMethod']->__invoke(get_called_class()); } public function publicMethod() { return self::$__joinPoints['method:publicMethod']->__invoke($this); } public static function publicStaticMethod() { return self::$__joinPoints['static:publicStaticMethod']->__invoke(get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns1\TestFinalClass', unserialize('a:1:{s:6:"method";a:4:{s:12:"publicMethod";b:1;s:15:"protectedMethod";b:1;s:18:"publicStaticMethod";b:1;s:21:"protectedStaticMethod";b:1;}}')); tests/Go/Instrument/Transformer/_files/final-class.php000064400000000375144760114140017102 0ustar00__invoke(get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns3\TestClass1', unserialize('a:1:{s:6:"method";a:1:{s:4:"test";b:1;}}')); TestClass1::test(); class TestClass11__AopProxied { public static function test() {} } class TestClass11 extends TestClass11__AopProxied implements \Go\Aop\Proxy { /** *Property was created automatically, do not change it manually */ private static $__joinPoints = array(); public static function test() { return self::$__joinPoints['static:test']->__invoke(get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns3\TestClass11', unserialize('a:1:{s:6:"method";a:1:{s:4:"test";b:1;}}')); TestClass11::test(); class TestClass2__AopProxied { public static function test() {} } class TestClass2 extends TestClass2__AopProxied implements \Go\Aop\Proxy { /** *Property was created automatically, do not change it manually */ private static $__joinPoints = array(); public static function test() { return self::$__joinPoints['static:test']->__invoke(get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns3\TestClass2', unserialize('a:1:{s:6:"method";a:1:{s:4:"test";b:1;}}')); TestClass2::test(); tests/Go/Instrument/Transformer/_files/multiple-classes.php000064400000000415144760114140020167 0ustar00method('filename')); include_once($class->method('filename')); require($class->method('filename')); require_once($class->method('filename')); $y = $z ? include $x : $v; include $z ? $x : $y; $app->bindInstallPaths(require __DIR__.'/paths.php'); tests/Go/Instrument/Transformer/_files/yii_style_output.php000064400000001415144760114140020334 0ustar00method('filename')), __DIR__); include_once \Go\Instrument\Transformer\FilterInjectorTransformer::rewrite(($class->method('filename')), __DIR__); require \Go\Instrument\Transformer\FilterInjectorTransformer::rewrite(($class->method('filename')), __DIR__); require_once \Go\Instrument\Transformer\FilterInjectorTransformer::rewrite(($class->method('filename')), __DIR__); $y = $z ? include \Go\Instrument\Transformer\FilterInjectorTransformer::rewrite( $x , __DIR__): $v; include \Go\Instrument\Transformer\FilterInjectorTransformer::rewrite( $z ? $x : $y, __DIR__); $app->bindInstallPaths(require \Go\Instrument\Transformer\FilterInjectorTransformer::rewrite( __DIR__.'/paths.php', __DIR__)); tests/Go/Tests/First.php000064400000005352144760114140011164 0ustar00 properties private function privateMethod() { return $this->private; } protected function protectedMethod() { return $this->protected; } public function publicMethod() { return $this->public; } // Static methods that access self:: properties private static function staticSelfPrivate() { return self::$staticPrivate; } protected static function staticSelfProtected() { return self::$staticProtected; } public static function staticSelfPublic() { return self::$staticPublic; } public static function staticSelfPublicAccessPrivate() { return self::$staticPrivate; } // Static methods that access static:: properties with LSB protected static function staticLsbProtected() { return get_called_class(); } public static function staticLsbPublic() { return get_called_class(); } // Pass by reference public function passByReference(&$valueByReference) { $valueByReference = null; return $valueByReference; } // Pass by reference public static function staticPassByReference(&$valueByReference) { $valueByReference = null; return $valueByReference; } // Recursion test public function recursion($value, $level = 0) { if ($level > 0) { $value += $this->recursion($value, $level-1); } return $value; } // Recursion test public static function staticLsbRecursion($value, $level = 0) { if ($level > 0) { $value += static::staticLsbRecursion($value, $level-1); } return $value; } /** * Method for checking invocation with any number of arguments * * NB: Real proxy use the method definition to prepare invocation proxy, so variable number of arguments * will not work at all! * * @return string */ public function variableArgsTest() { return join('', func_get_args()); } /** * Method for checking invocation with any number of arguments * * NB: Real proxy use the method definition to prepare invocation proxy, so variable number of arguments * will not work at all! * * @return string */ public static function staticVariableArgsTest() { return join('', func_get_args()); } } tests/bootstrap.php000064400000001055144760114140010437 0ustar00 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ ini_set('display_errors', 1); if (defined("AUTOLOAD_PATH")) { if (is_file(__DIR__ . '/../' .AUTOLOAD_PATH)) { $loader = include_once __DIR__ . '/../' . AUTOLOAD_PATH; $loader->add('Go', __DIR__); } else { throw new InvalidArgumentException("Cannot load custom autoload file located at ".AUTOLOAD_PATH); } }