Seasar DI Container with AOP
S2Container.PHP5 is a port of Java version Seasar2 to PHP5.
For information on AOP, refer to S2AOP page.

Warnings

  • Aspect may not be applied to a final class
  • Aspect may not be applied to a static method
  • Not specifying a pointcut attribute does not imply all methods - it means only interfaces with implementations. To imply all methods, specify ".*" in pointcut attribute.

S2AOP Reference

Required Files

S2AOP is defined in S2Container configuration file (dicon file). There is no restriction on placement of the configuration file, but it is usually placed in the same folder as the Crosscutting Concern or in the same folder as the component it configures.

Configuration File

aspect tag (required when using AOP)

Weave an aspect into a component. Interceptor is specified in a PHP statement in a BODY or in a component tag in a child tag.
If several aspects are weaved into a component, the are processes in the order they are declared. Refer to Custom implementation of Interceptor for further details.

Warning:

Components specified by the aspect tag are gotten from the contrainer when the container is initialized. Therefore, even if the instance attribute of a component specified by the aspect tag is set to "prototype", new instance will NOT be created on each invocation of a method in an Interceptor.

pointcut attribute (optional)

Several method names may be specified by delimiting the names with a comma. Not specifying any pointcut implies all methods in an interface implemented by a component. Regular expression may be used to specify method names.

Example

Following is a example of a definition to apply aspect to getTime() method in Date by specifying pointcut attribute. If pointcut attribute is not specified, aspect will be applied to all methods in the interface implemented by Date.

<?php
class Date {
    function Date() {}
    
    function getTime(){
        return '12:00:30';
    }

    function getDate(){
        return '25';
    }
}
?>
Following is an example using a regular expression to apply aspect to all public methods in java.util.Date. Note that this example applies to ALL public methods regardless of whether they are implemented by the class or not.
<component class="Date">
    <aspect pointcut=".*">
        <component class="TraceInterceptor"/>
    </aspect>
</component>

Available S2AOP Interceptor

Following are Interceptors made available by S2AOP. Custom Interceptors may also be created.

(1) TraceInterceptor

Class name

TraceInterceptor

Description

Interceptor to process trace as a "Crosscutting Concern". Following is an example of a dicon file that applies TraceInterceptor to the Date class. getTime() method will be applied in this example.

<component class="Date">
    <aspect pointcut="getTime">
        <component class="TraceInterceptor"/>
    </aspect>
</component>

Refer to TraceInterceptor for detailed information.

(2) ThrowsInterceptor

Class name

ThrowsInterceptor

Description

Interceptor to process Exception as a "Crosscutting Concern". To use this Interceptor, just extend ThrowsInterceptor class and implement function handleThrowable(Exception, MethodInvocation) method. Refer to ThrowsInterceptor for detailed information.

(3) MockInterceptor

Class name

MockInterceptor

Description

Interceptor to make tests using Mock easier. Refer to for further details.

(4) DelegateInterceptor

Class name

DelegateInterceptor

Description

Interceptor to delegate method invocation to another component. To use, specify a component to delegate in a target property in DelegateInterceptor. To use a different method name in delegation, specify DeleateInterceptor#addMethodNameMap( $methodName, $targetMethodName). For example, to delegate method bar() to foo->bar2(), specify DeleateInterceptor#setTarget(foo),DeleateInterceptor#addMethodNameMap("bar", "bar2"). Refer to DelegateInterceptor for detailed information.

Warning:

Component specified by target property is gotten during container initialization. Therefore, the same instance will be used even when the instance property is set to "prototype". To get a new instance from a container on every method invocation, use PrototypeDelegateInterceptor.

(5) PrototypeDelegateInterceptor

Class name

PrototypeDelegateInterceptor

Description

Interceptor to delegate method invocation to another component. Get component on every method invocation. To use, specify a component to delegate in a target property in PrototypeDelegateInterceptor. To use a different method name in delegation, specify PrototypeDeleateInterceptor#addMethodNameMap($methodName, $targetMethodName). For example, to delegate method bar() to foo->bar2(), specify PrototypeDeleateInterceptor#setTarget(foo), PrototypeDeleateInterceptor#addMethodNameMap("bar", "bar2"). Refer to PrototypeDelegateInterceptor for detailed information.

(6) InterceptorChain

Class name

InterceptorChain

Description

Group several Interceptors to make it easier to manage and reuse. If several Interceptors are often used in several components, Interceptors may be bundled into Interceptor1 by using InterceptorChain. Each component can then specify InterceptorChain instead of several Interceptors.

<component name="interceptor1" .../>
<component name="interceptor2" .../>
<component name="interceptor3" .../>
<component name="chain" class="InterceptorChain">
<initMethod name="add"><arg>interceptor1</arg></initMethod>
<initMethod name="add"><arg>interceptor2</arg></initMethod>
<initMethod name="add"><arg>interceptor3</arg></initMethod>
</component> <component ...> <aspect>chain</aspect> </component> <component ...> <aspect>chain</aspect> </component>

(7) Custom implementation of Interceptor

Description

To create an custom Interceptor, implement following interface or abstract class.

MethodInterceptor
AbstractInterceptor

In either cases, only method invoke() is required.

public function invoke(MethodInvocation $methodInvocation)

AbstractInterceptor is an abstract class that implements MethodInterceptor. AbstractInterceptor has createProxy() method, which gets Proxy object, and getTargetClass() method, which gets class to apply aspect. To create an Interceptor that requires a class name with aspect applied (e.g. Interceptor to log output), class name may be obtained by using AbstractInterceptor.

public function createProxy($proxyClass)
protected function getTargetClass(MethodInvocation $methodInvocation)

Get object, method, arguments that is the target of getThis(), getMethod(), getArguments() in MethodInvocation. When proceed() is invoked, the actual method is invoked and execution result is returned. Following is an example of custom Interceptor:

Example
<?php
class ABS implements MethodInterceptor {
    public function invoke(MethodInvocation $invocation){

        print "Before \n";             <-- Before invocation

        $ret = $invocation->proceed();

        print "After \n";              <-- After invocation

        return $ret;
    }
}
?>

In this example, "Before" is outputted before MethodInvocation#proceed() is invoked and "After" is outputted after the invocation. When several aspects are defined to one component, aspects are invoked as follows:

  1. Aspects before MethodInterceptor are invoked in order as they are defined in the dicon file.
  2. Component is invoked after the last aspect defined before MethodInterceptor is invoked
  3. Aspects after MethodInterceptor are invoked in reverse order as they are defined in the dicon file.

Refer to Custom implementation of Interceptor for detailed information.

Using Aspect without a dicon file

It is possible to use aspect in a program without manually creating a dicon file by doing the followings:

  • Specify name of class (several names may be specified) to apply an aspect as PointcutImpl constructor arguments. All methods implemented by the class are automatically applied.
  • Specify Interceptor as the first argument and Pointcut created from PointcutImpl as the second argument to AspectImpl constructor.
  • Specify the target class and an array of Aspect created from AspectImpl to AopProxy constructor
  • Objects that had aspect applied may be gotten by using AopProxy#create()

In the following example, TraceInterceptor is applied to the Date class. Target method is getTime().

<?php
$pointcut = new PointcutImpl(array("getTime"));
$aspect = new AspectImpl(new TraceInterceptor(), $pointcut);
$aopProxy = new AopProxy('Date', array($aspect));
$proxy = $aopProxy->create();
$proxy->getTime();
?>

Example

S2Container.PHP needs to be setup before trying this example.

TraceInterceptor

This example uses TraceInterceptor to output trace when getTime() method in Date class is invoked. Following files are created in this example:

  • dicon file (Trace.dicon) with component definition
  • Execution script file (AopTraceClient.php) to test the settings

Create a dicon File

  • Define TraceInterceptor component. Set name attribute to "traceInterceptor"
  • Define Date class. Set pointcut attribute to getTime() method. Set aspect tag to Interceptor

Trace.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component name="traceInterceptor" class="TraceInterceptor"/>
    <component class="Date">
        <aspect pointcut="getTime">
            traceInterceptor
        </aspect>
    </component>
</components>

Create an Execution Script

  • Create container by specifying dicon file (Trace.dicon) as the first argument to S2Container#create() method
  • Get component by specifying class name (Date) as the first argument to S2Container#getComponent() method
  • Invoke getTime() method in Date component to check if trace aspect was properly applied

AopTraceClient.php

<?php
require_once(dirname(__FILE__) . '/trace.inc.php');

$PATH = EXAMPLE_DIR . "/aop/traceinterceptor/Trace.dicon";

$container = S2ContainerFactory::create($PATH);
$date = $container->getComponent('Date');
$date->getTime();
?>

Results

Trace is outputted before and after method is invoked.

% php AopTraceClient.php
BEGIN Date#getTime()
END   Date#getTime() : 12:00:30
%

Files in this example are available in the s2container.php5/src/examples/aop/traceinterceptor folder.


ThrowsInterceptor

(1) This example uses ThrowsInterceptor so process will continue even after an exception is thrown. Following files are created in this example:

  • Class that throws an Exception (Checker.class.php)
  • Interceptor that extends ThrowsInterceptor (HandleThrowableInterceptor.class.php)
  • dicon file (Checker.dicon) with component definition
  • Execution file (AopCheckerClient.php) to check if the settings are correct

Create class that throws an exception

  • If the argument to run() method is not null, output the argument
  • If the argument is null, throw and Exception

Checker.class.php

<?php
class Checker {
    public function check($str) {
        if ($str != null) {
            print $str . "\n";
        } else {
            throw new Exception("null");
        }
    }
}
?>

Create Interceptor that extends ThrowsInterceptor

  • Extend ThrowsInterceptor
  • Implement handleThrowable(Exception, MethodInvocation)

HandleThrowableInterceptor.class.php

<?php
class HandleThrowableInterceptor extends ThrowsInterceptor {
	
    public function handleThrowable(Exception $t, MethodInvocation $invocation){
    }
}
?>

Create a dicon File

  • Define created Interceptor as a component. Set name attribute to handleThrowableInterceptor
  • Aspect HandleThrowableInterceptor to the check() method in Check class which throws an exception

Checker.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component name="handleThrowableInterceptor" class="HandleThrowableInterceptor"/>
    <component class="Checker">
        <aspect pointcut="check">
            handleThrowableInterceptor
        </aspect>
    </component>
</components>

Create an Execution File

  • Create a container by specifying the path to the created dicon file (Checker.dicon) as the first argument to S2Container#create() method
  • Gets component by specifying class name (Foo.class) as the first argument to S2Container#getComponent() method
  • Specify String "foo" as an argument to Checker#check() method
  • Make it throw an Exception by specifying null to Checker#check() method
  • Specify String "hoge" as an argument to Checker#check() method

AopCheckerClient.php

<?php
require_once(dirname(__FILE__) . '/throws.inc.php');

$PATH = EXAMPLE_DIR . "/aop/throwsinterceptor/Checker.dicon";
$container = S2ContainerFactory::create($PATH);
$checker = $container->getComponent('Checker');
try{
    $checker->check("foo");
}catch(Exception $e){
    print "Exception : " . $e->getMessage() . "\n";
}

try{
    $checker->check(null);
}catch(Exception $e){
    print "Exception : " . $e->getMessage() . "\n";
}

try{
    $checker->check("hoge");
}catch(Exception $e){
    print "Exception : " . $e->getMessage() . "\n";
}
?>

Result

Check to make sure process is not stopped by an Exception.

% php AopCheckerClient.php
foo
hoge
%

Files for this example are available in the s2container.php5/src/examples/aop/throwsinterceptor folder


(2) This example creates an Interceptor that will replace an Exception to a different Exception and display a different message. The Interceptor is created by using class (HandleThrowableInterceptor.class.php) which extends ThrowsInterceptor.

Create Interceptor that replaces Exception with another Exception

Create a new message by throwing a new S2RuntimeException exception

<?php
class HandleThrowableInterceptor extends ThrowsInterceptor {
	
    public function handleThrowable(Exception $t, MethodInvocation $invocation){
        throw new S2RuntimeException("ESSR0007", array("arg"));
    }
}
?>

Invoke the method by selecting the execution file created above

Result

Confirm the change in error message.

% php AopCheckerClient.php
foo
Exception : arg should not be null or empty
hoge
%

Files in this example are available in the s2container.php5/src/examples/aop/throwsinterceptor folder.

DelegateInterceptor

This example uses DelegateInterceptor to delegate to a method in another class.
Create an abstract method in an interface and implement that interface to both an abstract class and non-abstract class. Then, delegate method implemented in an abstract class to method implemented in a non-abstract class. Following files are created in this example:

  • Interface(IBase.class.php)
  • Abstract class (Dummy.class.php) implementing an interface
  • Class (Substance.class.php) implementing an interface
  • dicon file (Delegate.dicon) configuring Delegation
  • Execution file (AopDelegateClient.class.php) to check if the configuration is correct.

Create an interface

  • Create abstract method

IBase.class.php

<?php
interface IBase {
    public function run();
}
?>

Create abstract class implementing an interface

  • Implement created interface

Dummy.class.php

<?php
abstract class Dummy implements IBase {
}
?>

Create an class implementing an interface

  • Implement created interface
  • Implement abstract method in an implemented interface

Substance.class.php

<?php
class Substance implements IBase{
    public function run() {
        print "substance\n";
    }
}
?>

Create dicon file

  • Define abstract class (Dummy) as a component
  • Define DelegateInterceptor component in aspect tag. Specify target to delegation by using DelegateInterceptor#setTarget(). Target of the delegation is Substance class so use method injection.

Delegate.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component class="Dummy">
        <aspect>
            <component class="DelegateInterceptor">
                <initMethod name="setTarget">
                    <arg>new Substance()</arg>
                </initMethod>
            </component>
        </aspect>
    </component>
</components>

Create execution file

  • Create a S2Container by specifying path to the dicon file (Delegate.dicon) as the first argument in S2Container#create() method
  • Get component by specifying class name (IBase) as the first argument to S2Container#getComponent() method.
  • Invoke IBase#run() method gotten from the container
AopDelegateCilent.php
<?php
require_once(dirname(__FILE__) . '/delegate.inc.php');
$PATH = EXAMPLE_DIR . "/aop/delegateinterceptor/Delegate.dicon";
$container = S2ContainerFactory::create($PATH);
$base = $container->getComponent('Dummy');
$base->run();
?>

Result

String "substance" is outputted implying that Dummy#run() method delegated to Substance#run() method.

% php AopDelegateCilent.php
substance
%

Files in this example are available in the s2container.php5/src/examples/aop/delegateinterceptor folder.


PrototypeDelegateInterceptor

This examples uses PrototypeDelegateInterceptor to delegate from a singleton component to a prototype component.
create abstract interface and implement the method on both abstract class (singleton) and non-abstract class (prototype). Delegate method implemented in an abstract class to method implemented in a non-abstract class. Following files are created in this example.

  • Interface (IBase.class.php)
  • singleton abstract class (Dummy.class.php) that implements an interface
  • Class (Substance.class.php) implementing an interface
  • dicon file (Delegate.dicon) with delegation settings
  • Execution file (AopPrototypeDelegateClient.php) to test if the settings are correct

Create an interface

  • Create an abstract method

IBase.class.php

<?php
interface IBase {
    public abstract function run();
}
?>

Create abstract class implmenting an interface

  • Implement created interface

Dummy.class.php

<?php
abstract class Dummy implements IBase {
}
?>

Create class implementing an interface

  • Implement created interface
  • Implement abstract method in implemented interface

Substance.class.php

<?php
class Substance implements IBase{
    private $sum = 0;

    public function run() {
        print "sum : " . $this->sum . "\n";
        $this->sum++;
    }
}
?>

Create dicon file

  • Define abstract class(Dummy) as a component. This component is a singleton.
  • Define component PrototypeDelegateInterceptor in aspect tag. Specify name of object to delegate to ("target") in targetName property of PrototypeDelegateInterceptor.
  • Define delegated class (Substance) as a component. instance attribute of this component is prototype.

PrototypeDelegate.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component class="Dummy">
        <aspect>
            <component class="PrototypeDelegateInterceptor">
                <property name="targetName">"target"</property>
            </component>
        </aspect>
    </component>

    <component name="target" class="Substance" instance="prototype"/>
</components>

Create execution file

  • Create a container by specifying the path to the created dicon file (PrototypeDelegate.dicon) as the first argument to S2Container#create() method
  • Gets component by specifying class name (IBase) as the first argument to S2Container#getComponent() method
  • repetitively invoke IBase#run() method gotten from the container
AopPrototypeDelegateCilent.php
<?php
require_once(dirname(__FILE__) . '/prototype.inc.php');

$PATH = EXAMPLE_DIR . "/aop/prototypedelegateinterceptor/PrototypeDelegate.dicon";
$container = S2ContainerFactory::create($PATH);
$base = $container->getComponent('Dummy');
for ($i = 0; $i < 5; ++$i) {
   $base->run();
}
?>

Result

"sum : 0" is outputted implying Dummy#run() method delegated to Substance#run() method. Additionally, sum are all 0 implying new Substance instance was created on each invocation of run() method.

% php AopPrototypeDelegateCilent.php
sum : 0
sum : 0
sum : 0
sum : 0
sum : 0
%

Files in this example are available in the s2container.php5/src/examples/aop/delegateinterceptor folder.


Custom implementation of Interceptor

This example creates an Interceptor that will trace class name, method name, arguments, and time required by the thread. Use this Iterceptor to trace "heavy" threads. Following files are created in this example:

  • Interceptor(MeasurementInterceptor.java) method that displays class name, method name, arguments, time required by the thread
  • Class to process "heavy" tasks (HeavyProcess.java)
  • dicon file (Measurement.dicon) with component definition
  • Execution file (AopMeasurementClient.java) to check if settings are correct

Create custom implementation Intercepter

  • Implement AbstractInterceptor class
  • Implement invoke(MethodInvocation $invocation) method
  • Get class name by invoking getTargetClass($invocation)->getName()
  • Get method name by invoking $invocation->getMethod()->getName()
  • Get arguments by invoking $invocation->getArguments()
  • Get the time before $invocation->proceed() actual method invocation.

MeasurementInterceptor.class.php

<?php
class MeasurementInterceptor extends AbstractInterceptor {
    public function invoke(MethodInvocation $invocation){
        $start = 0;
        $end = 0;
        $buf = "";

        $buf = $this->getTargetClass($invocation)->getName();
        $buf .= "#";
        $buf .= $invocation->getMethod()->getName();
        $buf .= "(";
        $args = $invocation->getArguments();
        $buf .= implode($args) . ")";
        try {
            $start = $this->microtime_float();
            $ret = $invocation->proceed();
            $end = $this->microtime_float();
            $buf .= " : ";
            $t = $end - $start;
	    print $buf . $t . "\n";
            return $ret;
        } catch (Exception $t) {
            $buf .= " Exception:";
            $buf .= $t->getMessage();
            throw $t;
        }
    }
	
    private function microtime_float() {
        list($usec, $sec) = explode(" ", microtime());
        return ((float)$usec + (float)$sec);
    } 
}
?>

Create class to process "heavy" threads

  • sleep 5 seconds when a thread requires extensive resources

HeavyProcess.class.php

<?php
class HeavyProcess {
    public function heavy(){
    	sleep(5);
    }
}
?>
Create dicon file
  • Define MeasurementInterceptor as a component Set name attribute to measurement
  • Apply MeasurementInterceptor as an aspect to heavy() method in HeavyProcess class

Measurement.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components32.dtd">
<components>
    <component name="measurement" class="MeasurementInterceptor"/>
    <component class="HeavyProcess">
        <aspect pointcut="heavy">
               measurement
        </aspect>
    </component>
</components>

Create execution file

  • Create a container by specifying the path to the created dicon file (Measurement.dicon) as the first argument to S2Container#create() method
  • Get component by specifying class name (HeavyProcess) as the first argument to S2Container#getComponent() method
  • Invoke HeavyProcess#heavy() method gotten from the container

AopMeasurementClient.php

<?php
require_once(dirname(__FILE__) . '/original.inc.php');
$PATH = EXAMPLE_DIR . "/aop/originalinterceptor/Measurement.dicon";

$container = S2ContainerFactory::create($PATH);
$heavyProcess = $container->getComponent('HeavyProcess');
$heavyProcess->heavy();
?>

Result

Class name, method name, arguments, and time used to process a thread are displayed.

% php AopMeasurementClient.php
HeavyProcess#heavy() : 5.0025010108948
%

Files in this example are available in the s2container.php5/src/examples/aop/originalinterceptor folder