Skip to content
This repository

Aspect oriented framework for PHP

branch: develop

remove NestedScopes rule

remove from phpmd phpmd/phpmd#74
latest commit 958e3c8c50
Akihito Koriyama authored February 27, 2014
Octocat-spinner-32 docs Scrutinizer Auto-Fixes February 10, 2014
Octocat-spinner-32 scripts Scrutinizer Auto-Fixes February 10, 2014
Octocat-spinner-32 src reduce cyclomatic complexity February 21, 2014
Octocat-spinner-32 tests gc February 21, 2014
Octocat-spinner-32 .gitignore add git ignore February 21, 2014
Octocat-spinner-32 .php_cs remove gomi file August 21, 2013
Octocat-spinner-32 .scrutinizer.yml remove NestedScopes rule February 27, 2014
Octocat-spinner-32 .travis.yml add php 5.6 to travis February 12, 2014
Octocat-spinner-32 LICENSE update meta September 15, 2013
Octocat-spinner-32 README.ja.md my matcher README December 06, 2013
Octocat-spinner-32 README.md add Japanese readme link February 21, 2014
Octocat-spinner-32 build.xml show sniff codes February 21, 2014
Octocat-spinner-32 composer.json psr4 February 11, 2014
Octocat-spinner-32 composer.lock psr4 February 11, 2014
Octocat-spinner-32 phpcs.xml suppress UnusedFunctionParameter.Found February 21, 2014
Octocat-spinner-32 phpdox.xml.dist remove cs submodule August 29, 2013
Octocat-spinner-32 phpmd.xml remove NestedScopes rule February 27, 2014
Octocat-spinner-32 phpunit.xml.dist allow test time September 13, 2013
README.md

Aspect Oriented Framework for PHP

Latest Stable Version Build Status Scrutinizer Quality Score Code Coverage

[Japanese]

Ray.Aop package provides method interception. This feature enables you to write code that is executed each time a matching method is invoked. It's suited for cross cutting concerns ("aspects"), such as transactions, security and logging. Because interceptors divide a problem into aspects rather than objects, their use is called Aspect Oriented Programming (AOP).

A Matcher is a simple interface that either accepts or rejects a value. For Ray.AOP, you need two matchers: one that defines which classes participate, and another for the methods of those classes. To make this easy, there's factory class to satisfy the common scenarios.

MethodInterceptors are executed whenever a matching method is invoked. They have the opportunity to inspect the call: the method, its arguments, and the receiving instance. They can perform their cross-cutting logic and then delegate to the underlying method. Finally, they may inspect the return value or exception and return. Since interceptors may be applied to many methods and will receive many calls, their implementation should be efficient and unintrusive.

Example: Forbidding method calls on weekends

To illustrate how method interceptors work with Ray.Aop, we'll forbid calls to our pizza billing system on weekends. The delivery guys only work Monday thru Friday so we'll prevent pizza from being ordered when it can't be delivered! This example is structurally similar to use of AOP for authorization.

To mark select methods as weekdays-only, we define an annotation. (Ray.Aop uses Doctrine Annotations)

<?php
/**
 * NotOnWeekends
 *
 * @Annotation
 * @Target("METHOD")
 */
final class NotOnWeekends
{
}

...and apply it to the methods that need to be intercepted:

<?php
class RealBillingService
{
    /**
     * @NotOnWeekends
     */
    chargeOrder(PizzaOrder $order, CreditCard $creditCard)
    {

Next, we define the interceptor by implementing the org.aopalliance.intercept.MethodInterceptor interface. When we need to call through to the underlying method, we do so by calling $invocation->proceed():

<?php
class WeekendBlocker implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation)
    {
        $today = getdate();
        if ($today['weekday'][0] === 'S') {
            throw new \RuntimeException(
                $invocation->getMethod()->getName() . " not allowed on weekends!"
            );
        }
        return $invocation->proceed();
    }
}

Finally, we configure everything. In this case we match any class, but only the methods with our @NotOnWeekends annotation:

<?php
$bind = new Bind;
$matcher = new Matcher(new Reader);
$interceptors = [new WeekendBlocker];
$pointcut = new Pointcut(
        $matcher->any(),
        $matcher->annotatedWith('Ray\Aop\Sample\Annotation\NotOnWeekends'),
        $interceptors
);
$bind->bind('Ray\Aop\Sample\AnnotationRealBillingService', [$pointcut]);

$compiler = require dirname(__DIR__) . '/scripts/instance.php';
$billing = $compiler->newInstance('RealBillingService', [], $bind);
try {
    echo $billing->chargeOrder();
} catch (\RuntimeException $e) {
    echo $e->getMessage() . "\n";
    exit(1);
}

Putting it all together, (and waiting until Saturday), we see the method is intercepted and our order is rejected:

<?php
RuntimeException: chargeOrder not allowed on weekends! in /apps/pizza/WeekendBlocker.php on line 14

Call Stack:
    0.0022     228296   1. {main}() /apps/pizza/main.php:0
    0.0054     317424   2. Ray\Aop\Weaver->chargeOrder() /apps/pizza/main.php:14
    0.0054     317608   3. Ray\Aop\Weaver->__call() /libs/Ray.Aop/src/Weaver.php:14
    0.0055     318384   4. Ray\Aop\ReflectiveMethodInvocation->proceed() /libs/Ray.Aop/src/Weaver.php:68
    0.0056     318784   5. Ray\Aop\Sample\WeekendBlocker->invoke() /libs/Ray.Aop/src/ReflectiveMethodInvocation.php:65

Explicit method name match

<?php
    $bind = new Bind;
    $bind->bindInterceptors('chargeOrder', [new WeekendBlocker]);

    $compiler = require dirname(__DIR__) . '/scripts/instance.php';
    $billing = $compiler->newInstance('RealBillingService', [], $bind);
    try {
       echo $billing->chargeOrder();
    } catch (\RuntimeException $e) {
       echo $e->getMessage() . "\n";
       exit(1);
    }

Own matcher

You can have your own matcher. To create contains matcher, You need to provide a class which have two method. One is contains for interface. The other one is isContains which return the result of the contains match.

use Ray\Aop\AbstractMatcher;

class MyMatcher extends AbstractMatcher
{
    /**
     * @param $contain
     *
     * @return MyMatcher
     */
    public function contains($contain)
    {
        $this->createMatcher(__FUNCTION__, $contain);

        return clone $this;

    }

    /**
     * Return isContains
     *
     * @param $name    class or method name
     * @param $target  \Ray\Aop\AbstractMatcher::TARGET_CLASS | \Ray\Aop\AbstractMatcher::Target_METHOD
     * @param $contain
     *
     * @return bool
     */
    protected function isContains($name, $target, $contain)
    {
        $result = (strpos($name, $contain) !== false);

        return $result;
    }
}

Limitations

Behind the scenes, method interception is implemented by generating code at runtime. Ray.Aop dynamically creates a subclass that applies interceptors by overriding methods.

This approach imposes limits on what classes and methods can be intercepted:

  • Classes must be non-final
  • Methods must be public
  • Methods must be non-final

AOP Alliance

The method interceptor API implemented by Ray.Aop is a part of a public specification called AOP Alliance.

Testing Ray.Aop

Here's how to install Ray.Aop from source to run the unit tests and sample:

$ git clone git://github.com/koriym/Ray.Aop.git
$ cd Ray.Aop
$ wget http://getcomposer.org/composer.phar
$ php composer.phar install
$ php doc/sample-01-quick-weave/main.php
// Charged. | chargeOrder not allowed on weekends!

Requirement

  • PHP 5.4+

Installation

Installing via Composer

The recommended way to install Ray.Aop is through Composer and the recommended way to use Ray.Aop is thorouh Ray.Di. Ray.Di is a Guice style annotation-driven dependency injection framework. It integrates Ray.Aop AOP functionality.

# Install Composer
curl -sS https://getcomposer.org/installer | php

# Add Ray.Aop as a dependency
php composer.phar require ray/aop:*

ini_set

You may want to set the xdebug.max_nesting_level ini option to a higher value:

ini_set('xdebug.max_nesting_level', 2000);
  • This documentation for the most part is taken from Guice/AOP.
Something went wrong with that request. Please try again.