Seasar DI Container with AOP

Extension

Setting include path to a dicon file

path attribute in an include tag in a dicon file may reference a constant by surrounding the constant name with a %

define(DICON_DIR,'/path/to/dir');   // constant definition

<components>
    <include path="%DICON_DIR%/bar.dicon"/>
</components>
Warning: only path name starting with %...% is evaluated.

__set() Setter Injection

Setter injection may be used with the newly introduced __set() in PHP5.

  • Property is explicitly specified (property tag is specified in the dicon file)
  • Sette method is not implemented by the class that is the target of injection
  • Target class of injection implements __set()
If one of the above conditions is true, __set() may be used to setter inject

Create execution class
  • Define setter method (setMessageA)
  • Implement __set()
  • Implement showMessage()

Setter method for $messageB property is not defined

<?php
class HelloImpl {

    private $messageA = "";
    private $messageB = "";

    function HelloImpl() {}
    
    function setMessageA($messageA){
        $this->messageA = $messageA;	
    }

    function __set($name,$val){
        $this->$name = $val;	
    }
    
    function showMessage(){
        print "{$this->messageA} {$this->messageB} \n";
    }
}
?>

Create dicon file
  • Define a component using component tag
  • Set property value of a component in property tag which is a child of a component tag

s2container.php5/src/examples/extension/uuset/uuset.dicon

<components>
    <component class="HelloImpl">
        <property name="messageA">"Hello"</property>
        <property name="messageB">"World"</property>
    </component>
</components>

Create execution script
  • Create S2Container by invoking S2ContainerFactory#create($path)
  • Get component from the S2Container by invoking getComponent()
  • Invoke on the method in the acquired component

s2container.php5/src/examples/extension/uuset/uusetClient.php

<?php
require_once(dirname(__FILE__) . '/uuset.inc.php');
$PATH = EXAMPLE_DIR . "/extension/uuset/uuset.dicon";
$container = S2ContainerFactory::create($PATH);
$hello = $container->getComponent('HelloImpl');
$hello->showMessage();
?>

Result

Value of a property tag should be outputted as below:

% php uusetClient.php
Hello World
%
Files in this example is available in the s2container.php5/src/examples/extension/uuset/ folder

dicon file in PHP configuration file format

dicon file is written in the same format as PHP configuration file (e.g. php.ini)
There are components section and sections for each component.

[components]
namespace = "dao"
.....
.....
.....
[componentA]                +
class = "A"                 |
.....                       |
.....                       |  component selection
[componentB]                |
.....                       |
.....                       |
.....                       ▽

components section

components section has 3 configuration items. If it is not necessary to specifgy configuration values in a components section, components section may be omitted.

-namespace Set namespace
-include Specify path of a dicon file to include. Several paths may be included using include{index} syntax.
-meta Set meta data

[components]
namespace = "dao"
include{0} = "/path/to/aaa.dicon"
include{1} = "/path/to/bbb.dicon"
meta{0}{name} = "metaA"
meta{0}{val}  = "meta data"
・・・
・・・
・・・

component section

Set component name in component section to a component name. If class name is omitted, class name will default to the name of the component. Following 10 items may also be set in the component section:

-class Specify class name. If omitted, defaults to a class name
-instance Specify instance mode
-autobinding Specify automatic binding mode
-exp Enter PHP expression
-arg Specify arguments to a constructor
-property Set a property
-init_method Set initMethod
-destroy_method Set destroyMethod
-aspect Set an aspect
-meta Set a meta data

[hello]
class = "Hello"
instance = "singleton"
autobinding = "auto"

arg{0}{val} = "hello world"
arg{1}{ref} = "bye"

property{0}{name} = "message"
property{0}{val} = "hello world 2 !!"

init_method{0}{name} = "initialize"
init_method{0}{arg{0}{val}} = "-1"

aspect{0}{pointcut} = "showMessage"
aspect{0}{exp} = "new TraceInterceptor()"

meta{0}{name} = "helloMeta"
meta{0}{ref} = "bye"

[bye]
class = "Bye"

...
...
...

Setting arg

-arg{index}{val} = value
-arg{index}{ref} = reference component
-arg{index}{exp} = PHP expression
-arg{index}{meta{0}{val}} = Meta data
[hello]
class=Hello
arg{0}{val} = "hello world"
arg{1}{ref} = bye
arg{2}{exp} = "array('hello','world','!!')"

[bye]
class = Bye

Setting a property

-property{index}{name} = name of property to set
-property{index}{val} = value to set
-property{index}{ref} = specify component to reference
-property{index}{exp} = PHP expression
-property{index}{meta{0}{val}} = Setting Meta data
[hello]
class=Hello

property{0}{name} = "messageA"     --- set value "Hello" to messageA property
property{0}{val} = "Hello"
property{1}{name} = "messageB"     --- set "World" to messageB property
property{1}{val} = "World"
property{2}{name} = "bye"          --- set component "bye" to bye property
property{2}{ref} = "bye"

[bye]
class = Bye

Setting init_method

-init_method{index}{name} = name of method to set
-init_method{index}{arg{0}{val}} = value of arguments
-init_method{index}{exp} = PHP expression
[hello]
class=Hello

init_method{0}{name} = "showMessage"
init_method{0}{arg{0}{val}} = "Hello"
init_method{0}{arg{1}{val}} = "World"

init_method{1}{exp} = "$component->initialize()"

Setting destroy_method

Similar to setting init_method

Setting aspect

-aspect{index}{pointcut} = pointcut
-aspect{index}{ref} = component to reference
-aspect{index}{exp} = PHP expression
[hello]
class=Hello

aspect{0}{pointcut} = "showMessage"
aspect{0}{ref} = "trace"

[trace]
class = "TraceInterceptor"

Setting meta

meta{index}{name} sets meta name、meta{index}{val} sets a value

-meta{index}{val} = value
-meta{index}{ref} = component to reference
-meta{index}{exp} = PHP expression
[components]
meta{0}{name} = "metaA"
meta{0}{val} = "5"
meta{1}{name} = "metaB"
meta{1}{ref} = hello
meta{2}{name} = "metaC"
meta{2}{exp} = "array('a','b','c')"

[hello]
class = HelloImpl

Miscellaneous

  • Single and double quoation may not be used in an item name (e.g. arg{0}{'val'}、arg{0}{"val"})
  • If a value is surrounded by double quotes, line returns becomes effective
  • index starts from value 0
  • Both XML format and PHP configuration format dicon configuration files for examples under folder s2container.php5/src/examples/ are included.

Database connection

Include database connection framework using extension

Following example uses the sample database table defined below. SQL script file (demo.sql) to create these tables is located in s2container.php5/src/s2container.php5/org/seasar/extension/db/sql/ folder.

Table: EMP
Column name Type NotNull Primary key
EMPNO NUMBER
ENAME VARCHAR

JOB VARCHAR

MGR NUMBER

HIREDATE DATE

SAL NUMBER

COMM NUMBER

DEPTNO NUMBER

TSTAMP TIMESTAMP


Table: DEPT
Column name Type NotNull Primary key
DEPTNO NUMBER yes yes
DNAME VARCHAR

LOC VARCHAR

VERSIONNO NUMBER

About DataSource

dicon configuration files containing native database connection configuration (DataSource) for MySQL, PostgreSQL, Pear DB, and ADOdb are available in the s2container.php5/src/s2container.php5/org/seasar/extension/db/ folder.

XML format dicon file INI format dicon file
native MySQL function mysql.dicon mysql.ini
native PostgreSQL function postgres.dicon postgres.ini
Pear DB peardb.dicon peardb.ini
ADOdb adodb.dicon adodb.ini
When using -Pear DB, specify to require DB.php.
When using -ADOdb, specify to require adodb-exceptions.inc.php and adodb.inc.php.

peardb.dicon template

Following is the content of peardb.dicon template. Properties of dataSource component should be set to values to properly connect to the database. If dsn properly is set, it will take precedence and be used.

XML format peardb.dicon file

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"components21.dtd">
<components namespace="peardb">
    <component name="dataSource" class="PearDBDataSource">
        <property name="dsn">                                  
            "mysql://hoge:hoge@localhost:3306/s2container"       
        </property>                                            
    </component>

    <component class="PearDBSqlHandler"/>
    <component name="dbtx" class="PearDBTxInterceptor"/>
    <component name="dao" class="SimpleDaoInterceptor"/>
    <component class="DaoMetaDataFactoryImpl"/>
    <component name="session" class="DBSessionImpl"/>
</components>

INI format peardb.ini file

[components]
namespace="peardb"

[dataSource]
class="PearDBDataSource"

property{0}{name}="dsn"                                         
property{0}{val} ="mysql://hoge:hoge@localhost:3306/s2container"

[PearDBSqlHandler]

[dbtx]
class="PearDBTxInterceptor"

[dao]
class="SimpleDaoInterceptor"

[DaoMetaDataFactoryImpl]

[session]
class="DBSessionImpl"

Usage

In this example, DEPT table is queries with a key passed by an argument. A DOA to return the result is implemented.
Files in this example is available in the s2container.php5/src/examples/extension/db/ folder.

Interface Definition

  • Define DeptDao interface
  • Define findDeptByDeptno method that will search by DEPTNO
<?php
interface DeptDao {
    function findDeptByDeptno($deptno);
}
?>

Create execution class

  • Create DeptDaoImpl class that implements DeptDao interface
  • Pass DataSource as a constructor argument
  • Implemnet findDeptByDeptno method. Obtain database connection using DataSource.
<?php
class DeptDaoImpl implements DeptDao {

    private $dataSource;

    function DeptDaoImpl(DataSource $dataSource) {
        $this->dataSource = $dataSource;
    }
    
    function findDeptByDeptno($deptno){
        $db = $this->dataSource->getConnection();
        $result = $db->query("select * from dept where deptno = '{$deptno}';"); 
        $row = $result->fetchRow();
        $this->dataSource->disconnect($db);
        return $row;
    }
}
?>
Return value of DataSource->getConnection() is as follows:
When using -Pear DB : return value of DB::connect()
When using -ADOdb : return value of NewADOConnection()
When using -MySQL : return value of mysql_connect()
When using -PostgreSQL : return value of pg_connect()
Which database connection string to use is specified in the following dicon file.

Create dicon file

Create a dicon file to use Pear DB. Specify peardb.dicon in an include tag.

XML format deptDao.dicon

<components>
    <include path="%S2CONTAINER_PHP5%/org/seasar/extension/db/peardb.dicon"/>
    <component name ="deptDao" class="DeptDaoImpl"/>
</components>

INI format deptDao.ini

[components]
include{0} = "%S2CONTAINER_PHP5%/org/seasar/extension/db/peardb.dicon"

[deptDao]
class="DeptDaoImpl"

Create execution script

  • Create a container using configuration file deptDao.dicon
  • Get deptDao component from the container
  • Invoke findDeptByDeptno to query on DEPTNO=10

dataSourceClient.php

<?php
require_once(dirname(__FILE__) . '/db.inc.php');
$PATH = EXAMPLE_DIR . "/extension/db/deptDao.dicon";
//$PATH = EXAMPLE_DIR . "/extension/db/deptDao.ini";

$container = S2ContainerFactory::create($PATH);
$dao = $container->getComponent('deptDao');
$result = $dao->findDeptByDeptno(10);

print_r($result);
?>

Result

% php dataSourceClient.php
Array
(
    [0] => 10
    [1] => ACCOUNTING
    [2] => NEW YORK
    [3] => 0
)
%

Use TxIntereceptor

Use TxInterceptor to manage database transaction.
Files in this example is available in the s2container.php5/src/examples/extension/db/ folder.

Interface Definition

  • Define DeptDao interface
  • Define findDeptByDeptno method that inquires DEPTNO.
<?php
interface DeptDao {
    function findDeptByDeptno($deptno);
}
?>

Create execution class

  • Create TxDeptDaoImpl class that implements DeptDao interface
  • Set a constructor to take DBSession as an argument
  • Implement findDeptByDeptno method. Obtain database connection from DBSession
Database connection, close, transaction begin, and commit is done by TxInterceptor. Rollback when an exception is thrown.
<?php
class TxDeptDaoImpl implements DeptDao {

    private $session;

    function TxDeptDaoImpl(DBSession $session) {
        $this->session = $session;
    }
    
    function findDeptByDeptno($deptno){
        $db = $this->session->getConnection();
        $result = $db->query("select * from dept where deptno = '{$deptno}';"); 
        $row = $result->fetchRow();

        return $row;
    }
}
?>
Return value of DBSession->getConnection() is as follows:
When using -Pear DB : return value of DB::connect()
When using -ADOdb : return value of NewADOConnection()
When using -MySQL : return value is mysql_connect()
When using -PostgreSQL : return value is pg_connect()


Creating a dicon file

Use a dicon file for Pear DB. Specify peardb.dicon in an include tag. Aspect dbtx(PearDBTxInterceptor) to the findDeptByDeptno method.

XML format txDeptDao.dicon

<components>
    <include path="%S2CONTAINER_PHP5%/org/seasar/extension/db/peardb.dicon"/>
    <component name ="deptDao" class="TxDeptDaoImpl">
        <aspect pointcut="findDeptByDeptno">
            dbtx
        </aspect>
    </component>
</components>

INI format txDeptDao.ini

[components]
include{0} = "%S2CONTAINER_PHP5%/org/seasar/extension/db/peardb.dicon"

[deptDao]
class="TxDeptDaoImpl"
aspect{0}{pointcut} = "findDeptByDeptno"
aspect{0}{ref} = "dbtx"

Create execution script

  • Create a container using configuration file txDeptDao.dicon
  • Get deptDao component from the container
  • Invoke findDeptByDeptno to query on DEPTNO=10

txClient.php

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

$PATH = EXAMPLE_DIR . "/extension/db/txDeptDao.dicon";
//$PATH = EXAMPLE_DIR . "/extension/db/txDeptDao.ini";

$container = S2ContainerFactory::create($PATH);
$dao = $container->getComponent('deptDao');
$result = $dao->findDeptByDeptno(10);
print_r($result);
?>

Result

% php dataSourceClient.php
Array
(
    [0] => 10
    [1] => ACCOUNTING
    [2] => NEW YORK
    [3] => 0
)
%

Using SimpleDaoInterceptor (This function has moved to S2Dao.PHP5)

Define interfaces and connect to a database using SQL statement
Files in this example is available in the s2container.php5/src/examples/extension/db/ folder.

When an array is returned

Retrieve result of database query in an array

Interface definition

  • Define DeptDao interface
  • Define findDeptByDeptno method to query by DEPTNO
  • Set SQL statement using constant annotation (QUERY)
<?php
interface DeptDao {
    const findDeptByDeptno_QUERY = 'select * from dept where deptno = \'{$deptno}\'';
    //const findDeptByDeptno_FILE = '%PATH_PREFIX%/path/to/sql/file';
                                                  // specify external SQL file
    function findDeptByDeptno($deptno);
}
?>

There are 3 methods to specify SQL statement that is executed by a method:
- write SQL statement directly in constant annotation : method name_QUERY (above sample)
- Reference external file in constant annotation : method name_FILE (path of the external file is specified between %...%)
- Create file with SQL statements that is named interface name_method name.sql in the same folder as the interface definition file.

Create implementation class

There isn't any implementation class in this example

Create a dicon file

Create a dicon file for to connect with ADOdb。include adodb.dicon file by specifying in an include tag. Apply aspect dao(SimpleDaoInterceptor) to findDeptByDeptno method.

XML format simpleDeptDao.dicon

<components>
    <include path="%S2CONTAINER_PHP5%/org/seasar/extension/db/adodb.dicon"/>
    <component name ="deptDao" class="DeptDao">
        <aspect pointcut="findDeptByDeptno">
            dao
        </aspect>
    </component>
</components>

INI format simpleDeptDao.ini

[components]
include{0} = "%S2CONTAINER_PHP5%/org/seasar/extension/db/adodb.dicon"

[deptDao]
class="DeptDao"
aspect{0}{pointcut} = "findDeptByDeptno"
aspect{0}{ref} = dao

Create execution script

  • Create a container using configuration file simpleDeptDao.dicon
  • Get deptDao component from the container
  • Invoke method findDeptByDeptno with DEPTNO(10)

simpleClient.php

<?php
require_once(dirname(__FILE__) . '/db.inc.php');
$PATH = EXAMPLE_DIR . "/extension/db/simpleDeptDao.dicon";
//$PATH = EXAMPLE_DIR . "/extension/db/simpleDeptDao.ini";

$container = S2ContainerFactory::create($PATH);
$dao = $container->getComponent('deptDao');
$result = $dao->findDeptByDeptno(10);
print_r($result);
?>

Result

% php simpleClient.php
Array
(
    [0] => Array
        (
            [0] => 10
            [1] => ACCOUNTING
            [2] => NEW YORK
            [3] => 0
        )
)
%

When an object is returned

Get property value as an object referencing result from database query

Interface definition

  • Define interface BeanDeptDao
  • Define findDeptByDeptno method that searches by column DEPTNO
  • Set DTO(Data Transfer Object) using constant annotation (BEAN)
<?php
interface BeanDeptDao {
    const BEAN = "DeptDto";
    function findDeptByDeptno($deptno);
}
?>

There are 3 methods to specify a SQL statement:
- write SQL statement directly in constant annotation : method name_QUERY
- Reference external file in constant annotation : method name_FILE (path of the external file is specified between %...%)
- Create file with SQL statements that is named interface name_method name.sql in the same folder as the interface definition file.

This example uses the third method and creates BeanDeptDao_findDeptByDeptno.sql file containing SQL statement to be executed by findDeptByDeptno and places this file in the same folder as BeanDeptDao interface definition file.

select * from dept where deptno = '{$deptno}'

Create execution class

Create DTO specified by constant annotation (BEAN)

<?php
class DeptDto {
    private $deptno;
    private $dname;
    
    function DeptDto() {}
    
    function setDeptno($deptno){
    	$this->deptno = $deptno;
    }
    function getDeptno(){
        return $this->deptno;	
    }
    
    function setDname($dname){
    	$this->dname = $dname;
    }
    function getDname(){
        return $this->dname;
    }

    function __toString(){
    	$str = "deptno = " . $this->deptno . ", dname = " . $this->dname;
        return $str;	
    }
}
?>

Set DTO property specified by constant annotation (BEAN) to column name, DTO property, and setter method name that are result of a database query.

Association between column name, property, and setter method name
Column name Property name Setter method name
deptno deptno setDeptno
dept_no deptNo setDeptNo
DEPTNO deptno setDeptno
DEPT_NO deptNo setDeptNo

Create dicon file

Create a dicon file for ADOdb. include adodb.dicon by specifying the name in the include tag. Apply aspect dao(SimpleDaoInterceptor) to findDeptByDeptno method. (When using Pear DB, include peardb.dicon file instead.)

XML format simpleDeptDao.dicon

<components>
    <include path="%S2CONTAINER_PHP5%/org/seasar/extension/db/adodb.dicon"/>
    <component name ="beanDeptDao" class="BeanDeptDao">
        <aspect pointcut="findDeptByDeptno">
            dao
        </aspect>
    </component>
</components>

INI format simpleDeptDao.ini

[components]
include{0} = "%S2CONTAINER_PHP5%/org/seasar/extension/db/adodb.dicon"

[beanDeptDao]
class="BeanDeptDao"
aspect{0}{pointcut} = "findDeptByDeptno"
aspect{0}{ref} = dao

Create execution script

  • Create a container using configuration file simpleDeptDao.dicon
  • Get component beanDeptDao from the container
  • Invoke findDeptByDeptno method with setting DEPTNO(10)

simpleBeanClient.php

<?php
require_once(dirname(__FILE__) . '/db.inc.php');
//$PATH = EXAMPLE_DIR . "/extension/db/simpleDeptDao.dicon";
$PATH = EXAMPLE_DIR . "/extension/db/simpleDeptDao.ini";

$container = S2ContainerFactory::create($PATH);
$dao = $container->getComponent('beanDeptDao');
$result = $dao->findDeptByDeptno(10);
print_r($result);
?>

Result

% php simpleBeanClient.php
Array
(
    [0] => DeptDto Object
        (
            [deptno:private] => 10
            [dname:private] => ACCOUNTING
        )

)
%

Values are set to properties deptno and dname in DeptDto class. Columns LOC and VERSIONNO in the query result are thrown away.


S2Unit

To make testing easier, S2 has a testing framework which is an extension of PHPUnit, PHPUnit2, and SimpleTest. Followings are its major features:

  • Create S2Container automatically on each test method (testXxx)
  • register(), getComponent(), include() for S2Container are made to available
  • If there is a public field in TestCase and if there is a component in a S2Container with the same name as the field name, it is automatically set
  • After the test method ends, all automatically set variables are set to null
  • If a test method (testXxx) is defined that corresponds to setUpXxx() and tearDownXxx(), it is invoked after setUp() and before testDown(). Initialization and clean up processes may be specified here.

Example

Use S2PHPUnit2TestCase that extends Pear PHPUnit2.
Files in this example is available in the s2container.php5/src/examples/extension/unit/ folder.

Target of the test

s2container.php5/src/examples/extension/unit/src/foo/FooLogic.class.php

<?php
class FooLogic {

    function FooLogic() {
    }
    
    function showYear(){
    	return 2005;
    }
}
?>

dicon file

s2container.php5/src/examples/extension/unit/src/foo/foo.ini

[logic]
class = FooLogic

Test class

s2container.php5/src/examples/extension/unit/test/phpunit2/foo/FooLogicTest.class.php

<?php
class FooLogicTest extends S2PHPUnit2TestCase {

    public $logic;   //--- if there is a definition of component named "logic"
                     //--- in a dicon file, it is set
    
    function __construct($name) {
    	parent::__construct($name);
    }

    function setUp(){
        $this->includeDicon(UNIT_EXAMPLE . "/src/foo/foo.ini");   //--- include dicon file
    }

    function testShowYear(){
        $year = $this->logic->showYear();
        $this->assertEquals($year , 2005);	
    }
}
?>

s2container.php5/src/examples/extension/unit/test/phpunit2/foo/FooAllTest.class.php

<?php
class FooAllTest {

    public static function main() {
        PHPUnit2_TextUI_TestRunner::run(self::suite());
    }
    
    public static function suite() {
        $suite = new PHPUnit2_Framework_TestSuite();
        $suite->addTestSuite('FooLogicTest');
        return $suite;  
    }
}
?>

Execution script

s2container.php5/src/examples/extension/unit/test/phpunit2/foo/fooTests.php

<?php
define('PHPUnit2_MAIN_METHOD', 'FooAllTest::main()');
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/TextUI/TestRunner.php';

FooAllTest::main();
?>

Result

% php allTests.php

TEST : FooLogicTest::testShowYear
------------------------------------

.

Time: 0.368033

OK (1 test)
%

Similar examples using Pear PHPUnit and SimpleTest are available in the s2container.php5/src/examples/extension/unit/test/ folder.