.gitignore000064400000000220144760116330006533 0ustar00# Exclude vendor files vendor/ # Exclude ide metadatas nbproject/ # Lock file composer.lock # PhpUnit working configuration file phpunit.xml .gush.yml000064400000000451144760116330006320 0ustar00adapter: github issue_tracker: github table-pr: bug_fix: ['Bug Fix?', no] new_feature: ['New Feature?', no] bc_breaks: ['BC Breaks?', no] deprecations: ['Deprecations?', no] tests_pass: ['Tests Pass?', no] fixed_tickets: ['Fixed Tickets', ''] license: ['License', MIT] .travis.before.sh000075500000000030144760116330007730 0ustar00set -x composer install .travis.yml000064400000000253144760116330006662 0ustar00language: php php: - 5.6 - 5.5 - 5.4 - 7.0 - hhvm-nightly install: - ./.travis.before.sh matrix: allow_failures: - php: hhvm-nightly - php: 7.0 CHANGELOG.md000064400000005123144760116330006363 0ustar00Changelog ====== 0.5.0 (May 24, 2014) * Proxies are now stored in the separate files to allow more transparent debugging * Cache warmer command added * Extended pointcut syntax for or-ed methods: ClassName->method1|method2(*) * Access to the annotations for method from MethodInvocation * Support for read-only file systems (phar, GAE, etc) * Direct access to advisors (no more serialize/unserialize) * New @within pointcut to match classes by annotation class * Nice demo GUI * Deprecate the usage of submodules for framework * Inheritance support during class-loading and weaving * List of small fixes and imrovements 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 libraryLICENSE000064400000002102144760116330005551 0ustar00Copyright (c) 2014 Lisachenko 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. Procfile000064400000000050144760116330006232 0ustar00web: vendor/bin/heroku-php-nginx demos/ README.md000064400000020343144760116330006032 0ustar00Go! Aspect-Oriented Framework for PHP ----------------- Go! AOP is a modern aspect-oriented framework in plain PHP with rich features for the new level of software development. Framework allows to solve a cross-cutting issues in the traditional object-oriented PHP code by providing a highly efficient and transparent hook system for your exisiting code. [![Build Status](https://secure.travis-ci.org/goaop/framework.png?branch=master)](https://travis-ci.org/goaop/framework) [![GitHub release](https://img.shields.io/github/release/goaop/framework.svg)](https://github.com/goaop/framework/releases/latest) [![Total Downloads](https://img.shields.io/packagist/dt/goaop/framework.svg)](https://packagist.org/packages/goaop/framework) [![Daily Downloads](https://img.shields.io/packagist/dd/goaop/framework.svg)](https://packagist.org/packages/goaop/framework) [![SensioLabs Insight](https://img.shields.io/sensiolabs/i/5998393e-29ea-48f8-8e7e-b19e86daa2db.svg)](https://insight.sensiolabs.com/projects/5998393e-29ea-48f8-8e7e-b19e86daa2db) [![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.4-8892BF.svg)](https://php.net/) [![License](https://img.shields.io/packagist/l/goaop/framework.svg)](https://packagist.org/packages/goaop/framework) Features ------------ * Provides dynamic hook system for PHP without changes in the original source code. * Doesn't require any PECL-extentions (php-aop, runkit, uopz) and DI-containers to work. * Object-oriented design of aspects, joinpoints and pointcuts. * Intercepting an execution of any public or protected method in a classes. * Intercepting an execution of static methods and methods in final classes. * Intercepting an execution of methods in the traits. * Intercepting an access to the public/protected properties for objects. * Hooks for static class initialization (after class is loaded into PHP memory). * Hooks for object initialization (intercepting `new` keywords). * Intercepting an invocation of system PHP functions. * Ability to change the return value of any methods/functions via `Around` type of advice. * Rich pointcut grammar syntax for defining pointcuts in the source code. * Native debugging for AOP with XDebug. The code with weaved aspects is fully readable and native. You can put a breakpoint in the original class or in the aspect and it will work (for debug mode)! * Can be integrated with any existing PHP frameworks and libraries. * Highly optimized for production use: support of opcode cachers, lazy loading of advices and aspects, joinpoints caching, no runtime checks of pointcuts, no runtime annotations parsing, no evals and `__call` methods, no slow proxies and `call_user_func_array()`. Fast bootstraping process (2-20ms) and advice invocation. 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. Installation ------------ Go! AOP framework can be installed with composer. Installation is quite easy: 1. Download the framework using composer 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 Ask composer to download the Go! AOP framework with its dependencies by running the command: ``` bash $ composer require goaop/framework ``` Composer will install the framework to your project's `vendor/goaop/framework` directory. ### Step 2: Create an application aspect kernel The aim of this framework 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 framework 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 framework 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. If you like this project, you could support it Flattr this project! [![Gratipay](https://img.shields.io/gratipay/lisachenko.svg)](https://gratipay.com/lisachenko/) Documentation ------------- Documentation about Go! library can be found at [official site][1]. [1]: http://go.aopphp.com app.yaml000064400000000165144760116330006217 0ustar00application: go-aop version: 1 runtime: php api_version: 1 handlers: - url: /.* script: demos/index.php bin/warmup000075500000002136144760116330006564 0ustar00#!/usr/bin/env php * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ use Go\Console\Command\WarmupCommand; use Go\Core\AspectKernel; use Symfony\Component\Console\Application; if (is_dir($vendor = __DIR__ . '/../vendor')) { require $vendor . '/autoload.php'; } elseif (is_dir($vendor = __DIR__ . '/../../..')) { require $vendor . '/autoload.php'; } else { die( 'You must set up the project dependencies.' . PHP_EOL . 'To do that, run the following commands:' . PHP_EOL . PHP_EOL . '$ curl -s http://getcomposer.org/installer | php' . PHP_EOL . '$ php composer.phar install' . PHP_EOL ); } if (!class_exists('Symfony\Component\Console\Application')) { die( 'You must install the symfony/console package in order ' . 'to use the command-line tool.' . PHP_EOL ); } $app = new Application('Go! AOP', AspectKernel::VERSION); $app->add(new WarmupCommand()); $app->run(); composer.json000064400000001621144760116330007273 0ustar00{ "name": "goaop/framework", "description": "Framework for aspect-oriented programming in PHP.", "type": "library", "keywords": ["library", "aop", "php", "aspect"], "homepage": "http://go.aopphp.com/", "license": "MIT", "require": { "php": ">=5.4.0", "jakubledl/dissect": "~1.0", "doctrine/annotations": "~1.0", "andrewsville/php-token-reflection": "~1.4" }, "require-dev": { "symfony/console": "~2.1" }, "suggest": { "symfony/console": "Enables the usage of the command-line tool." }, "bin": ["bin/warmup"], "authors": [ { "name": "Lisachenko Alexander", "homepage": "https://github.com/lisachenko" } ], "autoload": { "psr-4": {"Go\\": "src/"} }, "extra": { "branch-alias": { "dev-master": "1.0-dev" } } } composer.lock000064400000024053144760116330007256 0ustar00{ "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], "hash": "92de328629c36bae8eea13236d818089", "content-hash": "a14241ab23645b34f0abaf16733d6f94", "packages": [ { "name": "andrewsville/php-token-reflection", "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/Andrewsville/PHP-Token-Reflection.git", "reference": "e6d0ac2baf66cdf154be34c3d2a2aa1bd4b426ee" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Andrewsville/PHP-Token-Reflection/zipball/e6d0ac2baf66cdf154be34c3d2a2aa1bd4b426ee", "reference": "e6d0ac2baf66cdf154be34c3d2a2aa1bd4b426ee", "shasum": "" }, "require": { "ext-tokenizer": "*", "php": ">=5.3.0" }, "type": "library", "autoload": { "psr-0": { "TokenReflection": "./" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3" ], "authors": [ { "name": "Ondřej Nešpor", "homepage": "https://github.com/andrewsville" }, { "name": "Jaroslav Hanslík", "homepage": "https://github.com/kukulich" } ], "description": "Library emulating the PHP internal reflection using just the tokenized source code.", "homepage": "http://andrewsville.github.com/PHP-Token-Reflection/", "keywords": [ "library", "reflection", "tokenizer" ], "time": "2014-08-06 16:37:08" }, { "name": "doctrine/annotations", "version": "v1.2.7", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", "shasum": "" }, "require": { "doctrine/lexer": "1.*", "php": ">=5.3.2" }, "require-dev": { "doctrine/cache": "1.*", "phpunit/phpunit": "4.*" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.3.x-dev" } }, "autoload": { "psr-0": { "Doctrine\\Common\\Annotations\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "description": "Docblock Annotations Parser", "homepage": "http://www.doctrine-project.org", "keywords": [ "annotations", "docblock", "parser" ], "time": "2015-08-31 12:32:49" }, { "name": "doctrine/lexer", "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", "shasum": "" }, "require": { "php": ">=5.3.2" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } }, "autoload": { "psr-0": { "Doctrine\\Common\\Lexer\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", "homepage": "http://www.doctrine-project.org", "keywords": [ "lexer", "parser" ], "time": "2014-09-09 13:34:57" }, { "name": "jakubledl/dissect", "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/jakubledl/dissect.git", "reference": "fd779d60d058fc4d5bbe1e5a16a8a90421bf91d2" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/jakubledl/dissect/zipball/d3a391de31e45a247e95cef6cf58a91c05af67c4", "reference": "fd779d60d058fc4d5bbe1e5a16a8a90421bf91d2", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { "symfony/console": "~2.1" }, "suggest": { "symfony/console": "for the command-line tool" }, "bin": [ "bin/dissect.php", "bin/dissect" ], "type": "library", "autoload": { "psr-0": { "Dissect": [ "src/" ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "unlicense" ], "authors": [ { "name": "Jakub Lédl", "email": "jakubledl@gmail.com" } ], "description": "Lexing and parsing in pure PHP", "homepage": "https://github.com/jakubledl/dissect", "keywords": [ "ast", "lexing", "parser", "parsing" ], "time": "2013-01-29 21:29:14" } ], "packages-dev": [ { "name": "symfony/console", "version": "v2.7.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", "reference": "06cb17c013a82f94a3d840682b49425cd00a2161" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161", "reference": "06cb17c013a82f94a3d840682b49425cd00a2161", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { "psr/log": "~1.0", "symfony/event-dispatcher": "~2.1", "symfony/phpunit-bridge": "~2.7", "symfony/process": "~2.1" }, "suggest": { "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { "dev-master": "2.7-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony Console Component", "homepage": "https://symfony.com", "time": "2015-09-25 08:32:23" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=5.4.0" }, "platform-dev": [] } demos/Demo/Annotation/Cacheable.php000064400000000721144760116330013216 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Annotation/Deprecated.php000064400000000601144760116330013424 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Annotation; use Doctrine\Common\Annotations\Annotation; /** * @Annotation * @Target("METHOD") */ class Deprecated extends Annotation { } demos/Demo/Annotation/Loggable.php000064400000000577144760116330013114 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Annotation; use Doctrine\Common\Annotations\Annotation; /** * @Annotation * @Target("METHOD") */ class Loggable extends Annotation { } demos/Demo/Aspect/AwesomeAspectKernel.php000064400000002263144760116330014400 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 DeclareErrorAspect()); $container->registerAspect(new CachingAspect()); $container->registerAspect(new LoggingAspect()); $container->registerAspect(new IntroductionAspect()); $container->registerAspect(new PropertyInterceptorAspect()); $container->registerAspect(new FunctionInterceptorAspect()); $container->registerAspect(new FluentInterfaceAspect()); $container->registerAspect(new HealthyLiveAspect()); $container->registerAspect(new DynamicMethodsAspect()); } } demos/Demo/Aspect/CachingAspect.php000064400000003306144760116330013172 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\Around; /** * Caching aspect */ class CachingAspect implements Aspect { /** * This advice intercepts an execution of cacheable methods * * Logic is pretty simple: we look for the value in the cache and if it's not present here * then invoke original method and store it's result in the cache. * * Real-life examples will use APC or Memcache to store value in the cache * * @param MethodInvocation $invocation Invocation * * @Around("@execution(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])) { // We can use ttl value from annotation, but Doctrine annotations doesn't work under GAE if (!isset($_SERVER['APPENGINE_RUNTIME'])) { echo "Ttl is: ", $invocation->getMethod()->getAnnotation('Demo\Annotation\Cacheable')->time, PHP_EOL; } $memoryCache[$key] = $invocation->proceed(); } echo "Take ", sprintf("%0.3f", (microtime(true) - $time) * 1e3), "ms to call method $key", PHP_EOL; return $memoryCache[$key]; } } demos/Demo/Aspect/DeclareErrorAspect.php000064400000002046144760116330014207 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Lang\Annotation\DeclareError; /** * This aspect can be very useful for development to generate an error when executing prohibited methods */ class DeclareErrorAspect implements Aspect { /** * Message to show when calling the method * * @var string * @DeclareError("@execution(Demo\Annotation\Deprecated)", level=16384) // E_USER_DEPRECATED */ protected $message = 'Method is deprecated and should not be called in debug mode'; /** * Prevent developers from using this method by always generating a warning * * @var string * @DeclareError("execution(public Demo\Example\ErrorDemo->notSoGoodMethod(*))", level=512) // E_USER_WARNING */ protected $badMethod = 'Method can generate division by zero! Do not use it!'; } demos/Demo/Aspect/DynamicMethodsAspect.php000064400000003631144760116330014547 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\Before; /** * Aspect that intercepts specific magic methods, declared with __call and __callStatic */ class DynamicMethodsAspect implements Aspect { /** * This advice intercepts an execution of __call methods * * Unlike traditional "execution" pointcut, "dynamic" is checking the name of method in * the runtime, allowing to write interceptors for __call more transparently. * * @param MethodInvocation $invocation Invocation * * @Before("dynamic(public Demo\Example\DynamicMethodsDemo->save*(*))") */ public function beforeMagicMethodExecution(MethodInvocation $invocation) { $obj = $invocation->getThis(); // we need to unpack args from invocation args list($methodName, $args) = $invocation->getArguments(); echo 'Calling Magic Interceptor for method: ', is_object($obj) ? get_class($obj) : $obj, $invocation->getMethod()->isStatic() ? '::' : '->', $methodName, '()', ' with arguments: ', json_encode($args), PHP_EOL; } /** * This advice intercepts an execution of methods via __callStatic * * @param MethodInvocation $invocation * @Before("dynamic(public Demo\Example\DynamicMethodsDemo::find*(*))") */ public function beforeMagicStaticMethodExecution(MethodInvocation $invocation) { // we need to unpack args from invocation args list($methodName) = $invocation->getArguments(); echo "Calling Magic Static Interceptor for method: ", $methodName, PHP_EOL; } } demos/Demo/Aspect/FluentInterface.php000064400000000475144760116330013560 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; /** * Fluent interface marker */ interface FluentInterface { } demos/Demo/Aspect/FluentInterfaceAspect.php000064400000002365144760116330014720 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\Around; /** * Fluent interface aspect provides an easy way to reuse "chain" interface for classes * * Basically, it uses around method to intercept all public setters in the class that implements * special marker interface FluentInterface. Then it checks the return value for setter, if it's null, * then advice replaces it with reference to the object "$this". * * @see http://go.aopphp.com/blog/2013/03/19/implementing-fluent-interface-pattern-in-php/ */ class FluentInterfaceAspect implements Aspect { /** * Fluent interface advice * * @Around("within(Demo\Aspect\FluentInterface+) && execution(public **->set*(*))") * * @param MethodInvocation $invocation * @return mixed|null|object */ protected function aroundMethodExecution(MethodInvocation $invocation) { $result = $invocation->proceed(); return $result!==null ? $result : $invocation->getThis(); } } demos/Demo/Aspect/FunctionInterceptorAspect.php000064400000003314144760116330015641 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\FunctionInvocation; use Go\Lang\Annotation\Around; /** * Function interceptor can intercept an access to the system functions */ class FunctionInterceptorAspect implements Aspect { /** * This advice intercepts an access to the array_*** function in Demo\Example\ namespace * * @param FunctionInvocation $invocation * * @Around("execution(Demo\Example\array_*(*)) && !execution(Demo\Example\array_multisort(*))") * * @return mixed */ public function aroundArrayFunctions(FunctionInvocation $invocation) { echo 'Calling Around Interceptor for ', $invocation, ' with arguments: ', json_encode($invocation->getArguments()), PHP_EOL; return $invocation->proceed(); } /** * This advice intercepts an access to the file_get_contents() function * * @param FunctionInvocation $invocation * * @Around("execution(Demo\Example\file_get_contents(*))") * * @return mixed */ public function aroundFileGetContents(FunctionInvocation $invocation) { echo 'Calling Around Interceptor for ', $invocation, ' with arguments: ', json_encode($invocation->getArguments()), PHP_EOL; // return $invocation->proceed(); // Do not call original file_get_contents() return 'Hello!'; // Override return value for function } } demos/Demo/Aspect/HealthyLiveAspect.php000064400000003403144760116330014052 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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\HumanDemo->eat(*))") */ protected function humanEat() {} /** * Washing hands before eating * * @param MethodInvocation $invocation Invocation * @Before("$this->humanEat") */ protected function washUpBeforeEat(MethodInvocation $invocation) { /** @var $person \Demo\Example\HumanDemo */ $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\HumanDemo */ $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\HumanDemo->sleep(*))") */ protected function cleanTeethBeforeSleep(MethodInvocation $invocation) { /** @var $person \Demo\Example\HumanDemo */ $person = $invocation->getThis(); $person->cleanTeeth(); } } demos/Demo/Aspect/Introduce/SerializableImpl.php000064400000001641144760116330015662 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aspect/IntroductionAspect.php000064400000001504144760116330014315 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Lang\Annotation\DeclareParents; /** * Introduction aspect can dynamically add new interfaces and traits to the class */ class IntroductionAspect implements Aspect { /** * Add a single interface and trait to the class. * * You can also give several interfaces/traits via [] * * @DeclareParents( * value="within(Demo\Example\IntroductionDemo)", * interface="Serializable", * defaultImpl="Demo\Aspect\Introduce\SerializableImpl" * ) * * @var null */ protected $introduction = null; } demos/Demo/Aspect/LoggingAspect.php000064400000002532144760116330013224 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\Before; /** * Logging aspect * * @see http://go.aopphp.com/blog/2013/07/21/implementing-logging-aspect-with-doctrine-annotations/ */ class LoggingAspect implements Aspect { /** * This advice intercepts an execution of loggable methods * * We use "Before" type of advice to log only class name, method name and arguments before * method execution. * You can choose your own logger, for example, monolog or log4php. * Also you can choose "After" or "Around" advice to access an return value from method. * * To inject logger into this aspect you can look at Warlock framework with DI+AOP * * @param MethodInvocation $invocation Invocation * * @Before("@execution(Demo\Annotation\Loggable)") */ public function beforeMethodExecution(MethodInvocation $invocation) { echo 'Calling Before Interceptor for ', $invocation, ' with arguments: ', json_encode($invocation->getArguments()), PHP_EOL; } } demos/Demo/Aspect/PropertyInterceptorAspect.php000064400000002221144760116330015674 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\FieldAccess; use Go\Lang\Annotation\Around; /** * Property interceptor can intercept an access to the public and protected properties * * Be aware, it's very tricky and will not work for indirect modification, such as array_pop($this->property); */ class PropertyInterceptorAspect implements Aspect { /** * Advice that controls an access to the properties * * @param FieldAccess $fieldAccess Joinpoint * * @Around("access(public|protected Demo\Example\PropertyDemo->*)") * @return mixed */ public function aroundFieldAccess(FieldAccess $fieldAccess) { $value = $fieldAccess->proceed(); echo "Calling Around Interceptor for ", $fieldAccess, ", value: ", json_encode($value), PHP_EOL; // $value = 666; You can change the return value for read/write operations in advice! return $value; } } demos/Demo/Example/CacheableDemo.php000064400000001424144760116330013305 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; use Demo\Annotation\Cacheable; /** * Example class to show how to use caching with AOP */ class CacheableDemo { /** * Returns a report and explicitly cache a result for future use * * In this example we use "Cacheable" annotation to explicit mark a method * * @param string $from This can be any value * @Cacheable(time=10) * * @return string */ public function getReport($from) { // long calculation for 100ms usleep(0.1*1e6); return $from; } } demos/Demo/Example/DynamicMethodsDemo.php000064400000001726144760116330014373 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; /** * Example class to show how to intercept magic methods * * @method void saveById(int $id) * @method void saveByName(string $name) * @method void load(int $id) * @method static void find(array $args) */ class DynamicMethodsDemo { /** * Magic invoker * * @param string $name Method name * @param array $args Method arguments */ public function __call($name, array $args) { echo "I'm method: {$name}", PHP_EOL; } /** * Magic static invoker * * @param string $name Method name * @param array $args Method arguments */ public static function __callStatic($name, array $args) { echo "I'm static method: {$name}", PHP_EOL; } } demos/Demo/Example/ErrorDemo.php000064400000001633144760116330012551 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; use Demo\Annotation\Deprecated as deprecated; // <== We import specific system annotation /** * In this class we use system functions that will be intercepted by aspect */ class ErrorDemo { /** * Some old method that is used by system in production, but shouldn't be used for the new code * * @deprecated */ public function oldMethod() { echo "Hello, I'm old method", PHP_EOL; } /** * Method that is very tricky and should generate a notice */ public function notSoGoodMethod() { $value = round(microtime(true)) % 3; // Sometimes this equal to 0 return rand(1, 10) / $value; } } demos/Demo/Example/FunctionDemo.php000064400000001460144760116330013243 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; /** * In this class we use system functions that will be intercepted by aspect */ class FunctionDemo { /** * Some array transformer * * @param array $data Incoming array * * @return array|mixed Outcoming array */ public function testArrayFunctions(array $data = array()) { return array_flip(array_unique(array_values($data))); } /** * Outputs a file content */ public function testFileContent() { echo '
', htmlspecialchars(file_get_contents(__FILE__)), '
'; } } demos/Demo/Example/HumanDemo.php000064400000001560144760116330012527 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; /** * Human class example */ class HumanDemo { /** * 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/IntroductionDemo.php000064400000002264144760116330014142 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; /** * Example class to show how to dynamically add new interfaces and traits to the class */ class IntroductionDemo { /** * Method that checks if the current instance implementing Serializable interface */ public function testSerializable() { if ($this instanceof \Serializable) { echo get_class($this), ' implements `Serializable` interface now!', PHP_EOL; $reflection = new \ReflectionObject($this); echo "List of interfaces:", PHP_EOL; foreach ($reflection->getInterfaceNames() as $interfaceName) { echo '-> ', $interfaceName, PHP_EOL; } echo "List of traits:", PHP_EOL; foreach ($reflection->getTraitNames() as $traitName) { echo '-> ', $traitName, PHP_EOL; } } else { echo get_class($this), ' does not implement `Serializable` interface', PHP_EOL; } } } demos/Demo/Example/LoggingDemo.php000064400000002317144760116330013046 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; use Demo\Annotation\Loggable; /** * Example class to show how to use logging with AOP */ class LoggingDemo { /** * Executes a task and logs all incoming arguments * * @Loggable * @param mixed $task Some specific argument */ public function execute($task) { $this->perform($task, 'first'); $this->perform($task, 'second'); } /** * Protected method can be also loggable * * @Loggable * * @param mixed $task Specific task * @param string $level */ protected function perform($task, $level) { // some logic here } /** * Everything is possible with AOP, so static methods can be intercepted too * * @Loggable * * @param string $task Some specific argument */ public static function runByName($task) { $instance = new static(); // Go! AOP requires LSB to work correctly $instance->execute($task); } } demos/Demo/Example/PropertyDemo.php000064400000001164144760116330013303 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; /** * Example class to show how to intercept an access to the properties */ class PropertyDemo { public $publicProperty = 123; protected $protectedProperty = 456; public function showProtected() { echo $this->protectedProperty, PHP_EOL; } public function setProtected($newValue) { $this->protectedProperty = $newValue; } } demos/Demo/Example/UserFluentDemo.php000064400000001757144760116330013563 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo\Example; use Demo\Aspect\FluentInterface; /** * Example class to show fluent interface in action */ class UserFluentDemo implements FluentInterface { protected $name; protected $surname; protected $password; public function setName($name) { echo "Set user name to ", $name, PHP_EOL; $this->name = $name; } public function setSurname($surname) { echo "Set user surname to ", $surname, PHP_EOL; $this->surname = $surname; } public function setPassword($password) { if (!$password) { throw new \InvalidArgumentException("Password shouldn't be empty"); } echo "Set user password to ", $password, PHP_EOL; $this->password = $password; } } demos/Demo/Highlighter.php000064400000001421144760116330011511 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Demo; /** * Highlighter utility class */ final class Highlighter { /** * Highlighter with built-in check for list of disabled function (Google AppEngine) * * @param string $file Name of the file */ public static function highlight($file) { $highlightFileFunc = new \ReflectionFunction('highlight_file'); if (!$highlightFileFunc->isDisabled()) { highlight_file($file); } else { echo '
' . htmlspecialchars(file_get_contents($file)) . '
'; } } } demos/autoload.php000064400000000711144760116330010200 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ // Composer autoloading if (file_exists(__DIR__ . '/../vendor/autoload.php')) { /** @var Composer\Autoload\ClassLoader $loader */ $loader = include __DIR__ . '/../vendor/autoload.php'; $loader->add('Demo', __DIR__); } demos/autoload_aspect.php000064400000001406144760116330011541 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ use Doctrine\Common\Annotations\AnnotationRegistry; use Demo\Aspect\AwesomeAspectKernel; use Go\Aop\Features; include __DIR__ .'/autoload.php'; // Initialize demo aspect container $defaultFeatures = AwesomeAspectKernel::getDefaultFeatures(); AwesomeAspectKernel::getInstance()->init(array( 'debug' => true, 'appDir' => __DIR__ . '/../demos', 'cacheDir' => __DIR__ . '/cache', 'features' => $defaultFeatures | Features::INTERCEPT_FUNCTIONS, )); AnnotationRegistry::registerFile(__DIR__ . '/Demo/Annotation/Cacheable.php'); demos/cache/.gitignore000064400000000002144760116330010703 0ustar00* demos/cache/.gitkeep000064400000000000144760116330010342 0ustar00demos/index.php000064400000026711144760116330007507 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ ?> Go! AOP Demo

Welcome

This demo shows your an examples of AOP usage.

Please, choose one of available examples from navigation menu.
You can also try to run this code with XDebug.

getReport(12345); // First call will take 0.1 second
        echo "Result is: ", $result, PHP_EOL;

        $result = $example->getReport(12346); // This call is cached and result should be '12345'
        echo "Result is: ", $result, PHP_EOL;
        break;

    case 'loggable':
        $aspectName = 'Demo\Aspect\LoggingAspect';

        $example = new LoggingDemo();
        $example->execute('LoggingTask'); // Logging for dynamic methods
        LoggingDemo::runByName('StaticTask'); // Logging for static methods
        break;

    case 'property-interceptor':
        $aspectName = 'Demo\Aspect\PropertyInterceptorAspect';

        $example = new PropertyDemo();
        echo $example->publicProperty; // Read public property
        $example->publicProperty = 987; // Write public property
        $example->showProtected();
        $example->setProtected(987);
        break;

    case 'dynamic-interceptor':
        $aspectName = 'Demo\Aspect\DynamicMethodsAspect';

        $example = new DynamicMethodsDemo();
        $example->saveById(123); // intercept magic dynamic method
        $example->load(456); // notice, that advice for this magic method is not called
        DynamicMethodsDemo::find(array('id' =>124)); //intercept magic static method
        break;

    case 'function-interceptor':
        $aspectName = 'Demo\Aspect\FunctionInterceptorAspect';

        $example = new FunctionDemo();
        $example->testArrayFunctions(array('test' => 1, 'code' => 2, 'more' => 1));
        $example->testFileContent();
        break;

    case 'fluent-interface':
        $aspectName = 'Demo\Aspect\FluentInterfaceAspect';

        $example = new UserFluentDemo(); // Original class doesn't provide fluent interface for us
        if ($example instanceof \Go\Aop\Proxy) { // This check is to prevent fatal errors when AOP is disabled
            $example
                ->setName('John')
                ->setSurname('Doe')
                ->setPassword('root');
        } else {
            echo "Fluent interface is not available without AOP", PHP_EOL;
        }
        break;

    case 'human-advices':
        $aspectName = 'Demo\Aspect\HealthyLiveAspect';

        $example = new HumanDemo();
        echo "Want to eat something, let's have a breakfast!", PHP_EOL;
        $example->eat();
        echo "I should work to earn some money", PHP_EOL;
        $example->work();
        echo "It was a nice day, go to bed", PHP_EOL;
        $example->sleep();
        break;

    case 'dynamic-traits':
        $aspectName = 'Demo\Aspect\IntroductionAspect';

        $example = new IntroductionDemo(); // Original class doesn't implement Serializable
        $example->testSerializable();
        break;

    case 'declare-errors':
        $aspectName = 'Demo\Aspect\DeclareErrorAspect';

        $example = new ErrorDemo();
        $example->oldMethod();
        $example->notSoGoodMethod();
        break;

    default:
}
?>
  
getFileName())); ?>
getFileName())); ?>
phpunit.xml.dist000064400000001711144760116330007724 0ustar00 ./tests/Go/ ./src/Go/ performance src/Aop/Advice.php000064400000000561144760116330007765 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop; /** * Tag interface for Advice. Implementations can be any type of advice, such as Interceptors. */ interface Advice { } src/Aop/AdviceAfter.php000064400000000565144760116330010753 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop; /** * Tag class for all after advices either for field access or method calling */ interface AdviceAfter extends Advice { } src/Aop/AdviceAround.php000064400000000567144760116330011144 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop; /** * Tag class for all around advices either for field access or method calling */ interface AdviceAround extends Advice { } src/Aop/AdviceBefore.php000064400000000567144760116330011116 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop; /** * Tag class for all before advices either for field access or method calling */ interface AdviceBefore extends Advice { } src/Aop/Advisor.php000064400000001244144760116330010200 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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(); } src/Aop/Aspect.php000064400000001103144760116330010002 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop; /** * Aspect interface marker * * 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 */ interface Aspect { } src/Aop/AspectException.php000064400000000575144760116330011675 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop; use RuntimeException; /** * Superclass for all AOP infrastructure exceptions */ class AspectException extends RuntimeException { } src/Aop/Features.php000064400000003163144760116330010351 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop; /** * Interface-enumeration of framework features to use in checking and configuration */ interface Features { /** * Enables interception of system function. * By default this feature is disabled, because this option is very expensive. */ const INTERCEPT_FUNCTIONS = 1; /** * Enables interception of "new" operator in the source code * By default this feature is disabled, because it's very tricky */ const INTERCEPT_INITIALIZATIONS = 2; /** * Enables interception of "include"/"require" operations in legacy code * By default this feature is disabled, because only composer should be used */ const INTERCEPT_INCLUDES = 4; /** * Enables usage of traits and introductions as well, available since PHP5.4 */ const USE_TRAIT = 8; /** * Allows to use closures with binding, available since PHP5.4 */ const USE_CLOSURE = 16; /** * Enables usage of splat '...' operator, available since PHP5.6 */ const USE_SPLAT_OPERATOR = 32; /** * Do not check the cache presence and assume that cache is already prepared * * This flag is usable for read-only file systems (GAE, phar, etc) */ const PREBUILT_CACHE = 64; /** * Allows to use 'static::class' in the source code of proxies, available since PHP5.5 */ const USE_STATIC_FOR_LSB = 128; } src/Aop/Framework/AbstractInvocation.php000064400000001501144760116330014317 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Invocation; /** * Abstract class for all invocations joinpoints */ 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/Aop/Framework/AbstractJoinpoint.php000064400000005027144760116330014166 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 */ 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 */ public static function sortAdvices(array $advices) { $sortedAdvices = $advices; uasort($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/Aop/Framework/AbstractMethodInvocation.php000064400000005063144760116330015467 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\MethodInvocation; use Go\Aop\Support\AnnotatedReflectionMethod; use ReflectionMethod; /** * Abstract method invocation implementation */ 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 AnnotatedReflectionMethod($this->className, $methodName); // Give an access to call protected method if ($method->isProtected()) { $method->setAccessible(true); } } /** * Gets the method being called. * * @return AnnotatedReflectionMethod the method being called. */ public function getMethod() { return $this->reflectionMethod; } /** * Returns the object that holds the current joinpoint's static * part. * * @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. * * @return object */ public function getStaticPart() { return $this->getMethod(); } /** * Returns friendly description of this joinpoint * * @return string */ final public function __toString() { return sprintf( "execution(%s%s%s())", is_object($this->instance) ? get_class($this->instance) : $this->instance, $this->reflectionMethod->isStatic() ? '::' : '->', $this->reflectionMethod->name ); } } src/Aop/Framework/AfterInterceptor.php000064400000002127144760116330014007 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAfter; use Go\Aop\Intercept\Joinpoint; /** * "After" interceptor */ class AfterInterceptor extends BaseInterceptor implements AdviceAfter { /** * After invoker * * @param Joinpoint $joinpoint the concrete joinpoint * * @return mixed the result of the call to {@link Joinpoint::proceed()} * @throws Exception */ final public function invoke(Joinpoint $joinpoint) { $result = null; try { $result = $joinpoint->proceed(); } catch (Exception $invocationException) { // this is need for finally emulation in PHP } $adviceMethod = $this->adviceMethod; $adviceMethod($joinpoint); if (isset($invocationException)) { throw $invocationException; } return $result; } } src/Aop/Framework/AfterThrowingInterceptor.php000064400000002024144760116330015525 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAfter; use Go\Aop\Intercept\Joinpoint; /** * "After Throwing" interceptor */ class AfterThrowingInterceptor extends BaseInterceptor implements AdviceAfter { /** * After throwing exception invoker * * @param Joinpoint $joinpoint the concrete joinpoint * * @return mixed the result of the call to {@link Joinpoint::proceed()} * @throws Exception */ final public function invoke(Joinpoint $joinpoint) { $result = null; try { $result = $joinpoint->proceed(); } catch (Exception $invocationException) { $adviceMethod = $this->adviceMethod; $adviceMethod($joinpoint); throw $invocationException; } return $result; } } src/Aop/Framework/AroundInterceptor.php000064400000001443144760116330014176 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Exception; use Go\Aop\AdviceAround; use Go\Aop\Intercept\Joinpoint; /** * "Around" interceptor */ class AroundInterceptor extends BaseInterceptor implements AdviceAround { /** * Around invoker * * @param Joinpoint $joinpoint the concrete joinpoint * * @return mixed the result of the call to {@link Joinpoint::proceed()} * @throws Exception */ final public function invoke(Joinpoint $joinpoint) { $adviceMethod = $this->adviceMethod; return $adviceMethod($joinpoint); } } src/Aop/Framework/BaseAdvice.php000064400000015406144760116330012521 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Features; 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; * } */ 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) { static $useClosure; if (!isset($useClosure)) { $useClosure = AspectKernel::getInstance()->hasFeature(Features::USE_CLOSURE); } $refAdvice = new ReflectionFunction($adviceMethod); $refVariables = $refAdvice->getStaticVariables(); $scope = 'aspect'; if (isset($refVariables['scope'])) { $scope = $refVariables['scope']; $refAdvice = new ReflectionFunction($refVariables['adviceCallback']); } if ($useClosure) { $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])) { $aspect = AspectKernel::getInstance()->getContainer()->getAspect($aspectName); $advice = static::$localAdvicesCache["$aspectName->$methodName"]['aspect']; $advice = static::createScopeCallback($aspect, $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) { static $useClosure; if (!isset($useClosure)) { $useClosure = AspectKernel::getInstance()->hasFeature(Features::USE_CLOSURE); } if ($useClosure) { 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 Aspect $aspect * @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(Aspect $aspect, $adviceCallback, $scope) { switch ($scope) { case 'aspect': return $adviceCallback; case 'proxy': return function (Joinpoint $joinpoint) use ($aspect, $adviceCallback) { $instance = $joinpoint->getThis(); $isNotObject = $instance !== (object) $instance; $target = $isNotObject ? $instance : get_class($instance); $callback = $adviceCallback->bindTo($aspect, $target); return $callback($joinpoint); }; case 'target': return function (Joinpoint $joinpoint) use ($aspect, $adviceCallback) { $instance = $joinpoint->getThis(); $isNotObject = $instance !== (object) $instance; $target = $isNotObject ? $instance : get_parent_class($instance); $callback = $adviceCallback->bindTo($aspect, $target); return $callback($joinpoint); }; default: return function (Joinpoint $joinpoint) use ($aspect, $adviceCallback, $scope) { $callback = $adviceCallback->bindTo($aspect, $scope); return $callback($joinpoint); }; } } } src/Aop/Framework/BaseInterceptor.php000064400000004347144760116330013626 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Serializable; use Go\Aop\Intercept\Interceptor; /** * Base interceptor realization */ abstract class BaseInterceptor extends BaseAdvice implements Interceptor, Serializable { /** * Pointcut expression * * @var null|string */ protected $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 string $pointcut \Go\Aop\Pointcut expression or advice name */ public function __construct($adviceMethod, $order = 0, $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/Aop/Framework/BeforeInterceptor.php000064400000001451144760116330014147 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\AdviceBefore; use Go\Aop\Intercept\Joinpoint; /** * "Before" interceptor */ class BeforeInterceptor extends BaseInterceptor implements AdviceBefore { /** * Before invoker for joinpoint * * @param Joinpoint $joinpoint the concrete joinpoint * * @return mixed the result of the call to {@link Joinpoint::proceed()}, */ final public function invoke(Joinpoint $joinpoint) { $adviceMethod = $this->adviceMethod; $adviceMethod($joinpoint); return $joinpoint->proceed(); } } src/Aop/Framework/Block/ClosureDynamicProceedTrait.php000064400000004161144760116330017010 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework\Block; trait ClosureDynamicProceedTrait { /** * Closure to use * * @var \Closure */ protected $closureToCall = null; /** * Previous scope of invocation * * @var null */ protected $previousInstance = null; /** * Invokes original method and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor \Go\Aop\Intercept\Interceptor */ $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->reflectionMethod->class); $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/Aop/Framework/Block/ClosureSplatDynamicProceedTrait.php000064400000002503144760116330020012 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework\Block; trait ClosureSplatDynamicProceedTrait { use ClosureDynamicProceedTrait; /** * Invokes original method and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor \Go\Aop\Intercept\Interceptor */ $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->reflectionMethod->class); $this->previousInstance = $this->instance; } $closureToCall = $this->closureToCall; return $closureToCall(...$this->arguments); } } src/Aop/Framework/Block/ClosureStaticProceedTrait.php000064400000003324144760116330016653 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework\Block; trait ClosureStaticProceedTrait { use ClosureDynamicProceedTrait; /** * Invokes original method and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor \Go\Aop\Intercept\Interceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->invoke($this); } // Rebind the closure if scope (class name) was changed since last time if ($this->previousInstance !== $this->instance) { if ($this->closureToCall === null) { $this->closureToCall = $this->getStaticInvoker($this->className, $this->reflectionMethod->name); } $this->closureToCall = $this->closureToCall->bindTo(null, $this->instance); $this->previousInstance = $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/Aop/Framework/Block/ReflectionProceedTrait.php000064400000001706144760116330016163 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework\Block; trait ReflectionProceedTrait { /** * Invokes original method and return result from it * * @return mixed */ public function proceed() { if (isset($this->advices[$this->current])) { /** @var $currentInterceptor \Go\Aop\Intercept\Interceptor */ $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/Aop/Framework/Block/SimpleInvocationTrait.php000064400000002246144760116330016052 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework\Block; trait SimpleInvocationTrait { /** * Invokes current method invocation with all interceptors * * @param null|object|string $instance Invocation instance (class name for static methods) * @param array $arguments List of arguments for method invocation * * @return mixed Result of invocation */ 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/Aop/Framework/Block/VariadicInvocationTrait.php000064400000002221144760116330016334 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework\Block; trait VariadicInvocationTrait { /** * Invokes current method invocation with all interceptors * * @param null|object|string $instance Invocation instance (class name for static methods) * @param array ...$arguments Variable list of arguments * * @return mixed Result of invocation */ final public function __invoke($instance = null, ...$arguments) { 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/Aop/Framework/ClassFieldAccess.php000064400000011457144760116330013670 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\Interceptor; use ReflectionProperty; /** * Represents a field access joinpoint */ class ClassFieldAccess extends AbstractJoinpoint implements FieldAccess { /** * 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. * * @return ReflectionProperty the field being accessed. */ public function getField() { return $this->reflectionProperty; } /** * Gets the value that must be set to the field. * * @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 Interceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->invoke($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. * * @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. * * @return object */ final public function getStaticPart() { return $this->getField(); } /** * Returns a friendly description of current joinpoint * * @return string */ final public function __toString() { return sprintf( "%s(%s%s%s)", $this->accessType == self::READ ? 'get' : 'set', is_object($this->instance) ? get_class($this->instance) : $this->instance, $this->reflectionProperty->isStatic() ? '::' : '->', $this->reflectionProperty->name ); } } src/Aop/Framework/DeclareErrorInterceptor.php000064400000007207144760116330015323 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Joinpoint; use Go\Aop\Pointcut; use ReflectionMethod; use ReflectionProperty; /** * Interceptor to dynamically trigger an user notice/warning/error on method call * * This interceptor can be used as active replacement for the @deprecated tag or to notify about * probable issues with specific method. */ class DeclareErrorInterceptor extends BaseInterceptor { /** * 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, $reflectorName, $message, $level = E_USER_NOTICE) { $class = is_string($object) ? $object : get_class($object); $message = vsprintf('[AOP Declare Error]: %s has an error: "%s"', array( $class . '->' . $reflectorName, $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 Joinpoint $joinpoint the method invocation joinpoint * * @return mixed the result of the call to {@link Joinpoint::proceed()} */ public function invoke(Joinpoint $joinpoint) { /** @var ReflectionMethod|ReflectionProperty $reflection */ $reflection = $joinpoint->getStaticPart(); $reflectorName = 'unknown'; if ($reflection && method_exists($reflection, 'getName')) { $reflectorName = $reflection->getName(); } $this->adviceMethod->__invoke( $joinpoint->getThis(), $reflectorName, $this->message, $this->level ); return $joinpoint->proceed(); } } src/Aop/Framework/DynamicClosureMethodInvocation.php000064400000000767144760116330016653 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Framework\Block\ClosureDynamicProceedTrait; use Go\Aop\Framework\Block\SimpleInvocationTrait; class DynamicClosureMethodInvocation extends AbstractMethodInvocation { use ClosureDynamicProceedTrait; use SimpleInvocationTrait; } src/Aop/Framework/DynamicClosureSplatMethodInvocation.php000064400000001006144760116330017642 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Framework\Block\ClosureSplatDynamicProceedTrait; use Go\Aop\Framework\Block\SimpleInvocationTrait; class DynamicClosureSplatMethodInvocation extends AbstractMethodInvocation { use ClosureSplatDynamicProceedTrait; use SimpleInvocationTrait; } src/Aop/Framework/DynamicInvocationMatcherInterceptor.php000064400000004246144760116330017674 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Interceptor; use Go\Aop\Intercept\Invocation; use Go\Aop\Intercept\Joinpoint; use Go\Aop\Pointcut; /** * Dynamic invocation 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 */ class DynamicInvocationMatcherInterceptor extends BaseInterceptor { /** * Dynamic matcher constructor * * @param Pointcut $pointcut Instance of dynamic matcher * @param Interceptor $interceptor Instance of interceptor to invoke */ public function __construct(Pointcut $pointcut, Interceptor $interceptor) { $this->pointcut = $pointcut; $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 Joinpoint $joinpoint the method invocation joinpoint * * @return mixed the result of the call to {@see Joinpoint->proceed()} */ final public function invoke(Joinpoint $joinpoint) { if ($joinpoint instanceof Invocation) { if ($this->pointcut->matches($joinpoint->getStaticPart(), $joinpoint->getThis(), $joinpoint->getArguments())) { return $this->adviceMethod->invoke($joinpoint); } } return $joinpoint->proceed(); } } src/Aop/Framework/DynamicReflectionMethodInvocation.php000064400000000762144760116330017324 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Framework\Block\ReflectionProceedTrait; use Go\Aop\Framework\Block\SimpleInvocationTrait; class DynamicReflectionMethodInvocation extends AbstractMethodInvocation { use ReflectionProceedTrait; use SimpleInvocationTrait; } src/Aop/Framework/MethodInvocationComposer.php000064400000003740144760116330015513 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; /** * Composer for method invocations * * This technique allows to build a class for maximum performance of method invocation */ class MethodInvocationComposer { const CLOSURE_DYNAMIC_TRAIT = '\Go\Aop\Framework\Block\ClosureDynamicProceedTrait'; const CLOSURE_SPLAT_TRAIT = '\Go\Aop\Framework\Block\ClosureSplatDynamicProceedTrait'; const CLOSURE_STATIC_TRAIT = '\Go\Aop\Framework\Block\ClosureStaticProceedTrait'; const REFLECTION_TRAIT = '\Go\Aop\Framework\Block\ReflectionProceedTrait'; const VARIADIC_INVOCATION = '\Go\Aop\Framework\Block\VariadicInvocationTrait'; const SIMPLE_INVOCATION = '\Go\Aop\Framework\Block\SimpleInvocationTrait'; /** * Composes a class with specific features and returns its name * * @param bool $isStatic Static or dynamic method * @param bool $useClosureBinding Enables usage of closures instead of reflection * @param bool $useSplatOperator Enables usage of optimized invocation with splat operator * @param bool $useVariadics Enables usage of optimized invocation with variadic args * * @return string Name of composed class */ public static function compose($isStatic, $useClosureBinding, $useSplatOperator, $useVariadics) { $className = __NAMESPACE__ . '\\'; $className .= $isStatic ? 'Static' : 'Dynamic'; if ($useClosureBinding) { $className .= 'Closure'; if ($useSplatOperator && !$isStatic) { $className .= 'Splat'; } } else { $className .= 'Reflection'; } if ($useVariadics) { $className .= 'Variadic'; } $className .= 'MethodInvocation'; return $className; } } src/Aop/Framework/OrderedAdvice.php000064400000000766144760116330013236 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Advice; /** * Ordered advice can have a custom order to implement sorting */ interface OrderedAdvice extends Advice { /** * Returns the advice order * * @return int */ public function getAdviceOrder(); } src/Aop/Framework/ReflectionConstructorInvocation.php000064400000007466144760116330017134 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\ConstructorInvocation; use Go\Aop\Intercept\Interceptor; use ReflectionClass; use ReflectionMethod; /** * Reflection constructor invocation implementation */ class ReflectionConstructorInvocation extends AbstractInvocation implements ConstructorInvocation { /** * Reflection class * * @var ReflectionClass */ protected $class = null; /** * Instance of created class, can be used for Around or After types of advices * * @var object|null */ protected $instance = null; /** * Instance of reflection constructor for class * * @var null|ReflectionMethod */ private $constructor = null; /** * Number of constructor arguments * * @var int */ private $constructorArguments = 0; /** * Constructor for constructor invocation :) * * @param string $className Class name * @param $advices array List of advices for this invocation */ public function __construct($className, $type, array $advices) { $this->class = new ReflectionClass($className); $this->constructor = $constructor = $this->class->getConstructor(); // Give an access to call protected constructor if ($constructor && $constructor->isProtected()) { $constructor->setAccessible(true); } if ($constructor) { $this->constructorArguments = $constructor->getNumberOfParameters(); } 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 Interceptor */ $currentInterceptor = $this->advices[$this->current]; $this->current++; return $currentInterceptor->invoke($this); } if (!$this->constructorArguments) { $this->instance = $this->class->newInstance(); } else { $this->instance = $this->class->newInstanceArgs($this->arguments); } return $this->instance; } /** * Gets the constructor being called. * * @return ReflectionMethod|null the constructor being called or null if it is absent. */ public function getConstructor() { return $this->constructor; } /** * Returns the object that holds the current joinpoint's static * part. * * @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. * * @return object */ public function getStaticPart() { return $this->getConstructor(); } /** * Invokes current constructor invocation with all interceptors * * @return mixed */ final public function __invoke(array $arguments = array()) { $this->current = 0; $this->arguments = $arguments; return $this->proceed(); } /** * Returns a friendly description of current joinpoint * * @return string */ final public function __toString() { return sprintf( "initialization(%s)", $this->class->name ); } } src/Aop/Framework/ReflectionFunctionInvocation.php000064400000006022144760116330016357 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\FunctionInvocation; use Go\Aop\Intercept\Interceptor; use ReflectionFunction; /** * Function invocation implementation */ 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 Interceptor */ $currentInterceptor = $this->advices[$this->current++]; return $currentInterceptor->invoke($this); } return $this->reflectionFunction->invokeArgs($this->arguments); } /** * Gets the function being called. * * @return ReflectionFunction the method being called. */ public function getFunction() { return $this->reflectionFunction; } /** * Returns the object that holds the current joinpoint's static * part. * * @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. * * @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; } /** * Returns a friendly description of current joinpoint * * @return string */ final public function __toString() { return sprintf( "execution(%s())", $this->reflectionFunction->getName() ); } } src/Aop/Framework/StaticClosureMethodInvocation.php000064400000000764144760116330016513 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Framework\Block\ClosureStaticProceedTrait; use Go\Aop\Framework\Block\SimpleInvocationTrait; class StaticClosureMethodInvocation extends AbstractMethodInvocation { use ClosureStaticProceedTrait; use SimpleInvocationTrait; } src/Aop/Framework/StaticInitializationJoinpoint.php000064400000005046144760116330016563 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Core\AspectContainer; use ReflectionClass; /** * Static initialization joinpoint is invoked after class is loaded into memory */ class StaticInitializationJoinpoint extends AbstractJoinpoint { /** * @var ReflectionClass */ protected $reflectionClass; /** * Constructor for static initialization joinpoint * * @param string $className Name of the class * @param string $type Type of joinpoint * @param $advices array List of advices for this invocation * * @internal param ReflectionClass $reflectionClass Reflection of class */ public function __construct($className, $type, array $advices) { if (strpos($className, AspectContainer::AOP_PROXIED_SUFFIX)) { $originalClass = substr($className, 0, -strlen(AspectContainer::AOP_PROXIED_SUFFIX)); } else { $originalClass = $className; } $this->reflectionClass = new \ReflectionClass($originalClass); parent::__construct($advices); } /** * Proceeds to the next interceptor in the chain. * * @return mixed see the children interfaces' proceed definition. */ public function proceed() { if (isset($this->advices[$this->current])) { $currentInterceptor = $this->advices[$this->current++]; $currentInterceptor->invoke($this); } } /** * Invokes current joinpoint with all interceptors * * @return mixed */ final public function __invoke() { $this->current = 0; return $this->proceed(); } /** * Returns the object that holds the current joinpoint's static * part. * * @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. * * @return ReflectionClass */ public function getStaticPart() { return $this->reflectionClass; } /** * Returns a friendly description of current joinpoint * * @return string */ final public function __toString() { return sprintf( "staticinitialization(%s)", $this->reflectionClass->getName() ); } } src/Aop/Framework/TraitIntroductionInfo.php000064400000003115144760116330015026 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\IntroductionInfo; /** * Advice for introduction that holds list of traits and interfaces for the concrete class */ class TraitIntroductionInfo implements IntroductionInfo { /** * List of interfaces to introduce * * @var array */ private $introducedInterfaces; /** * List of traits to include * * @var array */ private $introducedTraits; /** * Create a DefaultIntroductionAdvisor for the given advice. * * @param string|string[] $introducedInterfaces List of introduced interfaces * @param string|string[] $introducedTraits List of introduced traits */ public function __construct($introducedInterfaces, $introducedTraits) { $this->introducedInterfaces = (array) $introducedInterfaces; $this->introducedTraits = (array) $introducedTraits; } /** * Return the additional interfaces introduced by this Advisor or Advice. * * @return array|string[] introduced interfaces */ public function getInterfaces() { return $this->introducedInterfaces; } /** * Return the list of traits with realization of introduced interfaces * * @return array|string[] trait implementations */ public function getTraits() { return $this->introducedTraits; } } src/Aop/Intercept/ConstructorInvocation.php000064400000001343144760116330015105 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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. */ interface ConstructorInvocation extends Invocation { /** * Gets the constructor being called. * * @return ReflectionMethod|null the constructor being called or null if it is absent. */ public function getConstructor(); } src/Aop/Intercept/FieldAccess.php000064400000001770144760116330012677 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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. */ interface FieldAccess extends Joinpoint { /** * The read access type */ const READ = 0; /** * The write access type */ const WRITE = 1; /** * Gets the field being accessed. * * @return ReflectionProperty the field being accessed. */ public function getField(); /** * Gets the value that must be set to the field. * * @return mixed */ public function getValueToSet(); /** * Returns the access type. * * @return integer */ public function getAccessType(); } src/Aop/Intercept/FunctionInvocation.php000064400000001500144760116330014340 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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. */ interface FunctionInvocation extends Invocation { /** * Gets the function being called. * * @return ReflectionFunction the function being called. */ public function getFunction(); /** * Invokes current function invocation with all interceptors * * @return mixed */ public function __invoke(); } src/Aop/Intercept/Interceptor.php000064400000002674144760116330013034 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 Interceptor {
 *
 *   public function invoke(Joinpoint $i) {
 *       $this->debug($i->getStaticPart(), $i->getThis(), $i->getArgs());
 *       return $i->proceed();
 *   }
 *
 *   protected function debug($accessibleObject, $object, $value) {
 *       ...
 *   }
 * }
 * 
* * @see Joinpoint */ interface Interceptor extends Advice { /** * Implement this method to perform extra treatments before and * after the invocation of joinpoint. * * @param Joinpoint $joinpoint current joinpoint * * @return mixed the result of the call */ public function invoke(Joinpoint $joinpoint); } src/Aop/Intercept/Invocation.php000064400000001263144760116330012640 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 */ 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/Aop/Intercept/Joinpoint.php000064400000003217144760116330012501 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 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. * * @return mixed see the children interfaces' proceed definition. */ public function proceed(); /** * Returns the object that holds the current joinpoint's static * part. * * @return object|null the object (can be null if the accessible object is * static). */ public function getThis(); /** * Returns the static part of this joinpoint. * * @return object */ public function getStaticPart(); /** * Returns a friendly description of current joinpoint * * @return string */ public function __toString(); } src/Aop/Intercept/MethodInvocation.php000064400000001562144760116330014003 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Intercept; use Go\Aop\Support\AnnotatedReflectionMethod; 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. */ interface MethodInvocation extends Invocation { /** * Gets the method being called. * * @return ReflectionMethod|AnnotatedReflectionMethod the method being called. */ public function getMethod(); /** * Invokes current method invocation with all interceptors * * @return mixed */ public function __invoke(); } src/Aop/IntroductionAdvisor.php000064400000002401144760116330012576 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/IntroductionInfo.php000064400000001755144760116330012075 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/MethodMatcher.php000064400000003304144760116330011314 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 point filter is not dynamic (self::KIND_DYNAMIC), evaluation can be performed statically, * and the result will be the same for all invocations of this method, whatever their arguments. * * If an implementation returns true from its 2-arg matches() method and filter is self::KIND_DYNAMIC, * 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 { /** * 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); } src/Aop/PointFilter.php000064400000001753144760116330011035 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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_INIT = 32; CONST KIND_STATIC_INIT = 64; const KIND_ALL = 127; const KIND_DYNAMIC = 256; /** * 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/Aop/Pointcut.php000064400000001325144760116330010376 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 */ interface Pointcut extends PointFilter { /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter(); } src/Aop/Pointcut/AndPointcut.php000064400000005202144760116330012624 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\Support\AndPointFilter; /** * Signature method pointcut checks method signature (modifiers and name) to match it */ class AndPointcut implements Pointcut { use PointcutClassFilterTrait; /** * @var Pointcut */ protected $first; /** * @var Pointcut */ protected $second; /** * 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 * @param object|string|null $instance [Optional] Instance for dynamic matching * @param array $arguments [Optional] Extra arguments for dynamic matching * * @return bool */ public function matches($point, $instance = null, array $arguments = null) { return $this->isMatchesPointcut($point, $this->first, $instance, $arguments) && $this->isMatchesPointcut($point, $this->second, $instance, $arguments); } /** * 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 * @param object|string|null $instance [Optional] Instance for dynamic matching * @param array $arguments [Optional] Extra arguments for dynamic matching * * @return bool */ protected function isMatchesPointcut($point, Pointcut $pointcut, $instance = null, array $arguments = null) { $preFilter = method_exists($point, 'getDeclaringClass') ? $point->getDeclaringClass() : $point->getNamespaceName(); return $pointcut->matches($point, $instance, $arguments) && $pointcut->getClassFilter()->matches($preFilter); } } src/Aop/Pointcut/AnnotationPointcut.php000064400000006575144760116330014252 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Instrument\RawAnnotationReader; use TokenReflection\ReflectionClass as ParsedReflectionClass; use TokenReflection\ReflectionMethod as ParsedReflectionMethod; use TokenReflection\ReflectionProperty as ParsedReflectionProperty; /** * Annotation property pointcut checks property annotation */ class AnnotationPointcut implements Pointcut { use PointcutClassFilterTrait; /** * Annotation class to match * * @var string */ protected $annotationName = ''; /** * Annotation reader * * @var null|RawAnnotationReader */ protected $annotationReader = null; /** * Kind of current filter, can be KIND_CLASS, KIND_METHOD, KIND_PROPERTY, KIND_TRAIT * * @var int */ protected $filterKind = 0; /** * Specifies name of the expected class to receive * * @var string */ protected $expectedClass = ''; /** * Method to call for annotation reader * * @var string */ protected $annotationMethod = ''; /** * Static mappings of kind to expected class and method name * * @var array */ protected static $mappings = array( self::KIND_CLASS => array( 'TokenReflection\ReflectionClass', 'getClassAnnotation' ), self::KIND_TRAIT => array( 'TokenReflection\ReflectionClass', 'getClassAnnotation' ), self::KIND_METHOD => array( 'TokenReflection\ReflectionMethod', 'getMethodAnnotation' ), self::KIND_PROPERTY => array( 'TokenReflection\ReflectionProperty', 'getPropertyAnnotation' ) ); /** * Annotation matcher constructor * * @param integer $filterKind Kind of filter, e.g. KIND_CLASS * @param RawAnnotationReader $reader Reader of annotations * @param string $annotationName Annotation class name to match */ public function __construct($filterKind, RawAnnotationReader $reader, $annotationName) { if (!isset(self::$mappings[$filterKind])) { throw new \InvalidArgumentException("Unsupported filter kind {$filterKind}"); } $this->filterKind = $filterKind; $this->annotationName = $annotationName; $this->annotationReader = $reader; list($this->expectedClass, $this->annotationMethod) = self::$mappings[$filterKind]; } /** * @param ParsedReflectionClass|ParsedReflectionMethod|ParsedReflectionProperty $point * {@inheritdoc} */ public function matches($point) { $expectedClass = $this->expectedClass; if (!$point instanceof $expectedClass) { return false; } $aliases = $point->getNamespaceAliases(); $this->annotationReader->setImports($aliases); $annotation = $this->annotationReader->{$this->annotationMethod}($point, $this->annotationName); return (bool) $annotation; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->filterKind; } } src/Aop/Pointcut/CFlowBelowMethodPointcut.php000064400000005310144760116330015266 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\MethodMatcher; 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 implements MethodMatcher, Pointcut { use PointcutClassFilterTrait; /** * 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; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return PointFilter::KIND_METHOD | PointFilter::KIND_DYNAMIC; } } src/Aop/Pointcut/ClassMemberReference.php000064400000004271144760116330014415 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\ModifierMatcherFilter; /** * Data transfer object for storing a reference to the class member (property or method) */ class ClassMemberReference { /** * Filter for class names * * @var PointFilter */ private $classFilter; /** * Member visibility filter (public/protected/etc) * * @var ModifierMatcherFilter */ private $visibilityFilter; /** * Filter for access type (statically "::" or dynamically "->") * * @var ModifierMatcherFilter */ private $accessTypeFilter; /** * Member name pattern * * @var string */ private $memberNamePattern; /** * Default constructor * * @param PointFilter $classFilter * @param ModifierMatcherFilter $visibilityFilter Public/protected/etc * @param ModifierMatcherFilter $accessTypeFilter Static or dynamic * @param string $memberNamePattern Expression for the name */ public function __construct( PointFilter $classFilter, ModifierMatcherFilter $visibilityFilter, ModifierMatcherFilter $accessTypeFilter, $memberNamePattern) { $this->classFilter = $classFilter; $this->visibilityFilter = $visibilityFilter; $this->accessTypeFilter = $accessTypeFilter; $this->memberNamePattern = $memberNamePattern; } /** * @return PointFilter */ public function getClassFilter() { return $this->classFilter; } /** * @return ModifierMatcherFilter */ public function getVisibilityFilter() { return $this->visibilityFilter; } /** * @return ModifierMatcherFilter */ public function getAccessTypeFilter() { return $this->accessTypeFilter; } /** * @return string */ public function getMemberNamePattern() { return $this->memberNamePattern; } } src/Aop/Pointcut/FunctionPointcut.php000064400000004275144760116330013720 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Pointcut/MagicMethodPointcut.php000064400000005233144760116330014307 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\MethodMatcher; use Go\Aop\Pointcut; use Go\Aop\PointFilter; /** * Magic method pointcut is a dynamic checker that verifies calls for __call and __callStatic */ class MagicMethodPointcut implements MethodMatcher, Pointcut { use PointcutClassFilterTrait; /** * Method name to match, can contain wildcards *,? * * @var string */ protected $methodName = ''; /** * Regular expression for matching * * @var string */ protected $regexp; /** * Modifier filter for method * * @var PointFilter */ protected $modifierFilter; /** * Magic method matcher constructor * * NB: only public methods can be matched as __call and __callStatic are public * * @param string $methodName Name of the dynamic method to match or glob pattern * @param PointFilter $modifierFilter Method modifier filter (static or not) */ public function __construct($methodName, PointFilter $modifierFilter = null) { $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 * @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 for __call, __callStatic if (!$instance) { return ($method->name === '__call' || $method->name === '__callStatic'); } if (!$this->modifierFilter->matches($method)) { return false; } // for __call and __callStatic method name is the first argument on invocation list($methodName) = $arguments; return ($methodName === $this->methodName) || (bool) preg_match("/^(?:{$this->regexp})$/", $methodName); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return PointFilter::KIND_METHOD | PointFilter::KIND_DYNAMIC; } } src/Aop/Pointcut/NotPointcut.php000064400000003656144760116330012675 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 { use PointcutClassFilterTrait; /** * @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 * @param object|string|null $instance [Optional] Instance for dynamic matching * @param array $arguments [Optional] Extra arguments for dynamic matching * * @return bool */ public function matches($point, $instance = null, array $arguments = null) { $preFilter = method_exists($point, 'getDeclaringClass') ? $point->getDeclaringClass() : $point->getNamespaceName(); $isMatchesPre = $this->pointcut->getClassFilter()->matches($preFilter); if (!$isMatchesPre) { return true; } $isMatchesPoint = $this->pointcut->matches($point, $instance, $arguments); if (!$isMatchesPoint) { return true; } return false; } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->kind; } } src/Aop/Pointcut/OrPointcut.php000064400000002745144760116330012513 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 * @param object|string|null $instance [Optional] Instance for dynamic matching * @param array $arguments [Optional] Extra arguments for dynamic matching * * @return bool */ public function matches($point, $instance = null, array $arguments = null) { return $this->isMatchesPointcut($point, $this->first, $instance, $arguments) || $this->isMatchesPointcut($point, $this->second, $instance, $arguments); } } src/Aop/Pointcut/PointcutClassFilterTrait.php000064400000002127144760116330015344 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\TruePointFilter; /** * Convenient trait for pointcuts. * * The "classFilter" property can be set to customize ClassFilter behavior. */ trait PointcutClassFilterTrait { /** * 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/Aop/Pointcut/PointcutGrammar.php000064400000030667144760116330013525 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Dissect\Lexer\Token; use Dissect\Parser\Grammar; 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\Core\AspectContainer; use Go\Instrument\RawAnnotationReader; /** * Pointcut grammar defines general structure of pointcuts and rules of parsing */ class PointcutGrammar extends Grammar { /** * Constructs a pointcut grammar with AST * * @param AspectContainer $container Instance of the container * @param RawAnnotationReader $annotationReader */ public function __construct(AspectContainer $container = null, RawAnnotationReader $annotationReader = null) { $this('empty') ->is(/* empty */); $stringConverter = $this->getNodeToStringConverter(); $this('pointcutExpression') ->is('pointcutExpression', '||', 'conjugatedExpression') ->call(function ($first, $_0, $second) { return new OrPointcut($first, $second); }) ->is('conjugatedExpression'); $this('conjugatedExpression') ->is('conjugatedExpression', '&&', 'negatedExpression') ->call(function ($first, $_0, $second) { return new AndPointcut($first, $second); }) ->is('negatedExpression'); $this('negatedExpression') ->is('!', 'brakedExpression') ->call(function ($_0, $item) { return new NotPointcut($item); }) ->is('brakedExpression'); $this('brakedExpression') ->is('(', 'pointcutExpression', ')') ->call(function ($_0, $e, $_1) { return $e; }) ->is('singlePointcut'); $this('singlePointcut') ->is('accessPointcut') ->is('annotatedAccessPointcut') ->is('executionPointcut') ->is('annotatedExecutionPointcut') ->is('withinPointcut') ->is('annotatedWithinPointcut') ->is('initializationPointcut') ->is('staticInitializationPointcut') ->is('cflowbelowPointcut') ->is('dynamicExecutionPointcut') ->is('pointcutReference'); $this('accessPointcut') ->is('access', '(', 'propertyAccessReference', ')') ->call(function ($_0, $_1, $propertyReference) { return $propertyReference; }); $this('executionPointcut') ->is('execution', '(', 'methodExecutionReference', ')') ->call(function ($_0, $_1, $methodReference) { return $methodReference; }) ->is('execution', '(', 'functionExecutionReference', ')') ->call(function ($_0, $_1, $functionReference) { return $functionReference; }); $this('withinPointcut') ->is('within', '(', 'classFilter', ')') ->call(function ($_0, $_1, $classFilter) { $pointcut = new TruePointcut(PointFilter::KIND_ALL); $pointcut->setClassFilter($classFilter); return $pointcut; }); $this('annotatedAccessPointcut') ->is('annotation', 'access', '(', 'namespaceName', ')') ->call(function ($_0, $_1, $_2, $annotationClassName) use ($annotationReader) { $kindProperty = PointFilter::KIND_PROPERTY; return new AnnotationPointcut($kindProperty, $annotationReader, $annotationClassName); }); $this('annotatedExecutionPointcut') ->is('annotation', 'execution', '(', 'namespaceName', ')') ->call(function ($_0, $_1, $_2, $annotationClassName) use ($annotationReader) { $kindMethod = PointFilter::KIND_METHOD; return new AnnotationPointcut($kindMethod, $annotationReader, $annotationClassName); }); $this('annotatedWithinPointcut') ->is('annotation', 'within', '(', 'namespaceName', ')') ->call(function ($_0, $_1, $_2, $annotationClassName) use ($annotationReader) { $pointcut = new TruePointcut(PointFilter::KIND_ALL); $kindClass = PointFilter::KIND_CLASS; $classFilter = new AnnotationPointcut($kindClass, $annotationReader, $annotationClassName); $pointcut->setClassFilter($classFilter); return $pointcut; }); $this('initializationPointcut') ->is('initialization', '(', 'classFilter', ')') ->call(function ($_0, $_1, $classFilter){ $pointcut = new TruePointcut(PointFilter::KIND_INIT + PointFilter::KIND_CLASS); $pointcut->setClassFilter($classFilter); return $pointcut; }); $this('staticInitializationPointcut') ->is('staticinitialization', '(', 'classFilter', ')') ->call(function ($_0, $_1, $classFilter){ $pointcut = new TruePointcut(PointFilter::KIND_STATIC_INIT + PointFilter::KIND_CLASS); $pointcut->setClassFilter($classFilter); return $pointcut; }); $this('cflowbelowPointcut') ->is('cflowbelow', '(', 'executionPointcut', ')') ->call(function ($_0, $_1, $pointcut) { return new CFlowBelowMethodPointcut($pointcut); }); $this('dynamicExecutionPointcut') // ideally, this should be 'dynamic', 'methodExecutionReference' ->is('dynamic', '(', 'memberReference', '(', 'argumentList', ')', ')') ->call(function ($_0, $_1, ClassMemberReference $reference) { $memberFilter = $reference->getVisibilityFilter(); $memberFilter = $memberFilter->merge($reference->getAccessTypeFilter()); $pointcut = new MagicMethodPointcut( $reference->getMemberNamePattern(), $memberFilter); $pointcut->setClassFilter($reference->getClassFilter()); return $pointcut; }); $this('pointcutReference') ->is('namespaceName', '->', 'namePatternPart') ->call(function ($className, $_0, $name) use ($container) { return new PointcutReference($container, "{$className}->{$name}"); }); $this('propertyAccessReference') ->is('memberReference') ->call(function (ClassMemberReference $reference) { $memberFilter = $reference->getVisibilityFilter(); $memberFilter = $memberFilter->merge($reference->getAccessTypeFilter()); $pointcut = new SignaturePointcut( PointFilter::KIND_PROPERTY, $reference->getMemberNamePattern(), $memberFilter); $pointcut->setClassFilter($reference->getClassFilter()); return $pointcut; }); $this('methodExecutionReference') ->is('memberReference', '(', 'argumentList', ')') ->call(function (ClassMemberReference $reference) { $memberFilter = $reference->getVisibilityFilter(); $memberFilter = $memberFilter->merge($reference->getAccessTypeFilter()); $pointcut = new SignaturePointcut( PointFilter::KIND_METHOD, $reference->getMemberNamePattern(), $memberFilter); $pointcut->setClassFilter($reference->getClassFilter()); return $pointcut; }); $this('functionExecutionReference') ->is('namespacePattern', 'nsSeparator', 'namePatternPart', '(', 'argumentList', ')') ->call(function ($namespacePattern, $_0, $namePattern) { $nsFilter = new SimpleNamespaceFilter($namespacePattern); $pointcut = new FunctionPointcut($namePattern); $pointcut->setNamespaceFilter($nsFilter); return $pointcut; }); $this('memberReference') ->is('memberModifiers', 'classFilter', 'memberAccessType', 'namePatternPart') ->call(function ( ModifierMatcherFilter $memberModifiers, PointFilter $classFilter, ModifierMatcherFilter $memberAccessType, $namePattern ) { $reference = new ClassMemberReference( $classFilter, $memberModifiers, $memberAccessType, $namePattern); return $reference; }); $this('classFilter') ->is('namespacePattern') ->call(function ($pattern) { $truePointFilter = TruePointFilter::getInstance(); return $pattern === '**' ? $truePointFilter : new SignaturePointcut(PointFilter::KIND_CLASS, $pattern, $truePointFilter); }) ->is('namespacePattern', '+') ->call(function ($parentClassName) { return new InheritanceClassFilter($parentClassName); }); $this('argumentList') ->is('*'); $this('memberAccessType') ->is('::') ->call(function () { return new ModifierMatcherFilter(\ReflectionMethod::IS_STATIC); }) ->is('->') ->call(function () { $modifierMatcherFilter = new ModifierMatcherFilter(); $modifierMatcherFilter->notMatch(\ReflectionMethod::IS_STATIC); return $modifierMatcherFilter; }); $this('namespacePattern') ->is('**')->call($stringConverter) ->is('namePatternPart') ->is('namespacePattern', 'nsSeparator', 'namePatternPart')->call($stringConverter) ->is('namespacePattern', 'nsSeparator', '**')->call($stringConverter); $this('namePatternPart') ->is('*')->call($stringConverter) ->is('namePart')->call($stringConverter) ->is('namePatternPart', '*')->call($stringConverter) ->is('namePatternPart', 'namePart')->call($stringConverter) ->is('namePatternPart', '|', 'namePart')->call($stringConverter); $this('namespaceName') ->is('namePart')->call($stringConverter) ->is('namespaceName', 'nsSeparator', 'namePart')->call($stringConverter); $this('memberModifiers') ->is('memberModifier','|','memberModifiers') ->call(function ($modifier, $_0, ModifierMatcherFilter $matcher) { return $matcher->orMatch($modifier); }) ->is('memberModifier', 'memberModifiers') ->call(function ($modifier, ModifierMatcherFilter $matcher) { return $matcher->andMatch($modifier); }) ->is('memberModifier') ->call(function ($modifier) { return new ModifierMatcherFilter($modifier); }); $converter = $this->getModifierConverter(); $this('memberModifier') ->is('public')->call($converter) ->is('protected')->call($converter) ->is('private')->call($converter) ->is('final')->call($converter); $this->start('pointcutExpression'); } /** * 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 (Token $token) { $name = strtoupper($token->getValue()); return constant("ReflectionMethod::IS_{$name}"); }; } } src/Aop/Pointcut/PointcutLexer.php000064400000003360144760116330013204 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Dissect\Lexer\SimpleLexer; /** * This class defines a lexer for pointcut expression */ class PointcutLexer extends SimpleLexer { /** * Lexer token definitions */ public function __construct() { // General tokens $this->token('execution'); $this->token('dynamic'); $this->token('within'); $this->token('access'); $this->token('cflowbelow'); $this->token('initialization'); $this->token('staticinitialization'); // 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('||'); $this->token('annotation', '@'); // 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/Aop/Pointcut/PointcutParseTable.php000064400000023467144760116330014161 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ /** * This table was generated for production use, do not touch it */ return array('action'=>array(0=>array('!'=>4,'('=>6,'access'=>19,'annotation'=>20,'execution'=>21,'within'=>22,'initialization'=>23,'staticinitialization'=>24,'cflowbelow'=>25,'dynamic'=>26,'namePart'=>28,),1=>array('||'=>29,'$eof'=>0,),2=>array('&&'=>30,'$eof'=>-3,'||'=>-3,')'=>-3,),4=>array('('=>6,'access'=>19,'annotation'=>20,'execution'=>21,'within'=>22,'initialization'=>23,'staticinitialization'=>24,'cflowbelow'=>25,'dynamic'=>26,'namePart'=>28,),6=>array('!'=>4,'('=>6,'access'=>19,'annotation'=>20,'execution'=>21,'within'=>22,'initialization'=>23,'staticinitialization'=>24,'cflowbelow'=>25,'dynamic'=>26,'namePart'=>28,),19=>array('('=>33,),20=>array('access'=>34,'execution'=>35,'within'=>36,),21=>array('('=>37,),22=>array('('=>38,),23=>array('('=>39,),24=>array('('=>40,),25=>array('('=>41,),26=>array('('=>42,),27=>array('->'=>43,'nsSeparator'=>44,),29=>array('!'=>4,'('=>6,'access'=>19,'annotation'=>20,'execution'=>21,'within'=>22,'initialization'=>23,'staticinitialization'=>24,'cflowbelow'=>25,'dynamic'=>26,'namePart'=>28,),30=>array('!'=>4,'('=>6,'access'=>19,'annotation'=>20,'execution'=>21,'within'=>22,'initialization'=>23,'staticinitialization'=>24,'cflowbelow'=>25,'dynamic'=>26,'namePart'=>28,),32=>array(')'=>47,'||'=>29,),33=>array('public'=>52,'protected'=>53,'private'=>54,'final'=>55,),34=>array('('=>56,),35=>array('('=>57,),36=>array('('=>58,),37=>array('**'=>63,'*'=>65,'namePart'=>66,'public'=>52,'protected'=>53,'private'=>54,'final'=>55,),38=>array('**'=>63,'*'=>65,'namePart'=>66,),39=>array('**'=>63,'*'=>65,'namePart'=>66,),40=>array('**'=>63,'*'=>65,'namePart'=>66,),41=>array('execution'=>21,),42=>array('public'=>52,'protected'=>53,'private'=>54,'final'=>55,),43=>array('*'=>65,'namePart'=>66,),44=>array('namePart'=>74,),45=>array('&&'=>30,'$eof'=>-2,'||'=>-2,')'=>-2,),48=>array(')'=>75,),50=>array('**'=>63,'*'=>65,'namePart'=>66,),51=>array('|'=>77,'public'=>52,'protected'=>53,'private'=>54,'final'=>55,'**'=>-55,'*'=>-55,'namePart'=>-55,),56=>array('namePart'=>28,),57=>array('namePart'=>28,),58=>array('namePart'=>28,),59=>array(')'=>82,),60=>array(')'=>83,),61=>array('('=>84,),62=>array('nsSeparator'=>85,),64=>array('*'=>86,'namePart'=>87,'|'=>88,'nsSeparator'=>-43,')'=>-43,'+'=>-43,'::'=>-43,'->'=>-43,),67=>array(')'=>89,),68=>array('+'=>90,'nsSeparator'=>91,')'=>-37,'::'=>-37,'->'=>-37,),69=>array(')'=>92,),70=>array(')'=>93,),71=>array(')'=>94,),72=>array('('=>95,),73=>array('*'=>86,'namePart'=>87,'|'=>88,'$eof'=>-32,'||'=>-32,'&&'=>-32,')'=>-32,),76=>array('::'=>97,'->'=>98,),77=>array('public'=>52,'protected'=>53,'private'=>54,'final'=>55,),79=>array(')'=>100,'nsSeparator'=>44,),80=>array(')'=>101,'nsSeparator'=>44,),81=>array(')'=>102,'nsSeparator'=>44,),84=>array('*'=>104,),85=>array('**'=>106,'*'=>65,'namePart'=>66,),88=>array('namePart'=>107,),91=>array('**'=>106,'*'=>65,'namePart'=>66,),95=>array('*'=>104,),96=>array('*'=>65,'namePart'=>66,),103=>array(')'=>111,),105=>array('('=>112,'*'=>86,'namePart'=>87,'|'=>88,'nsSeparator'=>-44,),108=>array('*'=>86,'namePart'=>87,'|'=>88,')'=>-44,'+'=>-44,'nsSeparator'=>-44,'::'=>-44,'->'=>-44,),109=>array(')'=>113,),110=>array('*'=>86,'namePart'=>87,'|'=>88,')'=>-36,'('=>-36,),112=>array('*'=>104,),113=>array(')'=>115,),114=>array(')'=>116,),3=>array('$eof'=>-5,'||'=>-5,'&&'=>-5,')'=>-5,),5=>array('$eof'=>-7,'||'=>-7,'&&'=>-7,')'=>-7,),7=>array('$eof'=>-9,'||'=>-9,'&&'=>-9,')'=>-9,),8=>array('$eof'=>-10,'||'=>-10,'&&'=>-10,')'=>-10,),9=>array('$eof'=>-11,'||'=>-11,'&&'=>-11,')'=>-11,),10=>array('$eof'=>-12,'||'=>-12,'&&'=>-12,')'=>-12,),11=>array('$eof'=>-13,'||'=>-13,'&&'=>-13,')'=>-13,),12=>array('$eof'=>-14,'||'=>-14,'&&'=>-14,')'=>-14,),13=>array('$eof'=>-15,'||'=>-15,'&&'=>-15,')'=>-15,),14=>array('$eof'=>-16,'||'=>-16,'&&'=>-16,')'=>-16,),15=>array('$eof'=>-17,'||'=>-17,'&&'=>-17,')'=>-17,),16=>array('$eof'=>-18,'||'=>-18,'&&'=>-18,')'=>-18,),17=>array('$eof'=>-19,'||'=>-19,'&&'=>-19,')'=>-19,),18=>array('$eof'=>-20,'||'=>-20,'&&'=>-20,')'=>-20,),28=>array('->'=>-51,'nsSeparator'=>-51,')'=>-51,),31=>array('$eof'=>-6,'||'=>-6,'&&'=>-6,')'=>-6,),46=>array('$eof'=>-4,'||'=>-4,'&&'=>-4,')'=>-4,),47=>array('$eof'=>-8,'||'=>-8,'&&'=>-8,')'=>-8,),49=>array(')'=>-33,),52=>array('**'=>-56,'*'=>-56,'namePart'=>-56,'|'=>-56,'public'=>-56,'protected'=>-56,'private'=>-56,'final'=>-56,),53=>array('**'=>-57,'*'=>-57,'namePart'=>-57,'|'=>-57,'public'=>-57,'protected'=>-57,'private'=>-57,'final'=>-57,),54=>array('**'=>-58,'*'=>-58,'namePart'=>-58,'|'=>-58,'public'=>-58,'protected'=>-58,'private'=>-58,'final'=>-58,),55=>array('**'=>-59,'*'=>-59,'namePart'=>-59,'|'=>-59,'public'=>-59,'protected'=>-59,'private'=>-59,'final'=>-59,),63=>array('nsSeparator'=>-42,')'=>-42,'+'=>-42,'::'=>-42,'->'=>-42,),65=>array('$eof'=>-46,'||'=>-46,'&&'=>-46,')'=>-46,'('=>-46,'nsSeparator'=>-46,'*'=>-46,'namePart'=>-46,'|'=>-46,'+'=>-46,'::'=>-46,'->'=>-46,),66=>array('$eof'=>-47,'||'=>-47,'&&'=>-47,')'=>-47,'('=>-47,'nsSeparator'=>-47,'*'=>-47,'namePart'=>-47,'|'=>-47,'+'=>-47,'::'=>-47,'->'=>-47,),74=>array('->'=>-52,'nsSeparator'=>-52,')'=>-52,),75=>array('$eof'=>-21,'||'=>-21,'&&'=>-21,')'=>-21,),78=>array('**'=>-54,'*'=>-54,'namePart'=>-54,),82=>array('$eof'=>-22,'||'=>-22,'&&'=>-22,')'=>-22,),83=>array('$eof'=>-23,'||'=>-23,'&&'=>-23,')'=>-23,),86=>array('$eof'=>-48,'||'=>-48,'&&'=>-48,')'=>-48,'('=>-48,'nsSeparator'=>-48,'*'=>-48,'namePart'=>-48,'|'=>-48,'+'=>-48,'::'=>-48,'->'=>-48,),87=>array('$eof'=>-49,'||'=>-49,'&&'=>-49,')'=>-49,'('=>-49,'nsSeparator'=>-49,'*'=>-49,'namePart'=>-49,'|'=>-49,'+'=>-49,'::'=>-49,'->'=>-49,),89=>array('$eof'=>-24,'||'=>-24,'&&'=>-24,')'=>-24,),90=>array(')'=>-38,'::'=>-38,'->'=>-38,),92=>array('$eof'=>-28,'||'=>-28,'&&'=>-28,')'=>-28,),93=>array('$eof'=>-29,'||'=>-29,'&&'=>-29,')'=>-29,),94=>array('$eof'=>-30,'||'=>-30,'&&'=>-30,')'=>-30,),97=>array('*'=>-40,'namePart'=>-40,),98=>array('*'=>-41,'namePart'=>-41,),99=>array('**'=>-53,'*'=>-53,'namePart'=>-53,),100=>array('$eof'=>-25,'||'=>-25,'&&'=>-25,')'=>-25,),101=>array('$eof'=>-26,'||'=>-26,'&&'=>-26,')'=>-26,),102=>array('$eof'=>-27,'||'=>-27,'&&'=>-27,')'=>-27,),104=>array(')'=>-39,),106=>array('nsSeparator'=>-45,')'=>-45,'+'=>-45,'::'=>-45,'->'=>-45,),107=>array('$eof'=>-50,'||'=>-50,'&&'=>-50,')'=>-50,'('=>-50,'nsSeparator'=>-50,'*'=>-50,'namePart'=>-50,'|'=>-50,'+'=>-50,'::'=>-50,'->'=>-50,),111=>array(')'=>-34,),115=>array('$eof'=>-31,'||'=>-31,'&&'=>-31,')'=>-31,),116=>array(')'=>-35,),),'goto'=>array(0=>array('pointcutExpression'=>1,'conjugatedExpression'=>2,'negatedExpression'=>3,'brakedExpression'=>5,'singlePointcut'=>7,'accessPointcut'=>8,'annotatedAccessPointcut'=>9,'executionPointcut'=>10,'annotatedExecutionPointcut'=>11,'withinPointcut'=>12,'annotatedWithinPointcut'=>13,'initializationPointcut'=>14,'staticInitializationPointcut'=>15,'cflowbelowPointcut'=>16,'dynamicExecutionPointcut'=>17,'pointcutReference'=>18,'namespaceName'=>27,),4=>array('brakedExpression'=>31,'singlePointcut'=>7,'accessPointcut'=>8,'annotatedAccessPointcut'=>9,'executionPointcut'=>10,'annotatedExecutionPointcut'=>11,'withinPointcut'=>12,'annotatedWithinPointcut'=>13,'initializationPointcut'=>14,'staticInitializationPointcut'=>15,'cflowbelowPointcut'=>16,'dynamicExecutionPointcut'=>17,'pointcutReference'=>18,'namespaceName'=>27,),6=>array('pointcutExpression'=>32,'conjugatedExpression'=>2,'negatedExpression'=>3,'brakedExpression'=>5,'singlePointcut'=>7,'accessPointcut'=>8,'annotatedAccessPointcut'=>9,'executionPointcut'=>10,'annotatedExecutionPointcut'=>11,'withinPointcut'=>12,'annotatedWithinPointcut'=>13,'initializationPointcut'=>14,'staticInitializationPointcut'=>15,'cflowbelowPointcut'=>16,'dynamicExecutionPointcut'=>17,'pointcutReference'=>18,'namespaceName'=>27,),29=>array('conjugatedExpression'=>45,'negatedExpression'=>3,'brakedExpression'=>5,'singlePointcut'=>7,'accessPointcut'=>8,'annotatedAccessPointcut'=>9,'executionPointcut'=>10,'annotatedExecutionPointcut'=>11,'withinPointcut'=>12,'annotatedWithinPointcut'=>13,'initializationPointcut'=>14,'staticInitializationPointcut'=>15,'cflowbelowPointcut'=>16,'dynamicExecutionPointcut'=>17,'pointcutReference'=>18,'namespaceName'=>27,),30=>array('negatedExpression'=>46,'brakedExpression'=>5,'singlePointcut'=>7,'accessPointcut'=>8,'annotatedAccessPointcut'=>9,'executionPointcut'=>10,'annotatedExecutionPointcut'=>11,'withinPointcut'=>12,'annotatedWithinPointcut'=>13,'initializationPointcut'=>14,'staticInitializationPointcut'=>15,'cflowbelowPointcut'=>16,'dynamicExecutionPointcut'=>17,'pointcutReference'=>18,'namespaceName'=>27,),33=>array('propertyAccessReference'=>48,'memberReference'=>49,'memberModifiers'=>50,'memberModifier'=>51,),37=>array('methodExecutionReference'=>59,'functionExecutionReference'=>60,'memberReference'=>61,'namespacePattern'=>62,'memberModifiers'=>50,'namePatternPart'=>64,'memberModifier'=>51,),38=>array('classFilter'=>67,'namespacePattern'=>68,'namePatternPart'=>64,),39=>array('classFilter'=>69,'namespacePattern'=>68,'namePatternPart'=>64,),40=>array('classFilter'=>70,'namespacePattern'=>68,'namePatternPart'=>64,),41=>array('executionPointcut'=>71,),42=>array('memberReference'=>72,'memberModifiers'=>50,'memberModifier'=>51,),43=>array('namePatternPart'=>73,),50=>array('classFilter'=>76,'namespacePattern'=>68,'namePatternPart'=>64,),51=>array('memberModifiers'=>78,'memberModifier'=>51,),56=>array('namespaceName'=>79,),57=>array('namespaceName'=>80,),58=>array('namespaceName'=>81,),76=>array('memberAccessType'=>96,),77=>array('memberModifiers'=>99,'memberModifier'=>51,),84=>array('argumentList'=>103,),85=>array('namePatternPart'=>105,),91=>array('namePatternPart'=>108,),95=>array('argumentList'=>109,),96=>array('namePatternPart'=>110,),112=>array('argumentList'=>114,),)); src/Aop/Pointcut/PointcutParser.php000064400000001207144760116330013357 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Pointcut/PointcutReference.php000064400000005053144760116330014024 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Core\AspectContainer; use Go\Core\AspectKernel; /** * Reference to the pointcut holds an id of pointcut to fetch when needed */ class PointcutReference implements Pointcut { /** * @var Pointcut */ protected $pointcut = null; /** * Name of the pointcut to fetch from the container * * @var string */ private $pointcutName; /** * Instance of aspect container * * @var AspectContainer */ private $container; /** * Pointcut reference constructor * * @param AspectContainer $container Instance of container * @param string $pointcutName Referenced pointcut */ public function __construct(AspectContainer $container, $pointcutName) { $this->container = $container; $this->pointcutName = $pointcutName; } /** * Performs matching of point of code * * @param mixed $point Specific part of code, can be any Reflection class * @param object|string|null $instance [Optional] Instance for dynamic matching * @param array $arguments [Optional] Extra arguments for dynamic matching * * @return bool */ public function matches($point, $instance = null, array $arguments = null) { return $this->getPointcut()->matches($point, $instance, $arguments); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->getPointcut()->getKind(); } /** * Return the class filter for this pointcut. * * @return PointFilter */ public function getClassFilter() { return $this->getPointcut()->getClassFilter(); } /** * Returns a real pointcut from the container * * @return Pointcut */ public function getPointcut() { if (!$this->pointcut) { $this->pointcut = $this->container->getPointcut($this->pointcutName); } return $this->pointcut; } /** * {@inheritdoc} */ public function __sleep() { return array('pointcutName'); } /** * {@inheritdoc} */ public function __wakeup() { $this->container = AspectKernel::getInstance()->getContainer(); } } src/Aop/Pointcut/SignaturePointcut.php000064400000003630144760116330014066 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; use Go\Aop\PointFilter; /** * Signature pointcut checks element signature (modifiers and name) to match it */ class SignaturePointcut implements Pointcut { use PointcutClassFilterTrait; /** * Element name to match, can contain wildcards **,*,?,| * * @var string */ protected $name = ''; /** * Modifier filter for element * * @var PointFilter */ protected $modifierFilter; /** * Filter kind * * @var int */ protected $filterKind = 0; /** * Signature matcher constructor * * @param integer $filterKind Kind of filter, e.g. KIND_CLASS * @param string $name Name of the entity to match or glob pattern * @param PointFilter $modifierFilter Method modifier filter */ public function __construct($filterKind, $name, PointFilter $modifierFilter) { $this->filterKind = $filterKind; $this->name = $name; $this->regexp = strtr(preg_quote($this->name, '/'), array( '\\*' => '[^\\\\]+?', '\\*\\*' => '.+?', '\\?' => '.', '\\|' => '|' )); $this->modifierFilter = $modifierFilter; } /** * {@inheritdoc} */ public function matches($point) { if (!$this->modifierFilter->matches($point)) { return false; } return ($point->name === $this->name) || (bool) preg_match("/^(?:{$this->regexp})$/", $point->name); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->filterKind; } } src/Aop/Pointcut/TruePointcut.php000064400000002233144760116330013042 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\Pointcut; /** * Canonical Pointcut instance that always matches. */ class TruePointcut implements Pointcut { use PointcutClassFilterTrait; /** * Filter kind * * @var int */ protected $filterKind = 0; /** * Default constructor can be used to specify concrete filter kind * * @param int $filterKind Kind of filter, e.g. KIND_METHOD */ public function __construct($filterKind = self::KIND_ALL) { $this->filterKind = $filterKind; } /** * 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 $this->filterKind; } } src/Aop/PointcutAdvisor.php000064400000001163144760116330011726 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Proxy.php000064400000000615144760116330007713 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Support/AbstractGenericPointcutAdvisor.php000064400000002222144760116330016360 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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; /** * 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/Aop/Support/AndPointFilter.php000064400000002733144760116330013133 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Support/AnnotatedReflectionMethod.php000064400000002761144760116330015343 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Doctrine\Common\Annotations\Reader; use Go\Core\AspectKernel; use ReflectionMethod; /** * Extended version of ReflectionMethod with annotation support */ class AnnotatedReflectionMethod extends ReflectionMethod { /** * Annotation reader * * @var Reader */ private static $annotationReader = null; /** * Gets a method annotation. * * @param string $annotationName The name of the annotation. * @return mixed The Annotation or NULL, if the requested annotation does not exist. */ public function getAnnotation($annotationName) { return self::getReader()->getMethodAnnotation($this, $annotationName); } /** * Gets the annotations applied to a method. * * @return array An array of Annotations. */ public function getAnnotations() { return self::getReader()->getMethodAnnotations($this); } /** * Returns an annotation reader * * @return Reader $reader */ private static function getReader() { if (!self::$annotationReader) { self::$annotationReader = AspectKernel::getInstance()->getContainer()->get('aspect.annotation.reader'); }; return self::$annotationReader; } } src/Aop/Support/DeclareParentsAdvisor.php000064400000006232144760116330014473 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 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/Aop/Support/DefaultPointcutAdvisor.php000064400000004456144760116330014717 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\Advice; use Go\Aop\Framework\DynamicInvocationMatcherInterceptor; use Go\Aop\Pointcut; use Go\Aop\PointFilter; /** * 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); } /** * {@inheritdoc} */ public function getAdvice() { $advice = parent::getAdvice(); if ($this->pointcut->getKind() & PointFilter::KIND_DYNAMIC) { $advice = new DynamicInvocationMatcherInterceptor( $this->pointcut, $advice ); } return $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/Aop/Support/InheritanceClassFilter.php000064400000002635144760116330014637 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Support/LazyPointcutAdvisor.php000064400000004036144760116330014244 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\Advice; use Go\Aop\Pointcut; use Go\Core\AspectContainer; /** * Lazy pointcut advisor is used to create a delayed pointcut only when needed */ class LazyPointcutAdvisor extends AbstractGenericPointcutAdvisor { /** * Pointcut expression * * @var string */ private $pointcutExpression; /** * Instance of parsed pointcut * * @var Pointcut|null */ private $pointcut; /** * @var AspectContainer */ private $container; /** * Create a DefaultPointcutAdvisor, specifying Pointcut and Advice. * * @param AspectContainer $container Instance of container * @param string $pointcutExpression The Pointcut targeting the Advice * @param Advice $advice The Advice to run when Pointcut matches */ public function __construct(AspectContainer $container, $pointcutExpression, Advice $advice) { $this->container = $container; $this->pointcutExpression = $pointcutExpression; $this->setAdvice($advice); } /** * Get the Pointcut that drives this advisor. * * @return Pointcut The pointcut */ public function getPointcut() { if (!$this->pointcut) { // Inject this dependencies and make them lazy! // should be extracted from AbstractAspectLoaderExtension into separate class /** @var Pointcut\PointcutLexer $lexer */ $lexer = $this->container->get('aspect.pointcut.lexer'); /** @var Pointcut\PointcutParser $parser */ $parser = $this->container->get('aspect.pointcut.parser'); $tokenStream = $lexer->lex($this->pointcutExpression); $this->pointcut = $parser->parse($tokenStream); } return $this->pointcut; } } src/Aop/Support/ModifierMatcherFilter.php000064400000005207144760116330014460 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\PointFilter; /** * ModifierMatcherFilter performs checks on modifiers for reflection point */ 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; } /** * Merges current one filter with another one and returns a new combined filter * * @param ModifierMatcherFilter $filter * * @return ModifierMatcherFilter */ public function merge(ModifierMatcherFilter $filter) { $newFilter = clone $this; $newFilter->andMask |= $filter->andMask; $newFilter->orMask |= $filter->orMask; $newFilter->notMask |= $filter->notMask; return $newFilter; } } src/Aop/Support/NamespacedReflectionFunction.php000064400000001756144760116330016036 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Support/NotPointFilter.php000064400000002277144760116330013174 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\PointFilter; /** * Logical "not" filter. */ class NotPointFilter implements PointFilter { /** * Kind of filter * * @var int */ private $kind = 0; /** * First part of filter * * @var PointFilter|null */ private $first = null; /** * Not constructor * * @param PointFilter $first First part of filter */ public function __construct(PointFilter $first) { $this->kind = $first->getKind(); $this->first = $first; } /** * 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); } /** * Returns the kind of point filter * * @return integer */ public function getKind() { return $this->kind; } } src/Aop/Support/OrPointFilter.php000064400000002730144760116330013006 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Aop/Support/PointcutBuilder.php000064400000007456144760116330013374 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\Framework\AfterInterceptor; use Go\Aop\Framework\AfterThrowingInterceptor; use Go\Aop\Framework\AroundInterceptor; use Go\Aop\Framework\BeforeInterceptor; use Go\Aop\Framework\DeclareErrorInterceptor; use Go\Core\AspectContainer; /** * Pointcut builder provides simple DSL for declaring pointcuts in plain PHP code */ class PointcutBuilder { /** * @var AspectContainer */ protected $container; /** * Default constructor for the builder * * @param AspectContainer $container Instance of container */ public function __construct(AspectContainer $container) { $this->container = $container; } /** * Declares the "Before" hook for specific pointcut expression * * @param string $pointcutExpression Pointcut, e.g. "within(**)" * @param callable $advice Advice to call */ public function before($pointcutExpression, \Closure $advice) { $this->container->registerAdvisor( new LazyPointcutAdvisor($this->container, $pointcutExpression, new BeforeInterceptor($advice)), $this->getPointcutId($pointcutExpression) ); } /** * Declares the "After" hook for specific pointcut expression * * @param string $pointcutExpression Pointcut, e.g. "within(**)" * @param callable $advice Advice to call */ public function after($pointcutExpression, \Closure $advice) { $this->container->registerAdvisor( new LazyPointcutAdvisor($this->container, $pointcutExpression, new AfterInterceptor($advice)), $this->getPointcutId($pointcutExpression) ); } /** * Declares the "AfterThrowing" hook for specific pointcut expression * * @param string $pointcutExpression Pointcut, e.g. "within(**)" * @param callable $advice Advice to call */ public function afterThrowing($pointcutExpression, \Closure $advice) { $this->container->registerAdvisor( new LazyPointcutAdvisor($this->container, $pointcutExpression, new AfterThrowingInterceptor($advice)), $this->getPointcutId($pointcutExpression) ); } /** * Declares the "Around" hook for specific pointcut expression * * @param string $pointcutExpression Pointcut, e.g. "within(**)" * @param callable $advice Advice to call */ public function around($pointcutExpression, \Closure $advice) { $this->container->registerAdvisor( new LazyPointcutAdvisor($this->container, $pointcutExpression, new AroundInterceptor($advice)), $this->getPointcutId($pointcutExpression) ); } /** * Declares the error message for specific pointcut expression * * @param string $pointcutExpression Pointcut, e.g. "within(**)" * @param string $message Error message to show * @param integer $level Error level to trigger */ public function declareError($pointcutExpression, $message, $level = E_USER_ERROR) { $advice = new DeclareErrorInterceptor($message, $level); $this->container->registerAdvisor( new LazyPointcutAdvisor($this->container, $pointcutExpression, $advice), $this->getPointcutId($pointcutExpression) ); } /** * Returns a unique name for pointcut expression * * @param string $pointcutExpression * * @return string */ private function getPointcutId($pointcutExpression) { static $index = 0; return preg_replace('/\W+/', '_', $pointcutExpression) . '.' . $index++; } } src/Aop/Support/SimpleNamespaceFilter.php000064400000003426144760116330014465 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; 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; } } src/Aop/Support/TruePointFilter.php000064400000002172144760116330013345 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Console/Command/WarmupCommand.php000064400000010111144760116330013575 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Console\Command; use Go\Core\AspectKernel; use Go\Instrument\ClassLoading\SourceTransformingLoader; use Go\Instrument\FileSystem\Enumerator; use Go\Instrument\Transformer\FilterInjectorTransformer; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Console command for warming the cache */ class WarmupCommand extends Command { /** * {@inheritDoc} */ protected function configure() { $this ->setName('goaop:warmup') ->addArgument('loader', InputArgument::REQUIRED, "Path to the aspect loader file") ->setDescription("Warm up the cache with woven aspects") ->setHelp(<<writeln("Loading aspect kernel for warmup..."); $loader = $input->getArgument('loader'); $path = stream_resolve_include_path($loader); if (!is_readable($path)) { throw new \InvalidArgumentException("Invalid loader path: {$loader}"); } include_once $path; if (!class_exists('Go\Core\AspectKernel', false)) { $message = "Kernel was not initialized yet, please configure it in the {$path}"; throw new \InvalidArgumentException($message); } $kernel = AspectKernel::getInstance(); $options = $kernel->getOptions(); if (empty($options['cacheDir'])) { throw new \InvalidArgumentException("Cache warmer require the `cacheDir` options to be configured"); } $enumerator = new Enumerator($options['appDir'], $options['includePaths'], $options['excludePaths']); $iterator = $enumerator->enumerate(); $totalFiles = iterator_count($iterator); $output->writeln("Total {$totalFiles} files to process."); $iterator->rewind(); set_error_handler(function ($errno, $errstr, $errfile, $errline) { throw new \ErrorException($errstr, $errno, 0, $errfile, $errline); }); $index = 0; $errors = array(); foreach ($iterator as $file) { if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $output->writeln("Processing file {$file->getRealPath()}"); } $isSuccess = null; try { // This will trigger creation of cache file_get_contents( FilterInjectorTransformer::PHP_FILTER_READ. SourceTransformingLoader::FILTER_IDENTIFIER. "/resource=" . $file->getRealPath() ); $isSuccess = true; } catch (\Exception $e) { $isSuccess = false; $errors[$file->getRealPath()] = $e; } if ($output->getVerbosity() == OutputInterface::VERBOSITY_NORMAL) { $output->write($isSuccess ? '.' : 'E'); if (++$index % 50 == 0) { $output->writeln("($index/$totalFiles)"); } } } restore_error_handler(); if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { foreach ($errors as $file=>$error) { $message = "File {$file} is not processed correctly due to exception: {$error->getMessage()}"; $output->writeln($message); } } $output->writeln("Done"); } } src/Core/AbstractAspectLoaderExtension.php000064400000011233144760116330014670 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Dissect\Lexer\Exception\RecognitionException; use Dissect\Lexer\Lexer; use Dissect\Lexer\TokenStream\TokenStream; use Dissect\Parser\Exception\UnexpectedTokenException; use Dissect\Parser\Parser; use Go\Aop\Aspect; use Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Lang\Annotation; use ReflectionMethod; use ReflectionProperty; /** * Abstract aspect loader */ abstract class AbstractAspectLoaderExtension implements AspectLoaderExtension { /** * Instance of pointcut lexer * * @var null|Lexer */ protected $pointcutLexer = null; /** * Instance of pointcut parser * * @var null|Parser */ protected $pointcutParser = null; /** * Default initialization of dependencies * * @param Lexer $pointcutLexer Instance of pointcut lexer * @param Parser $pointcutParser Instance of pointcut parser */ public function __construct(Lexer $pointcutLexer, Parser $pointcutParser) { $this->pointcutLexer = $pointcutLexer; $this->pointcutParser = $pointcutParser; } /** * General method for parsing pointcuts * * @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|PointFilter */ protected function parsePointcut(Aspect $aspect, $reflection, $metaInformation) { $stream = $this->makeLexicalAnalyze($aspect, $reflection, $metaInformation); return $this->parseTokenStream($reflection, $metaInformation, $stream); } /** * Performs lexical analyze of pointcut * * @param Aspect $aspect Instance of aspect * @param ReflectionMethod|ReflectionProperty $reflection * @param $metaInformation * * @return TokenStream * @throws \UnexpectedValueException */ protected function makeLexicalAnalyze(Aspect $aspect, $reflection, $metaInformation) { try { $resolvedThisPointcut = str_replace('$this', get_class($aspect), $metaInformation->value); $stream = $this->pointcutLexer->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 ReflectionMethod|ReflectionProperty $reflection * @param $metaInformation * @param TokenStream $stream * @return Pointcut * * @throws \UnexpectedValueException */ protected function parseTokenStream($reflection, $metaInformation, $stream) { try { $pointcut = $this->pointcutParser->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/Core/AdviceMatcher.php000064400000020167144760116330011446 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use ReflectionMethod; use ReflectionProperty; use Go\Aop; use Go\Aop\Support\NamespacedReflectionFunction; 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; /** * Flag to enable/disable support of global function interception * * @var bool */ private $isInterceptFunctions = false; /** * Constructor * * @param AspectLoader $loader Instance of aspect loader * @param bool $isInterceptFunctions Optional flag to enable function interception */ public function __construct(AspectLoader $loader, $isInterceptFunctions = false) { $this->loader = $loader; $this->isInterceptFunctions = $isInterceptFunctions; } /** * Returns list of function advices for namespace * * @param ReflectionFileNamespace $namespace * @param array|Aop\Advisor[] $advisors List of advisor to match * * @return array */ public function getAdvicesForFunctions(ReflectionFileNamespace $namespace, array $advisors) { if (!$this->isInterceptFunctions || $namespace->getName() == 'no-namespace') { return array(); } $advices = array(); foreach ($advisors as $advisorId => $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, $advisorId, $pointcut) ); } } } return $advices; } /** * Return list of advices for class * * @param ParsedReflectionClass $class Class to advise * @param array|Aop\Advisor[] $advisors List of advisor to match * * @return array|Aop\Advice[] List of advices for class */ public function getAdvicesForClass(ParsedReflectionClass $class, array $advisors) { $classAdvices = array(); $parentClass = $class->getParentClass(); if ($parentClass && preg_match('/' . AspectContainer::AOP_PROXIED_SUFFIX . '$/', $parentClass->name)) { $originalClass = $parentClass; } else { $originalClass = $class; } foreach ($advisors as $advisorId => $advisor) { if ($advisor instanceof Aop\PointcutAdvisor) { $pointcut = $advisor->getPointcut(); if ($pointcut->getClassFilter()->matches($class)) { $classAdvices = array_merge_recursive( $classAdvices, $this->getAdvicesFromAdvisor($originalClass, $advisor, $advisorId, $pointcut) ); } } if ($advisor instanceof Aop\IntroductionAdvisor) { if ($advisor->getClassFilter()->matches($class)) { $classAdvices = array_merge_recursive( $classAdvices, $this->getIntroductionFromAdvisor($originalClass, $advisor, $advisorId) ); } } } return $classAdvices; } /** * Returns list of advices from advisor and point filter * * @param ParsedReflectionClass $class Class to inject advices * @param Aop\PointcutAdvisor $advisor Advisor for class * @param string $advisorId Identifier of advisor * @param Aop\PointFilter $filter Filter for points * * @return array */ private function getAdvicesFromAdvisor( ParsedReflectionClass $class, Aop\PointcutAdvisor $advisor, $advisorId, Aop\PointFilter $filter) { $classAdvices = array(); $filterKind = $filter->getKind(); // Check class only for class filters if ($filterKind & Aop\PointFilter::KIND_CLASS) { if ($filter->matches($class)) { // Dynamic initialization if ($filterKind & Aop\PointFilter::KIND_INIT) { $classAdvices[AspectContainer::INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice(); } // Static initalization if ($filterKind & Aop\PointFilter::KIND_STATIC_INIT) { $classAdvices[AspectContainer::STATIC_INIT_PREFIX]['root'][$advisorId] = $advisor->getAdvice(); } } } // Check methods in class only for method filters if ($filterKind & Aop\PointFilter::KIND_METHOD) { $mask = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED; foreach ($class->getMethods($mask) as $method) { /** @var $method ReflectionMethod| */ if ($filter->matches($method)) { $prefix = $method->isStatic() ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX; $classAdvices[$prefix][$method->name][$advisorId] = $advisor->getAdvice(); } } } // Check properties in class only for property filters if ($filterKind & Aop\PointFilter::KIND_PROPERTY) { $mask = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED; foreach ($class->getProperties($mask) as $property) { /** @var $property ReflectionProperty */ if ($filter->matches($property) && !$property->isStatic()) { $classAdvices[AspectContainer::PROPERTY_PREFIX][$property->name][$advisorId] = $advisor->getAdvice(); } } } return $classAdvices; } /** * Returns list of introduction advices from advisor * * @param ParsedReflectionClass $class Class to inject advices * @param Aop\IntroductionAdvisor $advisor Advisor for class * @param string $advisorId Identifier of advisor * * @return array */ private function getIntroductionFromAdvisor( ParsedReflectionClass $class, Aop\IntroductionAdvisor $advisor, $advisorId) { // Do not make introduction for traits if ($class->isTrait()) { return array(); } $advice = $advisor->getAdvice(); $classAdvices[AspectContainer::INTRODUCTION_TRAIT_PREFIX][$advisorId] = $advice; return $classAdvices; } /** * Returns list of function advices for specific namespace * * @param ReflectionFileNamespace $namespace * @param Aop\PointcutAdvisor $advisor Advisor for class * @param string $advisorId Identifier of advisor * @param Aop\PointFilter $pointcut Filter for points * * @return array */ private function getFunctionAdvicesFromAdvisor( ReflectionFileNamespace $namespace, Aop\PointcutAdvisor $advisor, $advisorId, Aop\PointFilter $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][$advisorId] = $advisor->getAdvice(); } } return $advices; } } src/Core/AspectContainer.php000064400000006003144760116330012022 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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"; /** * Initialization prefix, is used for initialization pointcuts */ const INIT_PREFIX = "init"; /** * Initialization prefix, is used for initialization pointcuts */ const STATIC_INIT_PREFIX = "staticinit"; /** * 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); /** * Returns an advisor by identifier * * @param string $id Advisor identifier * * @return Aop\Advisor */ public function getAdvisor($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/Core/AspectKernel.php000064400000021172144760116330011324 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Go\Aop\Features; use Go\Instrument\ClassLoading\AopComposerLoader; use Go\Instrument\ClassLoading\SourceTransformingLoader; use Go\Instrument\CleanableMemory; use Go\Instrument\PathResolver; use Go\Instrument\Transformer\ConstructorExecutionTransformer; 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 TokenReflection; /** * Abstract aspect kernel is used to prepare an application to work with aspects. */ abstract class AspectKernel { /** * Version of kernel */ const VERSION = '0.4.3'; /** * Kernel options * * @var array */ protected $options = array( 'features' => 0 ); /** * 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 = $this->normalizeOptions($options); define('AOP_ROOT_DIR', $this->options['appDir']); define('AOP_CACHE_DIR', $this->options['cacheDir']); /** @var $container AspectContainer */ $container = $this->container = new $this->options['containerClass']; $container->set('kernel', $this); $container->set('kernel.interceptFunctions', $this->hasFeature(Features::INTERCEPT_FUNCTIONS)); $container->set('kernel.options', $this->options); SourceTransformingLoader::register(); foreach ($this->registerTransformers() as $sourceTransformer) { SourceTransformingLoader::addTransformer($sourceTransformer); } // Register kernel resources in the container for debug mode if ($this->options['debug']) { $this->addKernelResourcesToContainer($container); } AopComposerLoader::init($this->options, $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 a default bit mask of features by checking PHP version * * @return int */ public static function getDefaultFeatures() { $features = 0; if (PHP_VERSION_ID >= 50400) { $features += Features::USE_CLOSURE; $features += Features::USE_TRAIT; } if (PHP_VERSION_ID >= 50500) { $features += Features::USE_STATIC_FOR_LSB; } if (PHP_VERSION_ID >= 50600) { $features += Features::USE_SPLAT_OPERATOR; } return $features; } /** * Checks if kernel configuration has enabled specific feature * * @param integer $featureToCheck See Go\Aop\Features enumeration class for features * * @return bool Whether specific feature enabled or not */ public function hasFeature($featureToCheck) { return ($this->options['features'] & $featureToCheck) !== 0; } /** * 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 * cacheFileMode - integer Binary mask of permission bits that is set to cache files * features - integer Binary mask of features * 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. * * @return array */ protected function getDefaultOptions() { $features = static::getDefaultFeatures(); return array( 'debug' => false, 'appDir' => __DIR__ . '/../../../../../', 'cacheDir' => null, 'cacheFileMode' => null, 'features' => $features, 'includePaths' => array(), 'excludePaths' => array(), 'containerClass' => static::$containerClass, ); } /** * Normalizes options for the kernel * * @param array $options List of options * * @return array */ protected function normalizeOptions(array $options) { $options = array_replace($this->getDefaultOptions(), $options); if ($options['cacheDir']) { $options['excludePaths'][] = $options['cacheDir']; } $options['excludePaths'][] = __DIR__ . '/../'; $options['appDir'] = PathResolver::realpath($options['appDir']); $options['cacheDir'] = PathResolver::realpath($options['cacheDir']); $options['includePaths'] = PathResolver::realpath($options['includePaths']); $options['excludePaths'] = PathResolver::realpath($options['excludePaths']); return $options; } /** * 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 * * @return array|SourceTransformer[] */ protected function registerTransformers() { $cacheManager = $this->getContainer()->get('aspect.cache.path.manager'); $filterInjector = new FilterInjectorTransformer($this, SourceTransformingLoader::getId(), $cacheManager); $magicTransformer = new MagicConstantTransformer($this); $aspectKernel = $this; $sourceTransformers = function () use ($filterInjector, $magicTransformer, $aspectKernel, $cacheManager) { $transformers = array(); $aspectContainer = $aspectKernel->getContainer(); $transformers[] = new WeavingTransformer( $aspectKernel, new TokenReflection\Broker( new CleanableMemory() ), $aspectContainer->get('aspect.advice_matcher'), $cacheManager, $aspectContainer->get('aspect.cached.loader') ); if ($aspectKernel->hasFeature(Features::INTERCEPT_INCLUDES)) { $transformers[] = $filterInjector; } $transformers[] = $magicTransformer; if ($aspectKernel->hasFeature(Features::INTERCEPT_INITIALIZATIONS)) { $transformers[] = new ConstructorExecutionTransformer(); } return $transformers; }; return array( new CachingTransformer($this, $sourceTransformers, $cacheManager) ); } /** * Add resources for kernel * * @param AspectContainer $container */ protected function addKernelResourcesToContainer(AspectContainer $container) { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $refClass = new \ReflectionObject($this); $container->addResource($trace[1]['file']); $container->addResource($refClass->getFileName()); } } src/Core/AspectLoader.php000064400000015131144760116330011310 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Doctrine\Common\Annotations\Reader; use Go\Aop\Advisor; use Go\Aop\Aspect; use Go\Aop\Pointcut; use ReflectionClass; /** * 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(); /** * Annotation reader for aspects * * @var Reader|null */ protected $annotationReader = null; /** * List of aspects that was loaded * * @var array */ protected $loadedAspects = array(); /** * Loader constructor * * @param AspectContainer $container Instance of container to store pointcuts and advisors * @param Reader $reader Reader for annotations that is used for aspects */ public function __construct(AspectContainer $container, Reader $reader) { $this->container = $container; $this->annotationReader = $reader; } /** * 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 with the help of aspect loaders, but don't register it in the container * * @see loadAndRegister() method for registration * * @param \Go\Aop\Aspect $aspect Aspect to load * * @return array|Pointcut[]|Advisor[] */ public function load(Aspect $aspect) { $loadedItems = array(); $refAspect = new \ReflectionClass($aspect); if (!empty($this->loaders[AspectLoaderExtension::TARGET_CLASS])) { $loadedItems += $this->loadFrom($aspect, $refAspect, $this->loaders[AspectLoaderExtension::TARGET_CLASS]); } if (!empty($this->loaders[AspectLoaderExtension::TARGET_METHOD])) { $refMethods = $refAspect->getMethods(); foreach ($refMethods as $refMethod) { $loadedItems += $this->loadFrom($aspect, $refMethod, $this->loaders[AspectLoaderExtension::TARGET_METHOD]); } } if (!empty($this->loaders[AspectLoaderExtension::TARGET_PROPERTY])) { $refProperties = $refAspect->getProperties(); foreach ($refProperties as $refProperty) { $loadedItems += $this->loadFrom($aspect, $refProperty, $this->loaders[AspectLoaderExtension::TARGET_PROPERTY]); } } return $loadedItems; } /** * Loads and register all items of aspect in the container * * @param Aspect $aspect */ public function loadAndRegister(Aspect $aspect) { $loadedItems = $this->load($aspect); foreach ($loadedItems as $itemId => $item) { if ($item instanceof Pointcut) { $this->container->registerPointcut($item, $itemId); } if ($item instanceof Advisor) { $this->container->registerAdvisor($item, $itemId); } } $aspectClass = get_class($aspect); $this->loadedAspects[$aspectClass] = $aspectClass; } /** * Returns list of unloaded aspects in the container * * @return array|Aspect[] */ public function getUnloadedAspects() { $unloadedAspects = array(); foreach ($this->container->getByTag('aspect') as $aspect) { if (!isset($this->loadedAspects[get_class($aspect)])) { $unloadedAspects[] = $aspect; } } return $unloadedAspects; } /** * 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 * * @return array|Pointcut[]|Advisor[] */ protected function loadFrom(Aspect $aspect, $refPoint, array $loaders) { $loadedItems = array(); foreach ($loaders as $loader) { $loaderKind = $loader->getKind(); switch ($loaderKind) { case AspectLoaderExtension::KIND_REFLECTION: if ($loader->supports($aspect, $refPoint)) { $loadedItems += $loader->load($aspect, $refPoint); } break; case AspectLoaderExtension::KIND_ANNOTATION: $annotations = $this->getAnnotations($refPoint); foreach ($annotations as $annotation) { if ($loader->supports($aspect, $refPoint, $annotation)) { $loadedItems += $loader->load($aspect, $refPoint, $annotation); } } break; default: throw new \InvalidArgumentException("Unsupported loader kind {$loaderKind}"); } } return $loadedItems; } /** * 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) { switch (true) { case ($refPoint instanceof \ReflectionClass): return $this->annotationReader->getClassAnnotations($refPoint); case ($refPoint instanceof \ReflectionMethod): return $this->annotationReader->getMethodAnnotations($refPoint); case ($refPoint instanceof \ReflectionProperty): return $this->annotationReader->getPropertyAnnotations($refPoint); default: throw new \InvalidArgumentException("Unsupported reflection point " . get_class($refPoint)); } } } src/Core/AspectLoaderExtension.php000064400000004625144760116330013213 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Go\Aop\Advisor; use Go\Aop\Aspect; use Go\Aop\Pointcut; /** * 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 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 array|Pointcut[]|Advisor[] */ public function load(Aspect $aspect, $reflection, $metaInformation = null); } src/Core/CachedAspectLoader.php000064400000006100144760116330012374 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Go\Aop\Advisor; use Go\Aop\Aspect; use Go\Aop\Pointcut; use ReflectionClass; /** * Cached loader is responsible for faster initialization of pointcuts/advisors for concrete aspect * * @property AspectLoader loader */ class CachedAspectLoader extends AspectLoader { /** * Path to the cache directory * * @var null|string */ protected $cacheDir; /** * Identifier of original loader * * @var string */ protected $loaderId; /** * Cached loader constructor * * @param AspectContainer $container Instance of container * @param string $loaderId Original loader identifier * @param array $options List of kernel options */ public function __construct(AspectContainer $container, $loaderId, array $options = array()) { $this->cacheDir = isset($options['cacheDir']) ? $options['cacheDir'] : null; $this->loaderId = $loaderId; $this->container = $container; } /** * {@inheritdoc} */ public function load(Aspect $aspect) { $refAspect = new ReflectionClass($aspect); $fileName = $this->cacheDir . '/_aspect/' . sha1($refAspect->getName()); // If cache is present and actual, then use it if (file_exists($fileName) && filemtime($fileName) >= filemtime($refAspect->getFileName())) { $loadedItems = $this->loadFromCache($fileName); } else { $loadedItems = $this->loader->load($aspect); $this->saveToCache($loadedItems, $fileName); } return $loadedItems; } /** * {@inheritdoc} */ public function registerLoaderExtension(AspectLoaderExtension $loader) { $this->loader->registerLoaderExtension($loader); } /** * {@inheritdoc} */ public function __get($name) { if ($name === 'loader') { $this->loader = $this->container->get($this->loaderId); return $this->loader; } throw new \RuntimeException("Not implemented"); } /** * Loads pointcuts and advisors from the file * * @param string $fileName Name of the file with cache * * @return array|Pointcut[]|Advisor[] */ protected function loadFromCache($fileName) { $content = file_get_contents($fileName); $loadedItems = unserialize($content); return $loadedItems; } /** * Save pointcuts and advisors to the file * * @param array|Pointcut[]|Advisor[] $items List of items to store * @param string $fileName Name of the file with cache */ protected function saveToCache($items, $fileName) { $content = serialize($items); if (!is_dir(dirname($fileName))) { mkdir(dirname($fileName)); } file_put_contents($fileName, $content); } } src/Core/Container.php000064400000005434144760116330010671 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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) { 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]; } } /** * Checks if item with specified id is present in the container * * @param string $id Identifier * * @return bool */ public function has($id) { return isset($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/Core/GeneralAspectLoaderExtension.php000064400000011202144760116330014476 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Go\Aop\Advisor; 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; /** * 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 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 array|Pointcut[]|Advisor[] * * @throws \UnexpectedValueException */ public function load(Aspect $aspect, $reflection, $metaInformation = null) { $loadedItems = array(); $pointcut = $this->parsePointcut($aspect, $reflection, $metaInformation); $methodId = 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($aspect, $adviceCallback, $scope); } switch (true) { // Register a pointcut by its name case ($metaInformation instanceof Annotation\Pointcut): $loadedItems[$methodId] = $pointcut; break; case ($pointcut instanceof PointFilter): $advice = $this->getInterceptor($metaInformation, $adviceCallback); $loadedItems[$methodId] = new DefaultPointcutAdvisor($pointcut, $advice); break; default: throw new \UnexpectedValueException("Unsupported pointcut class: " . get_class($pointcut)); } return $loadedItems; } /** * @param $metaInformation * @param $adviceCallback * @return \Go\Aop\Intercept\Interceptor * @throws \UnexpectedValueException */ protected function getInterceptor($metaInformation, $adviceCallback) { $adviceOrder = $metaInformation->order; $pointcutExpression = $metaInformation->value; switch (true) { case ($metaInformation instanceof Annotation\Before): return new Framework\BeforeInterceptor($adviceCallback, $adviceOrder, $pointcutExpression); case ($metaInformation instanceof Annotation\After): return new Framework\AfterInterceptor($adviceCallback, $adviceOrder, $pointcutExpression); case ($metaInformation instanceof Annotation\Around): return new Framework\AroundInterceptor($adviceCallback, $adviceOrder, $pointcutExpression); case ($metaInformation instanceof Annotation\AfterThrowing): return new Framework\AfterThrowingInterceptor($adviceCallback, $adviceOrder, $pointcutExpression); default: throw new \UnexpectedValueException("Unsupported method meta class: " . get_class($metaInformation)); } } } src/Core/GoAspectContainer.php000064400000014327144760116330012320 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Doctrine\Common\Annotations\FileCacheReader; use ReflectionClass; use Go\Aop; use Go\Aop\Pointcut\PointcutLexer; use Go\Aop\Pointcut\PointcutGrammar; use Go\Aop\Pointcut\PointcutParser; use Go\Instrument\RawAnnotationReader; use Go\Instrument\ClassLoading\CachePathManager; use Doctrine\Common\Annotations\AnnotationReader; /** * 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 $container) { $aspectLoader = new AspectLoader( $container, $container->get('aspect.annotation.reader') ); $lexer = $container->get('aspect.pointcut.lexer'); $parser = $container->get('aspect.pointcut.parser'); // Register general aspect loader extension $aspectLoader->registerLoaderExtension(new GeneralAspectLoaderExtension($lexer, $parser)); $aspectLoader->registerLoaderExtension(new IntroductionAspectExtension($lexer, $parser)); return $aspectLoader; }); $this->share('aspect.cached.loader', function (Container $container) { $cachedAspectLoader = new CachedAspectLoader( $container, 'aspect.loader', $container->get('kernel.options') ); return $cachedAspectLoader; }); $this->share('aspect.advisor.accessor', function (Container $container) { return new LazyAdvisorAccessor( $container, $container->get('aspect.cached.loader') ); }); $this->share('aspect.advice_matcher', function (Container $container) { return new AdviceMatcher( $container->get('aspect.loader'), $container->get('kernel.interceptFunctions') ); }); $this->share('aspect.annotation.reader', function (Container $container) { $options = $container->get('kernel.options'); $reader = new AnnotationReader(); if (!empty($options['cacheDir'])) { $reader = new FileCacheReader( $reader, $options['cacheDir'] . DIRECTORY_SEPARATOR . '_annotations' . DIRECTORY_SEPARATOR, $options['debug'] ); } return $reader; }); $this->share('aspect.annotation.raw.reader', function () { return new RawAnnotationReader(); }); $this->share('aspect.cache.path.manager', function (Container $container) { return new CachePathManager($container->get('kernel')); }); // Pointcut services $this->share('aspect.pointcut.lexer', function () { return new PointcutLexer(); }); $this->share('aspect.pointcut.parser', function (Container $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')); } /** * Returns an advisor by identifier * * @param string $id Advisor identifier * * @return Aop\Advisor */ public function getAdvisor($id) { return $this->get("advisor.{$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) { $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 * * 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/Core/IntroductionAspectExtension.php000064400000006635144760116330014471 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Go\Aop\Advisor; use Go\Aop\Aspect; use Go\Aop\Framework; use Go\Aop\Pointcut; 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) || ($metaInformation instanceof Annotation\DeclareError); } /** * Loads definition from specific point of aspect into the 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 * * @return array|Pointcut[]|Advisor[] */ public function load(Aspect $aspect, $reflection, $metaInformation = null) { $loadedItems = array(); $pointcut = $this->parsePointcut($aspect, $reflection, $metaInformation); $propertyId = $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); $loadedItems[$propertyId] = $advisor; break; case ($metaInformation instanceof Annotation\DeclareError): $reflection->setAccessible(true); $message = $reflection->getValue($aspect); $level = $metaInformation->level; $advice = new Framework\DeclareErrorInterceptor($message, $level); $loadedItems[$propertyId] = new Support\DefaultPointcutAdvisor($pointcut, $advice); break; default: throw new \UnexpectedValueException("Unsupported pointcut class: " . get_class($pointcut)); } return $loadedItems; } } src/Core/LazyAdvisorAccessor.php000064400000003336144760116330012700 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Core; use Go\Aop\Advice; use Go\Aop\Advisor; /** * Provides an interface for loading of advisors from the container */ class LazyAdvisorAccessor { /** * @var AspectContainer|Container */ protected $container; /** * Aspect loader instance * * @var AspectLoader */ protected $loader; /** * Accessor constructor * * @param AspectContainer $container * @param AspectLoader $loader */ public function __construct(AspectContainer $container, AspectLoader $loader) { $this->container = $container; $this->loader = $loader; } /** * Magic accessor * * @param string $name Key name * * @throws \InvalidArgumentException if referenced value is not an advisor * @return Advice */ public function __get($name) { if ($this->container->has($name)) { $advisor = $this->container->get($name); } else { list(, $advisorName) = explode('.', $name); list($aspect) = explode('->', $advisorName); $aspectInstance = $this->container->getAspect($aspect); $this->loader->loadAndRegister($aspectInstance); $advisor = $this->container->get($name); } if (!$advisor instanceof Advisor) { throw new \InvalidArgumentException("Reference {$name} is not an advisor"); } $this->$name = $advisor->getAdvice(); return $this->$name; } } src/Instrument/ClassLoading/AopComposerLoader.php000064400000011256144760116330016147 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\ClassLoading; use Go\Core\AspectContainer; use Go\Instrument\FileSystem\Enumerator; 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 */ class AopComposerLoader { /** * Instance of original autoloader * * @var ClassLoader */ protected $original = null; /** * AOP kernel options * * @var array */ protected $options = array(); /** * File enumerator * * @var Enumerator */ protected $fileEnumerator; /** * Cache state * * @var array */ private $cacheState; /** * Was initialization successful or not * * @var bool */ private static $wasInitialized = false; /** * Constructs an wrapper for the composer loader * * @param ClassLoader $original Instance of current loader * @param AspectContainer $container Instance of the container * @param array $options Configuration options */ public function __construct(ClassLoader $original, AspectContainer $container, array $options = array()) { $this->options = $options; $this->original = $original; $prefixes = $original->getPrefixes(); $excludePaths = $options['excludePaths']; // Let's exclude core dependencies from that list $excludePaths[] = $prefixes['Dissect'][0]; $excludePaths[] = $prefixes['TokenReflection'][0]; $excludePaths[] = substr($prefixes['Doctrine\\Common\\Annotations\\'][0], 0, -16); $fileEnumerator = new Enumerator($options['appDir'], $options['includePaths'], $excludePaths); $this->fileEnumerator = $fileEnumerator; $this->cacheState = $container->get('aspect.cache.path.manager')->queryCacheState(); } /** * Initialize aspect autoloader * * Replaces original composer autoloader with wrapper * * @param array $options Aspect kernel options * @param AspectContainer $container * * @return bool was initialization sucessful or not */ public static function init(array $options = array(), AspectContainer $container) { $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], $container, $options); self::$wasInitialized = true; } spl_autoload_unregister($loaderToUnregister); } unset($loader); foreach ($loaders as $loader) { spl_autoload_register($loader); } return self::$wasInitialized; } /** * Autoload a class by it's name */ public function loadClass($class) { $file = $this->findFile($class); if ($file) { include $file; } } /** * {@inheritDoc} */ public function findFile($class) { static $isAllowedFilter = null, $isProduction = false; if (!$isAllowedFilter) { $isAllowedFilter = $this->fileEnumerator->getFilter(); $isProduction = !$this->options['debug']; } $file = $this->original->findFile($class); if ($file) { $cacheState = isset($this->cacheState[$file]) ? $this->cacheState[$file] : null; if ($cacheState && $isProduction) { $file = $cacheState['cacheUri'] ?: $file; } elseif ($isAllowedFilter(new \SplFileInfo($file))) { // can be optimized here with $cacheState even for debug mode, but no needed right now $file = FilterInjectorTransformer::rewrite($file); } } return $file; } /** * Whether or not loader was initialized * * @return bool */ public static function wasInitialized() { return self::$wasInitialized; } } src/Instrument/ClassLoading/CachePathManager.php000064400000012270144760116330015701 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\ClassLoading; use Go\Aop\Features; use Go\Core\AspectKernel; /** * Class that manages real-code to cached-code paths mapping. * Can be extended to get a more sophisticated real-to-cached code mapping */ class CachePathManager { /** * Name of the file with cache paths */ const CACHE_FILE_NAME = '/_transformation.cache'; /** * @var array */ protected $options = array(); /** * @var \Go\Core\AspectKernel */ protected $kernel; /** * @var string|null */ protected $cacheDir; /** * @var string|null */ protected $appDir; /** * Cached metadata for transformation state for the concrete file * * @var array */ protected $cacheState = array(); /** * New metadata items, that was not present in $cacheState * * @var array */ protected $newCacheState = array(); public function __construct (AspectKernel $kernel) { $this->kernel = $kernel; $this->options = $kernel->getOptions(); $this->cacheDir = $this->options['cacheDir']; $this->appDir = $this->options['appDir']; if ($this->cacheDir) { if (!is_dir($this->cacheDir)) { $cacheRootDir = dirname($this->cacheDir); if (!is_writable($cacheRootDir) || !is_dir($cacheRootDir)) { throw new \InvalidArgumentException( "Can not create a directory {$this->cacheDir} for the cache. Parent directory {$cacheRootDir} is not writable or not exist."); } mkdir($this->cacheDir, 0770); } if (!$this->kernel->hasFeature(Features::PREBUILT_CACHE) && !is_writable($this->cacheDir)) { throw new \InvalidArgumentException("Cache directory {$this->cacheDir} is not writable"); } if (file_exists($this->cacheDir. self::CACHE_FILE_NAME)) { $this->cacheState = include $this->cacheDir . self::CACHE_FILE_NAME; } } } /** * Returns current cache directory for aspects, can be bull * * @return null|string */ public function getCacheDir() { return $this->cacheDir; } /** * Configures a new cache directory for aspects * * @param string $cacheDir New cache directory */ public function setCacheDir($cacheDir) { $this->cacheDir = $cacheDir; } /** * @param string $resource * @return bool|string */ public function getCachePathForResource($resource) { if (!$this->cacheDir) { return false; } return str_replace($this->appDir, $this->cacheDir, $resource); } /** * Tries to return an information for queried resource * * @param string|null $resource Name of the file or null to get all information * * @return array|null Information or null if no record in the cache */ public function queryCacheState($resource=null) { if (!$resource) { return $this->cacheState; } if (isset($this->newCacheState[$resource])) { return $this->newCacheState[$resource]; } if (isset($this->cacheState[$resource])) { return $this->cacheState[$resource]; } return null; } /** * Put a record about some resource in the cache * * This data will be persisted during object destruction * * @param string $resource Name of the file * @param array $metadata Miscellaneous information about resource */ public function setCacheState($resource, array $metadata) { $this->newCacheState[$resource] = $metadata; } /** * Automatic destructor saves all new changes into the cache * * This implementation is not thread-safe, so be care */ public function __destruct() { $this->flushCacheState(); } /** * Flushes the cache state into the file */ public function flushCacheState() { if ($this->newCacheState) { $fullCacheMap = $this->newCacheState + $this->cacheState; $cachePath = substr(var_export($this->cacheDir, true), 1, -1); $rootPath = substr(var_export($this->appDir, true), 1, -1); $cacheData = ' 'AOP_CACHE_DIR . \'', '\'' . $rootPath => 'AOP_ROOT_DIR . \'' )); file_put_contents($this->cacheDir . self::CACHE_FILE_NAME, $cacheData); if (function_exists('opcache_invalidate')) { opcache_invalidate($this->cacheDir . self::CACHE_FILE_NAME, true); } $this->cacheState = $this->newCacheState + $this->cacheState; $this->newCacheState = array(); } } } src/Instrument/ClassLoading/SourceTransformingLoader.php000064400000007053144760116330017552 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\ClassLoading; use php_user_filter as PhpStreamFilter; use Go\Instrument\Transformer\StreamMetaData; use Go\Instrument\Transformer\SourceTransformer; /** * Php class loader filter for processing php code */ class SourceTransformingLoader extends PhpStreamFilter { /** * 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 = ''; /** * 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 static 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 static 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; } if ($closing || feof($this->stream)) { $consumed = strlen($this->data); // $this->stream contains pointer to the source $metadata = new StreamMetaData($this->stream, $this->data); self::transformCode($metadata); $bucket = stream_bucket_new($this->stream, $metadata->source); stream_bucket_append($out, $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 static function addTransformer(SourceTransformer $transformer) { self::$transformers[] = $transformer; } /** * Transforms source code by passing it through all transformers * * @param StreamMetaData|null $metadata Metadata from stream * @return void|bool Return false if transformation should be stopped */ public static function transformCode(StreamMetaData $metadata) { $result = null; foreach (self::$transformers as $transformer) { $result = $transformer->transform($metadata); if ($result === false) { break; } } return $result; } } src/Instrument/CleanableMemory.php000064400000003144144760116330013262 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument; use TokenReflection\Broker\Backend\Memory; use TokenReflection\ReflectionFile; use TokenReflection\Stream\StreamBase; /** * Special memory backend that keep token stream only for one file */ class CleanableMemory extends Memory { /** * Nested level to track hierarchy * * @var int */ protected static $level = 0; /** * Increments current level */ public static function enterProcessing() { self::$level++; } /** * Decrements current level */ public static function leaveProcessing() { self::$level = self::$level > 0 ? self::$level-1 : 0; } /** * {@inheritDoc} */ public function addFile(StreamBase $tokenStream, ReflectionFile $file) { if (self::$level <= 1) { // Clean the old cache only for main classes, allow to bypass cleaning for hierarchy $this->clearTokenCache(); } return parent::addFile($tokenStream, $file); } /** * Clear token stream cache from parent object */ private function clearTokenCache() { static $tokenStreams = null; if (!$tokenStreams) { $tokenStreams = new \ReflectionProperty(get_parent_class(), 'tokenStreams'); $tokenStreams->setAccessible(true); } $tokenStreams->setValue($this, array()); } } src/Instrument/FileSystem/Enumerator.php000064400000006104144760116330014427 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\FileSystem; /** * Enumerates files in the concrete directory, applying filtration logic */ class Enumerator { /** * Path to the root directory, where enumeration should start * * @var string */ private $rootDirectory; /** * List of additional include paths, should be below rootDirectory * * @var array */ private $includePaths; /** * List of additional exclude paths, should be below rootDirectory * * @var array */ private $excludePaths; /** * Initializes an enumerator * * @param string $rootDirectory Path to the root directory * @param array $includePaths List of additional include paths * @param array $excludePaths List of additional exclude paths */ public function __construct($rootDirectory, array $includePaths = array(), array $excludePaths = array()) { $this->rootDirectory = $rootDirectory; $this->includePaths = $includePaths; $this->excludePaths = $excludePaths; } /** * Returns an enumerator for files * * @return \CallbackFilterIterator|\RecursiveIteratorIterator|\SplFileInfo[] */ public function enumerate() { $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $this->rootDirectory, \FilesystemIterator::SKIP_DOTS ) ); $callback = $this->getFilter(); $iterator = new \CallbackFilterIterator($iterator, $callback); return $iterator; } /** * Returns a filter callback for enumerating files * * @return callable */ public function getFilter() { $rootDirectory = $this->rootDirectory; $includePaths = $this->includePaths; $excludePaths = $this->excludePaths; return function (\SplFileInfo $file) use ($rootDirectory, $includePaths, $excludePaths) { if ($file->getExtension() !== 'php') { return false; }; $realPath = $file->getRealPath(); // Do not touch files that not under rootDirectory if (strpos($realPath, $rootDirectory) !== 0) { return false; } if ($includePaths) { $found = false; foreach ($includePaths as $includePath) { if (strpos($realPath, $includePath) === 0) { $found = true; break; } } if (!$found) { return false; } } foreach ($excludePaths as $excludePath) { if (strpos($realPath, $excludePath) === 0) { return false; } } return true; }; } } src/Instrument/PathResolver.php000064400000005156144760116330012646 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument; /** * Special class for resolving path for different file systems, wrappers, etc * * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath/4050444 * @see http://bugs.php.net/bug.php?id=52769 */ class PathResolver { /** * Custom replacement for realpath() and stream_resolve_include_path() * * @param string|array $somePath Path without normalization or array of paths * @param bool $shouldCheckExistence Flag for checking existence of resolved filename * * @return array|bool|string */ public static function realpath($somePath, $shouldCheckExistence = false) { // Do not resolve empty string/false/arrays into the current path if (!$somePath) { return $somePath; } if (is_array($somePath)) { return array_map(array(__CLASS__, __FUNCTION__), $somePath); } // Trick to get scheme name and path in one action. If no scheme, then there will be only one part $components = explode('://', $somePath, 2); list ($pathScheme, $path) = isset($components[1]) ? $components : array(null, $components[0]); // Optimization to bypass complex logic for simple paths (eg. not in phar archives) if (!$pathScheme && ($fastPath = stream_resolve_include_path($somePath))) { return $fastPath; } $isRelative = !$pathScheme && ($path[0] !== '/') && ($path[1] !== ':'); if ($isRelative) { $path = getcwd() . DIRECTORY_SEPARATOR . $path; } // resolve path parts (single dot, double dot and double delimiters) $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); if (strpos($path, '.') !== false) { $parts = explode(DIRECTORY_SEPARATOR, $path); $absolutes = array(); foreach ($parts as $part) { if ('.' == $part) { continue; } elseif ('..' == $part) { array_pop($absolutes); } else { $absolutes[] = $part; } } $path = implode(DIRECTORY_SEPARATOR, $absolutes); } if ($pathScheme) { $path = "{$pathScheme}://{$path}"; } if ($shouldCheckExistence && !file_exists($path)) { return false; } return $path; } } src/Instrument/RawAnnotationReader.php000064400000012123144760116330014127 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument; 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 ParsedReflectionClass $class The ReflectionClass of the class from which * the class annotations should be read. * @return array An array of Annotations. */ public function getClassAnnotations(ParsedReflectionClass $class) { $this->parser->setTarget(Target::TARGET_CLASS); return $this->parser->parse($class->getDocComment(), 'class '.$class->getName()); } /** * Gets the annotations applied to a method. * * @param ParsedReflectionMethod $method The ReflectionMethod of the method from which * the annotations should be read. * @return array An array of Annotations. */ public function getMethodAnnotations(ParsedReflectionMethod $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 ParsedReflectionProperty $property The ReflectionProperty of the property * from which the annotations should be read. * @return array An array of Annotations. */ public function getPropertyAnnotations(ParsedReflectionProperty $property) { $this->parser->setTarget(Target::TARGET_PROPERTY); return $this->parser->parse($property->getDocComment(), 'property '.$property->getDeclaringClass()->name.'::$'.$property->getName()); } /** * Gets a class annotation. * * @param 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(ParsedReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * Gets a method annotation. * * @param 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(ParsedReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * Gets a property annotation. * * @param 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(ParsedReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } } src/Instrument/Transformer/BaseSourceTransformer.php000064400000002162144760116330017002 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; use Go\Core\AspectKernel; use Go\Core\AspectContainer; /** * Base source transformer class definition */ 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/Instrument/Transformer/CachingTransformer.php000064400000010301144760116330016275 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; use Go\Core\AspectKernel; use Go\Instrument\ClassLoading\CachePathManager; /** * Caching transformer that is able to take the transformed source from a cache */ class CachingTransformer extends BaseSourceTransformer { /** * Mask of permission bits for cache files. * By default, permissions are affected by the umask system setting * * @var integer|null */ protected $cacheFileMode; /** * @var array|callable|SourceTransformer[] */ protected $transformers = array(); /** * @var CachePathManager|null */ protected $cacheManager; /** * Class constructor * * @param AspectKernel $kernel Instance of aspect kernel * @param array|callable $transformers Source transformers or callable that should return transformers * @param CachePathManager $cacheManager Cache manager */ public function __construct(AspectKernel $kernel, $transformers, CachePathManager $cacheManager) { parent::__construct($kernel); $this->cacheManager = $cacheManager; $this->cacheFileMode = (int) $this->options['cacheFileMode']; $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|bool Return false if transformation should be stopped */ public function transform(StreamMetaData $metadata) { // Do not create a cache if (!$this->cacheManager->getCacheDir()) { return $this->processTransformers($metadata); } $originalUri = $metadata->uri; $wasProcessed = false; $cacheUri = $this->cacheManager->getCachePathForResource($originalUri); // Guard to disable overwriting of original files if ($cacheUri === $originalUri) { return false; } $lastModified = filemtime($originalUri); $cacheState = $this->cacheManager->queryCacheState($originalUri); $cacheModified = $cacheState ? $cacheState['filemtime'] : 0; if ($cacheModified < $lastModified || !$this->container->isFresh($cacheModified)) { $wasProcessed = $this->processTransformers($metadata); if ($wasProcessed) { $parentCacheDir = dirname($cacheUri); if (!is_dir($parentCacheDir)) { mkdir($parentCacheDir, 0770, true); } file_put_contents($cacheUri, $metadata->source); if (!$cacheState && $this->cacheFileMode) { chmod($cacheUri, $this->cacheFileMode); } } $this->cacheManager->setCacheState($originalUri, array( 'filemtime' => isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time(), 'cacheUri' => $wasProcessed ? $cacheUri : null )); return $wasProcessed; } if ($cacheState) { $wasProcessed = isset($cacheState['cacheUri']); } if ($wasProcessed) { $metadata->source = file_get_contents($cacheUri); } return $wasProcessed; } /** * Iterates over transformers * * @param StreamMetaData $metadata Metadata for source code * @return bool False, if transformation should be stopped */ private function processTransformers(StreamMetaData $metadata) { if (is_callable($this->transformers)) { $delayedTransformers = $this->transformers; $this->transformers = $delayedTransformers(); } foreach ($this->transformers as $transformer) { $isTransformed = $transformer->transform($metadata); // transformer reported about termination, next transformers will be skipped if ($isTransformed === false) { return false; } } return true; } } src/Instrument/Transformer/ConstructorExecutionTransformer.php000064400000012511144760116330021157 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; use Go\Aop\Framework\ReflectionConstructorInvocation; use Go\Core\AspectContainer; /** * Transforms the source code to add an ability to intercept new instances creation * * @see https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y * * new_expr: * T_NEW * class_name_reference * ctor_arguments * * class_name_reference: * class_name * | new_variable * * class_name: * T_STATIC * | name * * namespace_name: * T_STRING * | namespace_name T_NS_SEPARATOR T_STRING * * name: * namespace_name * | T_NAMESPACE T_NS_SEPARATOR namespace_name * | T_NS_SEPARATOR namespace_name * * ctor_arguments: * / empty / * | argument_list */ class ConstructorExecutionTransformer implements SourceTransformer { /** * List of constructor invocations per class * * @var array|ReflectionConstructorInvocation[] */ private static $constructorInvocationsCache = array(); /** * Singletone * * @return static */ public static function getInstance() { static $instance = null; if (!$instance) { $instance = new static; } return $instance; } /** * Rewrites all "new" expressions with our implementation * * @param StreamMetaData $metadata Metadata for source * * @return void|bool Return false if transformation should be stopped */ public function transform(StreamMetaData $metadata = null) { if (strpos($metadata->source, 'new ')===false) { return; } $tokenStream = token_get_all($metadata->source); $transformedSource = ''; $isWaitingClass = false; $isWaitingEnd = false; $isClassName = true; $classNamePosition = 0; foreach ($tokenStream as $index=>$token) { list ($token, $tokenValue) = (array) $token + array(1 => $token); if ($isWaitingClass && $token !== T_WHITESPACE && $token !== T_COMMENT) { $classNamePosition++; if ($token === T_VARIABLE && $classNamePosition === 1) { $isWaitingEnd = true; $isWaitingClass = false; } if (in_array($tokenStream[$index+1], array('(', ';', ')', '.'))) { $isWaitingEnd = true; $isWaitingClass = false; } if ($isClassName && $token !== T_NS_SEPARATOR && $token !== T_STRING && $token !== T_STATIC) { $isClassName = false; } } if ($token === T_NEW) { $tokenValue = '\\' . __CLASS__ . '::getInstance()->{'; $isWaitingClass = true; $isClassName = true; $classNamePosition = 0; } $transformedSource .= $tokenValue; if ($isWaitingEnd) { $transformedSource .= $isClassName ? '::class' : ''; $transformedSource .= '}'; $isWaitingEnd = false; } } $metadata->source = $transformedSource; } /** * Magic interceptor for instance creation * * @param string $className Name of the class to construct * * @return object Instance of required object */ public function __get($className) { return static::construct($className); } /** * Magic interceptor for instance creation * * @param string $className Name of the class to construct * @param array $args Arguments for the constructor * * @return object Instance of required object */ public function __call($className, array $args) { return static::construct($className, $args); } /** * Default implementation for accessing joinpoint or creating a new one on-fly * * @param string $fullClassName Name of the class to create * @param array $arguments Arguments for constructor * * @return object */ protected static function construct($fullClassName, array $arguments = array()) { $fullClassName = ltrim($fullClassName, '\\'); if (!isset(self::$constructorInvocationsCache[$fullClassName])) { $invocation = null; $dynamicInit = AspectContainer::INIT_PREFIX . ':root'; try { $joinPointsRef = new \ReflectionProperty($fullClassName, '__joinPoints'); $joinPointsRef->setAccessible(true); $joinPoints = $joinPointsRef->getValue(); if (isset($joinPoints[$dynamicInit])) { $invocation = $joinPoints[$dynamicInit]; } } catch (\ReflectionException $e) { $invocation = null; } if (!$invocation) { $invocation = new ReflectionConstructorInvocation($fullClassName, 'root', array()); } self::$constructorInvocationsCache[$fullClassName] = $invocation; } return self::$constructorInvocationsCache[$fullClassName]->__invoke($arguments); } } src/Instrument/Transformer/FilterInjectorTransformer.php000064400000013234144760116330017674 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; use Go\Core\AspectKernel; use Go\Instrument\PathResolver; use Go\Instrument\ClassLoading\CachePathManager; /** * Transformer that injects source filter for "require" and "include" operations */ class FilterInjectorTransformer implements SourceTransformer { /** * Php filter definition */ const PHP_FILTER_READ = 'php://filter/read='; /** * Name of the filter to inject * * @var string */ protected static $filterName; /** * Kernel options * * @var array */ protected static $options = array(); /** * @var AspectKernel|null */ protected static $kernel; /** * @var CachePathManager|null */ protected static $cachePathManager; /** * Class constructor * * @param AspectKernel $kernel Kernel to take configuration from * @param string $filterName Name of the filter to inject * @param CachePathManager $cacheManager Manager for cache files */ public function __construct(AspectKernel $kernel, $filterName, CachePathManager $cacheManager) { self::configure($kernel, $filterName, $cacheManager); } /** * Static configurator for filter * * @param AspectKernel $kernel Kernel to use for configuration * @param string $filterName Name of the filter to inject * @param CachePathManager $cacheManager Cache manager */ protected static function configure(AspectKernel $kernel, $filterName, CachePathManager $cacheManager) { if (self::$kernel) { throw new \RuntimeException("Filter injector can be configured only once."); } self::$kernel = $kernel; self::$options = $kernel->getOptions(); self::$filterName = $filterName; self::$cachePathManager = $cacheManager; } /** * Replace source path with correct one * * This operation can check for cache, can rewrite paths, add additional filters and much more * * @param string $originalResource 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($originalResource, $originalDir = '') { static $appDir, $cacheDir, $debug; if (!$appDir) { extract(self::$options, EXTR_IF_EXISTS); } $resource = (string) $originalResource; if ($resource['0'] !== '/') { $shouldCheckExistence = true; $resource = PathResolver::realpath($resource, $shouldCheckExistence) ?: PathResolver::realpath("{$originalDir}/{$resource}", $shouldCheckExistence) ?: $originalResource; } $cachedResource = self::$cachePathManager->getCachePathForResource($resource); // If the cache is disabled or no cache yet, then use on-fly method if (!$cacheDir || $debug || !file_exists($cachedResource)) { return self::PHP_FILTER_READ . self::$filterName . "/resource=" . $resource; } return $cachedResource; } /** * Wrap all includes into rewrite filter * * @param StreamMetaData $metadata Metadata for source * @return void|bool Return false if transformation should be stopped */ 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/Instrument/Transformer/MagicConstantTransformer.php000064400000006737144760116330017515 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; use Go\Core\AspectKernel; /** * Transformer that replaces magic __DIR__ and __FILE__ constants in the source code * * Additionally, ReflectionClass->getFileName() is also wrapped into normalizer method call */ 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 = $this->options['appDir']; self::$rewriteToPath = $this->options['cacheDir']; } /** * This method may transform the supplied source and return a new replacement for it * * @param StreamMetaData $metadata Metadata for source * @return void|bool Return false if transformation should be stopped */ 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) { // 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( array(self::$rewriteToPath, DIRECTORY_SEPARATOR . '_proxies'), array(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/Instrument/Transformer/SourceTransformer.php000064400000001210144760116330016200 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; /** * General source transformer interface */ interface SourceTransformer { /** * This method may transform the supplied source and return a new replacement for it * * @param StreamMetaData $metadata Metadata for source * @return void|bool Return false if transformation should be stopped */ public function transform(StreamMetaData $metadata); } src/Instrument/Transformer/StreamMetaData.php000064400000004235144760116330015363 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; use ArrayObject; use Go\Instrument\PathResolver; 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'] = PathResolver::realpath($matches[1]); } parent::__construct($metadata, ArrayObject::ARRAY_AS_PROPS); } } src/Instrument/Transformer/WeavingTransformer.php000064400000026642144760116330016360 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Instrument\Transformer; use Go\Aop\Advisor; use Go\Aop\Features; use Go\Aop\Framework\AbstractJoinpoint; use Go\Core\AdviceMatcher; use Go\Core\AspectContainer; use Go\Core\AspectKernel; use Go\Core\AspectLoader; use Go\Instrument\ClassLoading\CachePathManager; use Go\Instrument\CleanableMemory; 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; /** * Main transformer that performs weaving of aspects into the source code */ class WeavingTransformer extends BaseSourceTransformer { /** * Reflection broker instance * * @var Broker */ protected $broker; /** * @var AdviceMatcher */ protected $adviceMatcher; /** * @var CachePathManager */ private $cachePathManager; /** * Instance of aspect loader * * @var AspectLoader */ protected $aspectLoader; /** * 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 * @param CachePathManager $cachePathManager Cache manager * @param AspectLoader $loader Loader for aspects */ public function __construct( AspectKernel $kernel, Broker $broker, AdviceMatcher $adviceMatcher, CachePathManager $cachePathManager, AspectLoader $loader ) { parent::__construct($kernel); $this->broker = $broker; $this->adviceMatcher = $adviceMatcher; $this->cachePathManager = $cachePathManager; $this->aspectLoader = $loader; } /** * This method may transform the supplied source and return a new replacement for it * * @param StreamMetaData $metadata Metadata for source * @return void|bool Return false if transformation should be stopped */ public function transform(StreamMetaData $metadata) { $totalTransformations = 0; $fileName = $metadata->uri; try { CleanableMemory::enterProcessing(); $parsedSource = $this->broker->processString($metadata->source, $fileName, true); } catch (FileProcessingException $e) { CleanableMemory::leaveProcessing(); return false; } // Check if we have some new aspects that weren't loaded yet $unloadedAspects = $this->aspectLoader->getUnloadedAspects(); if ($unloadedAspects) { $this->loadAndRegisterAspects($unloadedAspects); } $advisors = $this->container->getByTag('advisor'); /** @var $namespaces ParsedFileNamespace[] */ $namespaces = $parsedSource->getNamespaces(); $lineOffset = 0; foreach ($namespaces as $namespace) { /** @var $classes ParsedClass[] */ $classes = $namespace->getClasses(); foreach ($classes as $class) { $parentClassNames = array_merge( $class->getParentClassNameList(), $class->getInterfaceNames(), $class->getTraitNames() ); foreach ($parentClassNames as $parentClassName) { class_exists($parentClassName); // trigger autoloading of class/interface/trait } // Skip interfaces and aspects if ($class->isInterface() || in_array('Go\Aop\Aspect', $class->getInterfaceNames())) { continue; } $wasClassProcessed = $this->processSingleClass($advisors, $metadata, $class, $lineOffset); $totalTransformations += (integer) $wasClassProcessed; } $wasFunctionsProcessed = $this->processFunctions($advisors, $metadata, $namespace); $totalTransformations += (integer) $wasFunctionsProcessed; } CleanableMemory::leaveProcessing(); // If we return false this will indicate no more transformation for following transformers return $totalTransformations > 0; } /** * Performs weaving of single class if needed * * @param array|Advisor[] $advisors * @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 * * @return bool True if was class processed, false otherwise */ private function processSingleClass(array $advisors, StreamMetaData $metadata, ParsedClass $class, &$lineOffset) { $advices = $this->adviceMatcher->getAdvicesForClass($class, $advisors); if (!$advices) { // Fast return if there aren't any advices for that class return false; } // 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 $useStatic = $this->kernel->hasFeature(Features::USE_STATIC_FOR_LSB); $child = ($this->kernel->hasFeature(Features::USE_TRAIT) && $class->isTrait()) ? new TraitProxy($class, $advices, $useStatic) : new ClassProxy($class, $advices, $useStatic); // Set new parent name instead of original $child->setParentName($newParentName); $contentToInclude = $this->saveProxyToCache($class, $child); // 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 .= $contentToInclude . 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", $contentToInclude); $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); } return true; } /** * 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 = ($this->kernel->hasFeature(Features::USE_TRAIT) && $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; } /** * Performs weaving of functions in the current namespace * * @param array|Advisor[] $advisors List of advisors * @param StreamMetaData $metadata Source stream information * @param ParsedFileNamespace $namespace Current namespace for file * * @return boolean True if functions were processed, false otherwise */ private function processFunctions(array $advisors, StreamMetaData $metadata, $namespace) { static $cacheDirSuffix = '/_functions/'; $wasProcessedFunctions = false; $functionAdvices = $this->adviceMatcher->getAdvicesForFunctions($namespace, $advisors); $cacheDir = $this->cachePathManager->getCacheDir(); if ($functionAdvices && $cacheDir) { $cacheDir = $cacheDir . $cacheDirSuffix; $fileName = str_replace('\\', '/', $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 = new FunctionProxy($namespace, $functionAdvices); file_put_contents($functionFileName, $source); } $content = 'include_once AOP_CACHE_DIR . ' . var_export($cacheDirSuffix . $fileName, true) . ';' . PHP_EOL; $metadata->source .= $content; $wasProcessedFunctions = true; } return $wasProcessedFunctions; } /** * Save AOP proxy to the separate file anr returns the php source code for inclusion * * @param ParsedClass $class Original class reflection * @param string|ClassProxy $child * * @return string */ private function saveProxyToCache($class, $child) { static $cacheDirSuffix = '/_proxies/'; $cacheDir = $this->cachePathManager->getCacheDir(); // Without cache we should rewrite original file if (!$cacheDir) { return $child; } $cacheDir = $cacheDir . $cacheDirSuffix; $fileName = str_replace($this->options['appDir'] . DIRECTORY_SEPARATOR, '', $class->getFileName()); $proxyFileName = $cacheDir . $fileName; $dirname = dirname($proxyFileName); if (!file_exists($dirname)) { mkdir($dirname, 0770, true); } $body = 'getNamespaceName(); if ($namespace) { $body .= "namespace {$namespace};" . PHP_EOL . PHP_EOL; } foreach ($class->getNamespaceAliases() as $alias => $fqdn) { $body .= "use {$fqdn} as {$alias};" . PHP_EOL; } $body .= $child; file_put_contents($proxyFileName, $body); return 'include_once AOP_CACHE_DIR . ' . var_export($cacheDirSuffix . $fileName, true) . ';' . PHP_EOL; } /** * Utility method to load and register unloaded aspects * * @param array $unloadedAspects List of unloaded aspects */ private function loadAndRegisterAspects(array $unloadedAspects) { foreach ($unloadedAspects as $unloadedAspect) { $this->aspectLoader->loadAndRegister($unloadedAspect); } } } src/Lang/Annotation/After.php000064400000000605144760116330012106 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Lang\Annotation; /** * After advice annotation * * @Annotation * @Target({ "METHOD", "PROPERTY" }) */ class After extends BaseInterceptor { } src/Lang/Annotation/AfterThrowing.php000064400000000606144760116330013631 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Lang\Annotation; /** * After throwing advice annotation * * @Annotation * @Target("METHOD") */ class AfterThrowing extends BaseInterceptor { } src/Lang/Annotation/Around.php000064400000000607144760116330012277 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Lang\Annotation; /** * Around advice annotation * * @Annotation * @Target({ "METHOD", "PROPERTY" }) */ class Around extends BaseInterceptor { } src/Lang/Annotation/Aspect.php000064400000000577144760116330012274 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Lang\Annotation; /** * Annotation marker class for aspect * * @Annotation * @Target("CLASS") */ class Aspect extends BaseAnnotation { } src/Lang/Annotation/BaseAnnotation.php000064400000002617144760116330013757 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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 */ final public 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/Lang/Annotation/BaseInterceptor.php000064400000001102144760116330014127 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Lang/Annotation/Before.php000064400000000607144760116330012251 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Lang\Annotation; /** * Before advice annotation * * @Annotation * @Target({ "METHOD", "PROPERTY" }) */ class Before extends BaseInterceptor { } src/Lang/Annotation/DeclareError.php000064400000001147144760116330013420 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Lang/Annotation/DeclareParents.php000064400000001415144760116330013741 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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/Lang/Annotation/Interceptor.php000064400000000506144760116330013343 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Lang\Annotation; /** * Interceptor annotation marker */ interface Interceptor { } src/Lang/Annotation/Pointcut.php000064400000000706144760116330012654 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Lang\Annotation; /** * Pointcut annotation * * @Annotation * @Target("METHOD") * * @Attributes({ * @Attribute("value", type = "string", required=true) * }) */ class Pointcut extends BaseAnnotation { } src/Proxy/AbstractProxy.php000064400000011144144760116330012000 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Proxy; use ReflectionParameter as Parameter; use TokenReflection\ReflectionParameter as ParsedParameter; /** * Abstract class for building different proxies */ abstract class AbstractProxy { /** * Indent for source code * * @var int */ protected $indent = 4; /** * List of advices that are used for generation of child * * @var array */ protected $advices = array(); /** * PHP expression string for accessing LSB information * * @var string */ protected $staticLsbExpression = '\get_called_class()'; /** * Should proxy use variadics support or not * * @var bool */ protected $useVariadics = false; /** * Constructs an abstract proxy class * * @param array $advices List of advices * @param bool $useStaticForLsb Should proxy use 'static::class' instead of '\get_called_class()' */ public function __construct(array $advices = array(), $useStaticForLsb = false, $useVariadics = false) { $this->advices = $this->flattenAdvices($advices); $this->useVariadics = $useVariadics; if ($useStaticForLsb) { $this->staticLsbExpression = 'static::class'; } } /** * Returns text representation of class * * @return string */ abstract public function __toString(); /** * 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) { // Deprecated since PHP5.6 in the favor of variadics, needed for BC only 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->isCallable()) { $type = 'callable'; } 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(), true); } } elseif ($parameter->isOptional()) { $defaultValue = 'null'; } $code = ( ($type ? "$type " : '') . // Typehint ($parameter->isPassedByReference() ? '&' : '') . // By reference sign ($this->useVariadics && $parameter->isVariadic() ? '...' : '') . // Variadic symbol '$' . // Variable symbol ($parameter->name) . // Name of the argument ($defaultValue !== null ? (" = " . $defaultValue) : '') // Default value if present ); return $code; } /** * Replace concrete advices with list of ids * * @param $advices * * @return array flatten list of advices */ private function flattenAdvices($advices) { $flattenAdvices = array(); foreach ($advices as $type => $typedAdvices) { foreach ($typedAdvices as $name => $concreteAdvices) { if (is_array($concreteAdvices)) { $flattenAdvices[$type][$name] = array_keys($concreteAdvices); } } } return $flattenAdvices; } } src/Proxy/ClassProxy.php000064400000051241144760116330011304 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Proxy; use Go\Aop\Advice; use Go\Aop\Features; use Go\Aop\Framework\MethodInvocationComposer; use Go\Aop\Intercept\Joinpoint; use Go\Aop\IntroductionInfo; use Go\Core\AspectContainer; use Go\Core\AspectKernel; use Go\Core\LazyAdvisorAccessor; use Reflection; use ReflectionClass; use ReflectionMethod as Method; use ReflectionProperty as Property; use TokenReflection\ReflectionClass as ParsedClass; use TokenReflection\ReflectionMethod as ParsedMethod; use TokenReflection\ReflectionParameter as ParsedParameter; use TokenReflection\ReflectionProperty as ParsedProperty; /** * Class proxy builder that is used to generate a child class from the list of joinpoints */ class ClassProxy extends AbstractProxy { /** * Parent class reflection * * @var null|ParsedClass */ protected $class = null; /** * 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(); /** * Static mappings for class name for excluding if..else check * * @var null|array */ protected static $invocationClassMap = null; /** * 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(); /** * Name for the current class * * @var string */ protected $name = ''; /** * 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 ParsedClass $parent Parent class reflection * @param array|Advice[] $classAdvices List of advices for class * @param bool $useStaticForLsb Should proxy use 'static::class' instead of '\get_called_class()' * * @throws \InvalidArgumentException if there are unknown type of advices * @return ClassProxy */ public function __construct(ParsedClass $parent, array $classAdvices, $useStaticForLsb = false) { parent::__construct($classAdvices, $useStaticForLsb); $this->class = $parent; $this->name = $parent->getShortName(); $this->parentClassName = $parent->getShortName(); $this->addInterface('\Go\Aop\Proxy'); $this->addJoinpointsProperty(); foreach ($classAdvices as $type => $typedAdvices) { switch ($type) { case AspectContainer::METHOD_PREFIX: case AspectContainer::STATIC_METHOD_PREFIX: foreach ($typedAdvices as $joinPointName => $advice) { $method = $parent->getMethod($joinPointName); if (!$method instanceof ParsedMethod) { continue; } $this->overrideMethod($method); } break; case AspectContainer::PROPERTY_PREFIX: foreach ($typedAdvices as $joinPointName => $advice) { $property = $parent->getProperty($joinPointName); if (!$property instanceof ParsedProperty) { continue; } $this->interceptProperty($property); } break; case AspectContainer::INTRODUCTION_TRAIT_PREFIX: foreach ($typedAdvices as $advice) { /** @var $advice IntroductionInfo */ foreach ($advice->getInterfaces() as $interface) { $this->addInterface($interface); } foreach ($advice->getTraits() as $trait) { $this->addTrait($trait); } } break; case AspectContainer::INIT_PREFIX: case AspectContainer::STATIC_INIT_PREFIX: break; // No changes for class default: throw new \InvalidArgumentException("Unsupported point `$type`"); } } } /** * Updates parent name for child * * @param string $newParentName New class name * * @return static */ 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 static */ 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 bool $byReference Is method should return value by reference * @param string $body Body of method * @param string $parameters Definition of parameters * * @return static */ public function setMethod($methodFlags, $methodName, $byReference, $body, $parameters) { $this->methodsCode[$methodName] = ( "/**\n * Method was created automatically, do not change it manually\n */\n" . join(' ', Reflection::getModifierNames($methodFlags)) . // List of method modifiers ' function ' . // 'function' keyword ($byReference ? '&' : '') . // Return value by reference $methodName . // Method name '(' . // Start of parameter list $parameters . // List of parameters ")\n" . // End of parameter list "{\n" . // Start of method body $this->indent($body) . "\n" . // Method body "}\n" // End of method body ); return $this; } /** * 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()) { $reflectionClass = new ReflectionClass($className); $joinPoints = static::wrapWithJoinPoints($advices, $reflectionClass->getParentClass()->name); $prop = $reflectionClass->getProperty('__joinPoints'); $prop->setAccessible(true); $prop->setValue($joinPoints); $staticInit = AspectContainer::STATIC_INIT_PREFIX . ':root'; if (isset($joinPoints[$staticInit])) { $joinPoints[$staticInit]->__invoke(); } } /** * Initialize static mappings to reduce the time for checking features * * @param bool $useClosureBinding Enables usage of closures instead of reflection * @param bool $useSplatOperator Enables usage of optimized invocation with splat operator */ protected static function setMappings($useClosureBinding, $useSplatOperator) { $dynamicMethodClass = MethodInvocationComposer::compose(false, $useClosureBinding, $useSplatOperator, false); $staticMethodClass = MethodInvocationComposer::compose(true, $useClosureBinding, $useSplatOperator, false); // We are using LSB here and overridden static property static::$invocationClassMap = array( AspectContainer::METHOD_PREFIX => $dynamicMethodClass, AspectContainer::STATIC_METHOD_PREFIX => $staticMethodClass, AspectContainer::PROPERTY_PREFIX => 'Go\Aop\Framework\ClassFieldAccess', AspectContainer::STATIC_INIT_PREFIX => 'Go\Aop\Framework\StaticInitializationJoinpoint', AspectContainer::INIT_PREFIX => 'Go\Aop\Framework\ReflectionConstructorInvocation' ); } /** * 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 * * NB: 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) { /** @var LazyAdvisorAccessor $accessor */ static $accessor = null; if (!self::$invocationClassMap) { $aspectKernel = AspectKernel::getInstance(); $accessor = $aspectKernel->getContainer()->get('aspect.advisor.accessor'); self::setMappings( $aspectKernel->hasFeature(Features::USE_CLOSURE), $aspectKernel->hasFeature(Features::USE_SPLAT_OPERATOR) ); } $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) { $filledAdvices = array(); foreach ($advices as $advisorName) { $filledAdvices[] = $accessor->$advisorName; } $joinpoint = new self::$invocationClassMap[$joinPointType]($className, $joinPointName, $filledAdvices); $joinPoints["$joinPointType:$joinPointName"] = $joinpoint; } } return $joinPoints; } /** * 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 static */ public function setProperty($propFlags, $propName, $defaultText = null) { $this->propertiesCode[$propName] = ( "/**\n * Property was created automatically, do not change it manually\n */\n" . // Doc-block join(' ', Reflection::getModifierNames($propFlags)) . // List of modifiers for property ' $' . // Space and vaiable symbol $propName . // Name of the property (is_string($defaultText) ? " = $defaultText" : '') . // Default value if present ";\n" // End of line with property definition ); return $this; } /** * 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 ParsedMethod $method Method reflection */ protected function overrideMethod(ParsedMethod $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 ParsedMethod $method Method reflection * * @return string new method body */ protected function getJoinpointInvocationBody(ParsedMethod $method) { $isStatic = $method->isStatic(); $scope = $isStatic ? $this->staticLsbExpression : '$this'; $prefix = $isStatic ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX; $args = join(', ', array_map(function (ParsedParameter $param) { $byReference = $param->isPassedByReference() ? '&' : ''; return $byReference . '$' . $param->name; }, $method->getParameters())); $body = ''; if (($this->class->name === $method->getDeclaringClassName()) && 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 ParsedProperty $property Reflection of property to intercept */ protected function interceptProperty(ParsedProperty $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); } $prefix = join(' ', Reflection::getModifierNames($this->class->getModifiers())); $classCode = ( $this->class->getDocComment() . "\n" . // Original doc-block ($prefix ? "$prefix " : '') . // List of class modifiers 'class ' . // 'class' keyword with one space $this->name . // Name of the class ' extends '. // 'extends' keyword with $this->parentClassName . // Name of the parent class ($this->interfaces ? ' implements ' . join(', ', $this->interfaces) : '') . "\n" . // Interfaces list "{\n" . // Start of class definition ($this->traits ? $this->indent('use ' . join(', ', $this->traits) .';'."\n") : '') . "\n" . // Traits list $this->indent(join("\n", $this->propertiesCode)) . "\n" . // Property definitions $this->indent(join("\n", $this->methodsCode)) . "\n" . // Method definitions "}" // End of class definition ); return $classCode // Inject advices on call . PHP_EOL . '\\' . __CLASS__ . "::injectJoinPoints('" . $this->class->name . "'," . var_export($this->advices, true) . ");"; } /** * Add code for intercepting properties * * @param null|ParsedMethod $constructor Constructor reflection or null */ protected function addFieldInterceptorsCode(ParsedMethod $constructor = null) { $byReference = false; $this->setProperty(Property::IS_PRIVATE, '__properties', 'array()'); $this->setMethod(Method::IS_PUBLIC, '__get', $byReference, $this->getMagicGetterBody(), '$name'); $this->setMethod(Method::IS_PUBLIC, '__set', $byReference, $this->getMagicSetterBody(), '$name, $value'); $this->isFieldsIntercepted = true; if ($constructor) { $this->override('__construct', $this->getConstructorBody($constructor, true)); } else { $this->setMethod(Method::IS_PUBLIC, '__construct', $byReference, $this->getConstructorBody(), ''); } } /** * Creates a method code from Reflection * * @param ParsedMethod $method Reflection for method * @param string $body Body of method * * @return string */ protected function getOverriddenMethod(ParsedMethod $method, $body) { $code = ( preg_replace('/ {4}|\t/', '', $method->getDocComment()) . "\n" . // Original Doc-block join(' ', Reflection::getModifierNames($method->getModifiers())) . // List of modifiers ' function ' . // 'function' keyword ($method->returnsReference() ? '&' : '') . // By reference symbol $method->name . // Name of the method '(' . // Start of parameters list join(', ', $this->getParameters($method->getParameters())) . // List of parameters ")\n" . // End of parameters list "{\n" . // Start of method body $this->indent($body) . "\n" . // Method body "}\n" // End of method body ); return $code; } /** * 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 $constructor Constructor reflection * @param bool $isCallParent Is there is a need to call parent code * * @return string */ private function getConstructorBody(ParsedMethod $constructor = null, $isCallParent = false) { $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 ($constructor) { $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/Proxy/FunctionProxy.php000064400000016055144760116330012030 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Proxy; use Go\Aop\Advice; use Go\Aop\Framework\ReflectionFunctionInvocation; use Go\Aop\Intercept\FunctionInvocation; use Go\Core\AspectContainer; use Go\Core\AspectKernel; use Go\Core\LazyAdvisorAccessor; use ReflectionFunction; use ReflectionParameter; use TokenReflection\ReflectionFileNamespace; /** * Function proxy builder that is used to generate a proxy-function from the list of joinpoints */ class FunctionProxy extends AbstractProxy { /** * List of advices for functions * * @var array */ protected static $functionAdvices = array(); /** * 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(); /** * 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 */ public function __construct(ReflectionFileNamespace $namespace, array $advices = array()) { parent::__construct($advices); $this->namespace = $namespace; if (empty($advices[AspectContainer::FUNCTION_PREFIX])) { return; } foreach ($advices[AspectContainer::FUNCTION_PREFIX] as $pointName => $value) { $function = new ReflectionFunction($pointName); $this->override($function, $this->getJoinpointInvocationBody($function)); } } /** * 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) { /** @var LazyAdvisorAccessor $accessor */ static $accessor = null; if (!$accessor) { $accessor = AspectKernel::getInstance()->getContainer()->get('aspect.advisor.accessor'); } $advices = self::$functionAdvices[$namespace][AspectContainer::FUNCTION_PREFIX][$joinPointName]; $filledAdvices = array(); foreach ($advices as $advisorName) { $filledAdvices[] = $accessor->$advisorName; } return new ReflectionFunctionInvocation($joinPointName, $filledAdvices); } /** * 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 $function Function reflection * @param string $body New body for function * * @return $this */ public function override(ReflectionFunction $function, $body) { $this->functionsCode[$function->name] = $this->getOverriddenFunction($function, $body); return $this; } /** * {@inheritDoc} */ public function __toString() { $functionsCode = ( "namespace->getDocComment() . "\n" . // Doc-comment for file 'namespace ' . // 'namespace' keyword $this->namespace->getName() . // Name ";\n" . // End of namespace name join("\n", $this->functionsCode) // Function definitions ); return $functionsCode // Inject advices on call . PHP_EOL . '\\' . __CLASS__ . "::injectJoinPoints('" . $this->namespace->getName() . "'," . var_export($this->advices, true) . ");"; } /** * Creates a function code from Reflection * * @param ReflectionFunction $function Reflection for function * @param string $body Body of function * * @return string */ protected function getOverriddenFunction(ReflectionFunction $function, $body) { static $inMemoryCache = array(); $functionName = $function->getName(); if (isset($inMemoryCache[$functionName])) { return $inMemoryCache[$functionName]; } $code = ( preg_replace('/ {4}|\t/', '', $function->getDocComment()) ."\n" . // Original doc-comment 'function ' . // 'function' keyword ($function->returnsReference() ? '&' : '') . // By reference symbol $functionName . // Function name '(' . // Start of parameters join(', ', $this->getParameters($function->getParameters())) . // List of parameters ")\n" . // End of parameters "{\n" . // Start of function body $this->indent($body) . "\n" . // Body of function "}\n" // End of function body ); $inMemoryCache[$functionName] = $code; return $code; } /** * Creates definition for trait method body * * @param ReflectionFunction $function Method reflection * * @return string new method body */ protected function getJoinpointInvocationBody(ReflectionFunction $function) { $class = '\\' . __CLASS__; $dynamicArgs = false; $hasOptionals = false; $hasReferences = false; $argValues = array_map(function (ReflectionParameter $param) use (&$dynamicArgs, &$hasOptionals, &$hasReferences) { $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; } } src/Proxy/TraitProxy.php000064400000010770144760116330011324 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Proxy; use Go\Aop\Features; use Go\Core\AspectContainer; use Go\Core\AspectKernel; use Go\Core\LazyAdvisorAccessor; use TokenReflection\ReflectionMethod as ParsedMethod; use TokenReflection\ReflectionParameter as ParsedParameter; /** * Trait proxy builder that is used to generate a trait from the list of joinpoints */ class TraitProxy extends ClassProxy { /** * List of advices for traits * * @var array */ protected static $traitAdvices = array(); /** * Overridden static property for TraitProxy * * {@inheritDoc} */ protected static $invocationClassMap = array(); /** * 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) { /** @var LazyAdvisorAccessor $accessor */ static $accessor = null; if (!self::$invocationClassMap) { $aspectKernel = AspectKernel::getInstance(); $accessor = $aspectKernel->getContainer()->get('aspect.advisor.accessor'); self::setMappings( $aspectKernel->hasFeature(Features::USE_CLOSURE), $aspectKernel->hasFeature(Features::USE_SPLAT_OPERATOR) ); } $advices = self::$traitAdvices[$traitName][$joinPointType][$pointName]; $filledAdvices = array(); foreach ($advices as $advisorName) { $filledAdvices[] = $accessor->$advisorName; } $joinpoint = new self::$invocationClassMap[$joinPointType]($className, $pointName . '➩', $filledAdvices); return $joinpoint; } /** * Creates definition for trait method body * * @param ParsedMethod $method Method reflection * * @return string new method body */ protected function getJoinpointInvocationBody(ParsedMethod $method) { $isStatic = $method->isStatic(); $class = '\\' . __CLASS__; $scope = $isStatic ? $this->staticLsbExpression : '$this'; $prefix = $isStatic ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX; $args = join(', ', array_map(function (ParsedParameter $param) { $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() { $classCode = ( $this->class->getDocComment() . "\n" . // Original doc-block 'trait ' . // 'trait' keyword $this->name . "\n" . // Name of the trait "{\n" . // Start of trait body $this->indent( 'use ' . join(', ', array(-1 => $this->parentClassName) + $this->traits) . $this->getMethodAliasesCode() ) . "\n" . // Use traits and aliases section $this->indent(join("\n", $this->methodsCode)) . "\n". // Method definitions "}" // End of trait body ); return $classCode // Inject advices on call . PHP_EOL . '\\' . __CLASS__ . "::injectJoinPoints('" . $this->class->name . "'," . var_export($this->advices, 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/AbstractInterceptorTest.php000064400000003433144760116330016272 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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.php000064400000007751144760116330015754 0ustar00assertInstanceOf($expected, $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/AbstractMethodInvocationTest.php000064400000001431144760116330017242 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()); } } tests/Go/Aop/Framework/AfterInterceptorTest.php000064400000002657144760116330015577 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; class AfterInterceptorTest extends AbstractInterceptorTest { public function testAdviceIsCalledAfterInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new AfterInterceptor($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 AfterInterceptor($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/AfterThrowingInterceptorTest.php000064400000002646144760116330017317 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; class AfterThrowingInterceptorTest extends AbstractInterceptorTest { public function testAdviceIsNotCalledAfterInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new AfterThrowingInterceptor($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 AfterThrowingInterceptor($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/AroundInterceptorTest.php000064400000003126144760116330015756 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Invocation; class AroundInterceptorTest extends AbstractInterceptorTest { public function testInvocationIsNotCalledWithoutProceed() { $sequence = array(); $advice = $this->getAdvice($sequence); // advice will not call Invocation->proceed() $invocation = $this->getInvocation($sequence); $interceptor = new AroundInterceptor($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 (Invocation $invocation) use (&$sequence) { $sequence[] = 'advice'; $result = $invocation->proceed(); $sequence[] = 'advice'; return $result; }; $invocation = $this->getInvocation($sequence); $interceptor = new AroundInterceptor($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/BaseInterceptorTest.php000064400000004150144760116330015376 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; use Go\Aop\Intercept\Invocation; use Go\Stubs\BaseInterceptorMock; class BaseInterceptorTest extends AbstractInterceptorTest { /** * Concrete class name for mock, should be redefined with LSB */ const INVOCATION_CLASS = 'Go\Aop\Intercept\Invocation'; public function testReturnsRawAdvice() { $sequence = array(); $advice = $this->getAdvice($sequence); $interceptor = $this->getMockForAbstractClass( 'Go\Aop\Framework\BaseInterceptor', array($advice) ); $this->assertEquals($advice, $interceptor->getRawAdvice()); } public function testCanSerializeInterceptor() { $sequence = array(); $advice = $this->getAdvice($sequence); $mock = new BaseInterceptorMock($advice); $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 = new BaseInterceptorMock($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/BeforeInterceptorTest.php000064400000001547144760116330015735 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Framework; class BeforeInterceptorTest extends AbstractInterceptorTest { public function testAdviceIsCalledBeforeInvocation() { $sequence = array(); $advice = $this->getAdvice($sequence); $invocation = $this->getInvocation($sequence); $interceptor = new BeforeInterceptor($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/DynamicClosureMethodInvocationTest.php000064400000007530144760116330020426 0ustar00markTestSkipped("Closure Method Invocation works only on PHP 5.4 and greater"); } if (defined('HHVM_VERSION')) { $this->markTestSkipped("Skipped due to the bug https://github.com/facebook/hhvm/issues/1203"); } } /** * Tests dynamic method invocations * * @dataProvider dynamicMethodsBatch */ public function testDynamicMethodInvocation($methodName, $expectedResult) { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new self::$invocationClass(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 self::$invocationClass(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 self::$invocationClass(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 self::$invocationClass(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\Interceptor'); $advice->expects($this->once()) ->method('invoke') ->will($this->returnCallback(function (MethodInvocation $object) use (&$value) { $value = 'ok'; return $object->proceed(); })); $invocation = new self::$invocationClass(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/DynamicClosureSplatMethodInvocationTest.php000064400000007713144760116330021435 0ustar00= 50600) { self::$invocationClass = MethodInvocationComposer::compose(false, true, true, false); } } /** * {@inheritdoc} */ protected function setUp() { if (PHP_VERSION_ID < 50600) { $this->markTestSkipped("Closure Method Invocation with splat works only on PHP 5.6 and greater"); } if (defined('HHVM_VERSION')) { $this->markTestSkipped("Skipped due to the bug https://github.com/facebook/hhvm/issues/1203"); } } /** * Tests dynamic method invocations * * @dataProvider dynamicMethodsBatch */ public function testDynamicMethodInvocation($methodName, $expectedResult) { $child = $this->getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new self::$invocationClass(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 self::$invocationClass(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 self::$invocationClass(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 self::$invocationClass(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\Interceptor'); $advice->expects($this->once()) ->method('invoke') ->will($this->returnCallback(function (MethodInvocation $object) use (&$value) { $value = 'ok'; return $object->proceed(); })); $invocation = new self::$invocationClass(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/DynamicReflectionMethodInvocationTest.php000064400000011363144760116330021103 0ustar00getMock(self::FIRST_CLASS_NAME, array('none')); $invocation = new self::$invocationClass(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 self::$invocationClass($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 self::$invocationClass(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('none')); $invocation = new self::$invocationClass($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 self::$invocationClass(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 self::$invocationClass(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 BeforeInterceptor(function ($object) use (&$value) { $value = 'ok'; }); $invocation = new self::$invocationClass(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/Framework/StaticClosureMethodInvocationTest.php000064400000011037144760116330020266 0ustar00markTestSkipped("Closure Method Invocation works only on PHP 5.4 and greater"); } if (defined('HHVM_VERSION')) { $this->markTestSkipped("Skipped due to the bug https://github.com/facebook/hhvm/issues/1203"); } } /** * Tests static method invocations with self * * @dataProvider staticSelfMethodsBatch */ public function testStaticSelfMethodInvocation($methodName, $expectedResult) { $childClass = $this->getMockClass(self::FIRST_CLASS_NAME, array('none')); $invocation = new self::$invocationClass($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 self::$invocationClass(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 self::$invocationClass(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 self::$invocationClass(self::FIRST_CLASS_NAME, 'staticPassByReference', array()); $value = 'test'; $result = $invocation($child, array(&$value)); $this->assertEquals(null, $result); $this->assertEquals(null, $value); } public function testRecursionWorks() { $invocation = new self::$invocationClass(self::FIRST_CLASS_NAME, 'staticLsbRecursion', array()); $child = new FirstStatic($invocation); $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 BeforeInterceptor(function () use (&$value) { $value = 'ok'; }); $invocation = new self::$invocationClass(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 self::$invocationClass(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/Pointcut/PointcutParserTest.php000064400000007730144760116330015146 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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(public Example->method|method1|method2(*))'), array('execution(final public Example\Aspect\*->method*(*))'), array('execution(protected|public **::*someStatic*Method*(*))'), // This will match property that has First\Second\Annotation\Class annotation array('@access(First\Second\Annotation\Class)'), // This will match method execution that has First\Second\Annotation\Class annotation array('@execution(First\Second\Annotation\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+)'), // This will match all the methods in the class with specific annotation. array('@within(First\Second\Annotation\Class)'), // Access pointcuts array('access(public|protected 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(**\*(*))'), // Dynamic pointcut for methods via __callStatic and __call array('dynamic(public Demo\Example\DynamicMethodsDemo::find*(*))'), array('dynamic(public Demo\Example\DynamicMethodsDemo->save*(*))'), // This will match static initialization pointcut array('staticinitialization(Some\Specific\Class\**)'), // This will match dynamic initialization pointcut array('initialization(Some\Specific\Class\**)'), ); } }tests/Go/Aop/Pointcut/SignaturePointcutTest.php000064400000007446144760116330015657 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Pointcut; use Go\Aop\PointFilter; use Go\Aop\Support\NotPointFilter; use Go\Aop\Support\TruePointFilter; class SignaturePointcutTest extends \PHPUnit_Framework_TestCase { const STUB_CLASS = 'Go\Stubs\First'; /** * Tests that method matched by name directly */ public function testDirectMethodMatchByName() { $filterKind = PointFilter::KIND_METHOD; $pointcut = new SignaturePointcut($filterKind, 'publicMethod', TruePointFilter::getInstance()); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'publicMethod')); $this->assertTrue($matched, "Pointcut should match this method"); } /** * Tests that pointcut can match property */ public function testCanMatchProperty() { $filterKind = PointFilter::KIND_METHOD; $pointcut = new SignaturePointcut($filterKind, 'public', TruePointFilter::getInstance()); $matched = $pointcut->matches(new \ReflectionProperty(self::STUB_CLASS, 'public')); $this->assertTrue($matched, "Pointcut should match this property"); } /** * Tests that pointcut won't match if modifier filter is not match */ public function testWontMatchModifier() { $trueInstance = TruePointFilter::getInstance(); $notInstance = new NotPointFilter($trueInstance); $filterKind = PointFilter::KIND_METHOD; $pointcut = new SignaturePointcut($filterKind, 'publicMethod', $notInstance); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'publicMethod')); $this->assertFalse($matched, "Pointcut should not match modifier"); } /** * Tests that pattern is working correctly */ public function testRegularPattern() { $filterKind = PointFilter::KIND_METHOD; $pointcut = new SignaturePointcut($filterKind, '*Method', TruePointFilter::getInstance()); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'publicMethod')); $this->assertTrue($matched, "Pointcut should match this method"); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'protectedMethod')); $this->assertTrue($matched, "Pointcut should match this method"); } /** * Tests that multiple pattern is matching */ public function testMultipleRegularPattern() { $filterKind = PointFilter::KIND_METHOD; $pointcut = new SignaturePointcut($filterKind, 'publicMethod|protectedMethod', TruePointFilter::getInstance()); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'publicMethod')); $this->assertTrue($matched, "Pointcut should match this method"); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'protectedMethod')); $this->assertTrue($matched, "Pointcut should match this method"); } /** * Tests that multiple pattern is using strict matching * * @link https://github.com/lisachenko/go-aop-php/issues/115 */ public function testIssue115() { $filterKind = PointFilter::KIND_METHOD; $pointcut = new SignaturePointcut($filterKind, 'public|Public', TruePointFilter::getInstance()); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'publicMethod')); $this->assertFalse($matched, "Pointcut should match strict"); $matched = $pointcut->matches(new \ReflectionMethod(self::STUB_CLASS, 'staticLsbPublic')); $this->assertFalse($matched, "Pointcut should match strict"); } }tests/Go/Aop/Support/AndPointFilterTest.php000064400000003320144760116330014704 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\PointFilter; class AndPointFilterTest extends \PHPUnit_Framework_TestCase { /** * Tests that filter intersect different kinds of filters */ public function testKindIsIntersected() { $first = $this->getMock('Go\Aop\PointFilter'); $first ->expects($this->any()) ->method('getKind') ->will($this->returnValue(PointFilter::KIND_METHOD | PointFilter::KIND_PROPERTY)); $second = $this->getMock('Go\Aop\PointFilter'); $second ->expects($this->any()) ->method('getKind') ->will($this->returnValue(PointFilter::KIND_METHOD | PointFilter::KIND_FUNCTION)); $filter = new AndPointFilter($first, $second); $this->assertEquals(PointFilter::KIND_METHOD, $filter->getKind()); } /** * @dataProvider logicCases */ public function testMatches(PointFilter $first, PointFilter $second, $expected) { $filter = new AndPointFilter($first, $second); $result = $filter->matches(new \ReflectionClass(__CLASS__) /* anything */); $this->assertSame($expected, $result); } public function logicCases() { $true = TruePointFilter::getInstance(); $false = new NotPointFilter($true); return array( array($false, $false, false), array($false, $true, false), array($true, $false, false), array($true, $true, true) ); } } tests/Go/Aop/Support/NotPointFilterTest.php000064400000001562144760116330014750 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\PointFilter; class NotPointFilterTest extends \PHPUnit_Framework_TestCase { /** * @dataProvider logicCases */ public function testMatches(PointFilter $first, $expected) { $filter = new NotPointFilter($first); $result = $filter->matches(new \ReflectionClass(__CLASS__) /* anything */); $this->assertSame($expected, $result); } public function logicCases() { $true = TruePointFilter::getInstance(); $false = new NotPointFilter($true); return array( array($false, true), array($true, false) ); } } tests/Go/Aop/Support/OrPointFilterTest.php000064400000003441144760116330014566 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\Aop\Support; use Go\Aop\PointFilter; class OrPointFilterTest extends \PHPUnit_Framework_TestCase { /** * Tests that filter combined different kinds of filters */ public function testKindIsCombined() { $first = $this->getMock('Go\Aop\PointFilter'); $first ->expects($this->any()) ->method('getKind') ->will($this->returnValue(PointFilter::KIND_METHOD | PointFilter::KIND_PROPERTY)); $second = $this->getMock('Go\Aop\PointFilter'); $second ->expects($this->any()) ->method('getKind') ->will($this->returnValue(PointFilter::KIND_METHOD | PointFilter::KIND_FUNCTION)); $filter = new OrPointFilter($first, $second); $expected = PointFilter::KIND_METHOD | PointFilter::KIND_FUNCTION | PointFilter::KIND_PROPERTY; $this->assertEquals($expected, $filter->getKind()); } /** * @dataProvider logicCases */ public function testMatches(PointFilter $first, PointFilter $second, $expected) { $filter = new OrPointFilter($first, $second); $result = $filter->matches(new \ReflectionClass(__CLASS__) /* anything */); $this->assertSame($expected, $result); } public function logicCases() { $true = TruePointFilter::getInstance(); $false = new NotPointFilter($true); return array( array($false, $false, false), array($false, $true, true), array($true, $false, true), array($true, $true, true) ); } } tests/Go/Aop/Support/TruePointFilterTest.php000064400000001733144760116330015127 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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.php000064400000007307144760116330013227 0ustar00getMock('Go\Core\AspectContainer'); $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); $loader = $this->getMock('Go\Core\AspectLoader', array(), array($container, $reader)); $this->adviceMatcher = new AdviceMatcher($loader, $container); $brokerInstance = new Broker(new Broker\Backend\Memory()); $brokerInstance->processFile(__FILE__); $this->reflectionClass = $brokerInstance->getClass(__CLASS__); } /** * Verifies that empty result will be returned without aspects and advisors */ public function testGetEmptyAdvicesForClass() { // by reflection $advices = $this->adviceMatcher->getAdvicesForClass($this->reflectionClass, array()); $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); $advices = $this->adviceMatcher->getAdvicesForClass($this->reflectionClass, array($advisor)); $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 = 'adviceMatcher'; // $this->adviceMatcher; $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); $advices = $this->adviceMatcher->getAdvicesForClass($this->reflectionClass, array($advisor)); $this->assertArrayHasKey(AspectContainer::PROPERTY_PREFIX, $advices); $this->assertArrayHasKey($propName, $advices[AspectContainer::PROPERTY_PREFIX]); $this->assertCount(1, $advices[AspectContainer::PROPERTY_PREFIX]); } } tests/Go/Core/GoAspectContainerTest.php000064400000006146144760116330014100 0ustar00container = new GoAspectContainer(); $this->container->set('kernel.options', array()); $this->container->set('kernel.interceptFunctions', false); } /** * 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/PathResolverTest.php000064400000004765144760116330014433 0ustar00assertEquals(__DIR__, PathResolver::realpath(__DIR__, true)); $this->assertEquals(false, PathResolver::realpath(__DIR__ . '/bad/dir', true)); } /** * Test multiple resolve */ public function testCanResolveArray() { $this->assertEquals(array(__DIR__ , __FILE__), PathResolver::realpath(array(__DIR__ , __FILE__))); } /** * Test for checking the logic of custom realpath() implementation * * @param string $path Given path * @param string $expected Expected path * * @dataProvider realpathExamples */ public function testRealpathWorkingCorrectly($path, $expected) { // Trick to get scheme name and path in one action. If no scheme, then there will be only one part $components = explode('://', $expected, 2); list ($pathScheme, $localPath) = isset($components[1]) ? $components : array(null, $components[0]); // resolve path parts (single dot, double dot and double delimiters) $localPath = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $localPath); if ($pathScheme) { $localPath = "$pathScheme://$localPath"; } $actual = PathResolver::realpath($path); $this->assertEquals($localPath, $actual); } /** * Test paths provider * * @return array */ public function realpathExamples() { $curDir = getcwd(); $parent = dirname($curDir); return array( array('/some/absolute/file' , '/some/absolute/file'), array('/some/absolute/file/../points/' , '/some/absolute/points/'), array('/some/./point.php' , '/some/point.php'), array('relative/to/the/dir' , "$curDir/relative/to/the/dir"), array('../relative/filename' , "$parent/relative/filename"), array('./point/file' , "$curDir/point/file"), array('C:\\Windows\\..\\filename', 'C:\\filename'), array('http://localhost/file.name' , 'http://localhost/file.name'), array('http://localhost/some/../relative.file' , 'http://localhost/relative.file'), array('phar://go.phar/some/path' , 'phar://go.phar/some/path'), array('phar://go.phar/some/../relative.file' , 'phar://go.phar/relative.file'), ); } } tests/Go/Instrument/Transformer/ConstructorExecutionTransformerTest.php000064400000007467144760116330022755 0ustar00metadata = new StreamMetaData($stream); fclose($stream); } /** * @dataProvider listOfExpressions */ public function testCanTransformNewExpressions($source, $expected) { $this->metadata->source = ""; self::$transformer->transform($this->metadata); $output = ""; $this->assertEquals($output, $this->metadata->source); } public function listOfExpressions() { return array( array( '$a = new stdClass', '$a = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ stdClass::class}' ), array( '$b = new stdClass()', '$b = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ stdClass::class}()' ), array( '$stdClass = "stdClass"; $c = new $stdClass', '$stdClass = "stdClass"; $c = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ $stdClass}' ), array( '$stdClass = "stdClass"; $d = new $stdClass()', '$stdClass = "stdClass"; $d = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ $stdClass}()' ), array( '$e = new \Exception', '$e = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ \Exception::class}' ), array( '$f = new \Exception("Test")', '$f = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ \Exception::class}("Test")' ), array( '$g = new self', '$g = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ self::class}', ), array( '$h = new static()', '$h = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ static::class}()' ), array( '$j = new self::$stdClass()', '$j = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ self::$stdClass}()' ), array( '$k = new static::$exception["Exception"]("Test")', '$k = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ static::$exception["Exception"]}("Test")' ), array( '$l = new self::$object[0]->name("Test Message")', '$l = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ self::$object[0]->name}("Test Message")' ), array( '$m = new static::$object[0]->name', '$m = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ static::$object[0]->name}' ), array( '$n = new stdClass(new static::$object[0]->name)', '$n = \Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ stdClass::class}(\Go\Instrument\Transformer\ConstructorExecutionTransformer::getInstance()->{ static::$object[0]->name})' ) ); } } tests/Go/Instrument/Transformer/FilterInjectorTransformerTest.php000064400000012255144760116330021456 0ustar00getKernelMock( array( 'cacheDir' => null, 'appDir' => '', 'debug' => false, 'features' => 0 ), $this->getMock('Go\Core\GoAspectContainer') ); self::$transformer = new FilterInjectorTransformer( $kernelMock, 'unit.test', $this->getMock('Go\Instrument\ClassLoading\CachePathManager', array(), array($kernelMock)) ); } $stream = fopen('php://input', '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, $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; } 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); } 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 testCanRewriteClassesWithToString() { $file = new \SplFileInfo(__FILE__); $actual = FilterInjectorTransformer::rewrite($file); $this->assertStringEndsWith(__FILE__, $actual); } 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.php000064400000006714144760116330021270 0ustar00transformer = new MagicConstantTransformer( $this->getKernelMock(array( 'cacheDir' => __DIR__, 'appDir' => dirname(__DIR__), )) ); if (defined("HHVM_VERSION")) { // Workaround for https://github.com/facebook/hhvm/issues/2485 $stream = fopen(__FILE__, 'r'); stream_filter_append($stream, 'string.tolower'); } else { $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.php000064400000023174144760116330020135 0ustar00getContainerMock(); $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); $loader = $this->getMock('Go\Core\AspectLoader', array(), array($container, $reader)); $this->broker = new \TokenReflection\Broker( new \TokenReflection\Broker\Backend\Memory() ); $this->adviceMatcher = $this->getAdviceMatcherMock(); $this->kernel = $this->getKernelMock( array( 'appDir' => dirname(__DIR__), 'cacheDir' => null, 'includePaths' => array(), 'excludePaths' => array() ), $container ); $this->transformer = new WeavingTransformer( $this->kernel, $this->broker, $this->adviceMatcher, $this->getMock('Go\Instrument\ClassLoading\CachePathManager', array(), array($this->kernel)), $loader ); if (defined("HHVM_VERSION")) { // Workaround for https://github.com/facebook/hhvm/issues/2485 $stream = fopen(__FILE__, 'r'); stream_filter_append($stream, 'string.tolower'); } else { $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 */ public function testMultipleNamespacesInOneFile() { $this->metadata->source = $this->loadTest('multiple-ns'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('multiple-ns-woven')); $this->assertEquals($expected, $actual); } /** * 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() { $container = $this->getContainerMock(); $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); $loader = $this->getMock('Go\Core\AspectLoader', array(), array($container, $reader)); $this->transformer = new WeavingTransformer( $this->getKernelMock( array( 'appDir' => dirname(__DIR__), 'includePaths' => array(__DIR__), 'excludePaths' => array() ), $container ), $this->broker, $this->adviceMatcher, $this->getMock('Go\Instrument\ClassLoading\CachePathManager', array(), array($this->kernel)), $loader ); $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 that proxy with static LSB feature is working */ public function testTransformerWithStaticLsbFeature() { $this->kernel->expects($this->any()) ->method('hasFeature') ->will( $this->returnCallback(function ($feature) { return $feature === Features::USE_STATIC_FOR_LSB; }) ); $this->metadata->source = $this->loadTest('class'); $this->transformer->transform($this->metadata); $actual = $this->normalizeWhitespaces($this->metadata->source); $expected = $this->normalizeWhitespaces($this->loadTest('class-woven')); // with Features::USE_STATIC_FOR_LSB we expect static::class in the proxy methods $expected = str_replace('\get_called_class()', 'static::class', $expected); $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', 'hasFeature') ); $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) { $advisorId = "advisor.{$refClass->name}->{$method->name}"; $advices[AspectContainer::METHOD_PREFIX][$method->name][$advisorId] = true; } return $advices; }) ); return $mock; } private function loadTest($name) { return file_get_contents(__DIR__ . '/_files/' . $name . '.php'); } /** * Returns a mock for the container * * @return AspectContainer */ private function getContainerMock() { $container = $this->getMock('Go\Core\AspectContainer'); $container ->expects($this->any()) ->method('getByTag') ->will($this->returnValueMap(array( array('advisor', array()) ))); return $container; } } tests/Go/Instrument/Transformer/_files/aspect.php000064400000000137144760116330016164 0ustar00__invoke($this); } protected function protectedMethod() { return self::$__joinPoints['method:protectedMethod']->__invoke($this); } public static function publicStaticMethod() { return self::$__joinPoints['static:publicStaticMethod']->__invoke(\get_called_class()); } protected static function protectedStaticMethod() { return self::$__joinPoints['static:protectedStaticMethod']->__invoke(\get_called_class()); } 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)); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns1\TestClass',array ( 'method' => array ( 'publicMethod' => array ( 0 => 'advisor.Test\\ns1\\TestClass->publicMethod', ), 'protectedMethod' => array ( 0 => 'advisor.Test\\ns1\\TestClass->protectedMethod', ), 'publicStaticMethod' => array ( 0 => 'advisor.Test\\ns1\\TestClass->publicStaticMethod', ), 'protectedStaticMethod' => array ( 0 => 'advisor.Test\\ns1\\TestClass->protectedStaticMethod', ), 'publicMethodDynamicArguments' => array ( 0 => 'advisor.Test\\ns1\\TestClass->publicMethodDynamicArguments', ), 'publicMethodFixedArguments' => array ( 0 => 'advisor.Test\\ns1\\TestClass->publicMethodFixedArguments', ), ), )); tests/Go/Instrument/Transformer/_files/class.php000064400000000761144760116330016015 0ustar00__invoke($this); } protected function protectedMethod() { return self::$__joinPoints['method:protectedMethod']->__invoke($this); } public static function publicStaticMethod() { return self::$__joinPoints['static:publicStaticMethod']->__invoke(\get_called_class()); } protected static function protectedStaticMethod() { return self::$__joinPoints['static:protectedStaticMethod']->__invoke(\get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns1\TestFinalClass',array ( 'method' => array ( 'publicMethod' => array ( 0 => 'advisor.Test\\ns1\\TestFinalClass->publicMethod', ), 'protectedMethod' => array ( 0 => 'advisor.Test\\ns1\\TestFinalClass->protectedMethod', ), 'publicStaticMethod' => array ( 0 => 'advisor.Test\\ns1\\TestFinalClass->publicStaticMethod', ), 'protectedStaticMethod' => array ( 0 => 'advisor.Test\\ns1\\TestFinalClass->protectedStaticMethod', ), ), )); tests/Go/Instrument/Transformer/_files/final-class.php000064400000000375144760116330017105 0ustar00__invoke(\get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns3\TestClass1',array ( 'method' => array ( 'test' => array ( 0 => 'advisor.Test\\ns3\\TestClass1->test', ), ), )); 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',array ( 'method' => array ( 'test' => array ( 0 => 'advisor.Test\\ns3\\TestClass11->test', ), ), )); 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',array ( 'method' => array ( 'test' => array ( 0 => 'advisor.Test\\ns3\\TestClass2->test', ), ), )); TestClass2::test(); tests/Go/Instrument/Transformer/_files/multiple-classes.php000064400000000415144760116330020172 0ustar00__invoke(\get_called_class()); } } \Go\Proxy\ClassProxy::injectJoinPoints('Test\ns1\TestClass1',array ( 'method' => array ( 'test' => array ( 0 => 'advisor.Test\\ns1\\TestClass1->test', ), ), )); } namespace Test\ns2 { 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\ns2\TestClass2',array ( 'method' => array ( 'test' => array ( 0 => 'advisor.Test\\ns2\\TestClass2->test', ), ), )); }tests/Go/Instrument/Transformer/_files/multiple-ns.php000064400000000300144760116330017146 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.php000064400000001415144760116330020337 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/Stubs/BaseInterceptorMock.php000064400000002263144760116330013777 0ustar00 'aspect', 'method' => 'Go\Aop\Framework\{closure}', 'aspect' => 'Go\Aop\Framework\BaseInterceptorTest' ); } /** * {@inheritdoc} */ public static function unserializeAdvice($adviceData) { return self::$advice; } /** * Implement this method to perform extra treatments before and * after the invocation of joinpoint. * * @param Joinpoint $joinpoint current joinpoint * * @return mixed the result of the call */ public function invoke(Joinpoint $joinpoint) { return $joinpoint; } } tests/Go/Stubs/First.php000064400000005352144760116330011165 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/Go/Stubs/FirstStatic.php000064400000000734144760116330012334 0ustar00__invoke(__CLASS__, array($value, $level)); } } tests/bootstrap.php000064400000001072144760116330010441 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file 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); } }