My favorites | Sign in
Project Home Downloads Issues Source
Search
for
SampleCode  
Sample Code
Featured
Updated Oct 3, 2010 by simen...@gmail.com

Introduction

Using Substrate involves configuring an application context. At the outermost level, this means specifying one or more configuration files to load into a substrate_Context. Each context configuration file may include additional context configuration files.

Table of Contents

Hello World

For this sample, we will assume a HelloWorld class exists.

<?php
class HelloWorld {
    private $foo;
    public function __construct($foo) {
        $this->foo = $foo;
    }
    public function getFoo() {
        return $this->foo;
    }
}
?>

The Substrate context configuration file will be called helloWorld.context.php.

<?php
$context->set('helloWorldObject', array(
    'className' => 'HelloWorld',
    'constructorArgs' => array(
        'foo' => 'bar',
    ),
));
?>

Using this context configuration can be done by creating a substrate_Context instance and by specifying the hellowWorld.context.php file.

// Load the Context class
require_once('substrate_Context.php');

// Create a new Substrate context from helloWorld.context.php
$context = new substrate_Context('helloWorld.context.php');

// Execute the context (instantiates all objects that are not set to lazyLoad)
$context->execute();

// Get our object from the context.
$helloWorldObject = $context->get('helloWorldObject');

print "Hello World Object's foo? " . $helloWorldObject->foo() . "\n";

Obviously Substrate would be overkill for this trivial example. However, it is important to show that Substrate context files are really just object instantiations definitions and that nothing really fancy needs to be done for most classes to be used by Substrate.

Referencing Objects

One of the most important features of Substrate is its object referencing ability. This is how an object can be defined once and injected into other objects.

<?php

$context->set('myComplicatedService', array(
    'className' => 'MyComplicatedService',
    'constructorArgs' => array(
        // Many or complicated constructor things going on here.
    ),
));

$context->set('exampleObjectUsingComplicatedService' array(
    'className' => 'ExampleUsesComplicatedService',
    'constructorArgs' => array(
        'myComplicatedService' => $context->ref('myComplicatedService'),
    ),
));

$context->set('anotherObjectUsingComplicatedService' array(
    'className' => 'AnotherUsesComplicatedService',
    'constructorArgs' => array(
        'myComplicatedService' => $context->ref('myComplicatedService'),
    ),
));

?>

This example assumes that the MyComplicatedService class requires a lot of configuration and possibly has some dependencies of its own. In this case, instantiating new instances in each of ExampleUsesComplicatedService and AnotherUsesComplicatedService classes would result in code duplication and could turn into a huge nightmare for maintenance.

By referencing a context object(in this case by name using $context->ref('myComplicatedService')), the classes that depend on MyComplicatedService no longer need to worry about what it takes to instantiate an instance of MyComplicatedService.

Complex Sample

Substrate excels where there is a need for more complex relationships between objects and their dependencies. The following is a more complex example showing how Substrate can be used to drop alternate implementations of interfaces into just part of the application context.

In this example, we will build a simple Library. The Library will have books, accessed by a Book DAO. We want to be able to test the library functionality without having access to a database, so we need to be able to have multiple implementations of our Book DAO.

To keep things simple, the IBookDao interface has just one method, findAll(). findAll() will be expected to return an array of associative arrays with the following keys: bookId, title and checkedOut.

interface IBookDao {
    public function findAll();
}

For our database implementation, we will want to have access to a PDO object, so it is requested in the constructor.

class PdoBookDao implements IBookDao {
    private $pdo;
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    public function findAll() {
        $statement = $this->pdo->prepare('SELECT * FROM book');
        $statement->execute();
        return $statement->fetchAll(PDO::FETCH_ASSOC);
    }
}

For our test implementation, we just define a private instance array containing a static array of books.

class TestBookDao implements IBookDao {
    private $bookData = array(
        array(
            'bookId' => 1,
            'title' => 'Substrate for Dummies',
            'checkedOut' => false,
        ),
        array(
            'bookId' => 42,
            'title' => 'CSS in 12 days',
            'checkedOut' => true,
        )
    );
    public function findAll() {
        return $this->bookData;
    }
}

We are going to break out our context configuration for these two classes into separate files, each defining an object with the same name (bookDao). The idea here is that the rest of the application probably doesn't care about the actual implementation details, it is mainly going to be concerned about the interface.

The PDO implementation context configuration file will be named pdoBookDao.context.php and will import pdo.context.php to get the pdo object reference that the rest of the application will share.

<?php

//
// This is pdoBookDao.context.php
//

// We want to load our PDO context configuration directly.
$context->import('pdo.context.php');

// Define the bookDao object.
$context->set('bookDao', array(
    'className' => 'PdoBookDao',
    'constructorArgs' => array(
        'pdo' => $context->ref('pdo'),
    ),
));

?>

The test implementation context configuration file will be named testBookDao.context.php and is pretty simple. It has no other dependencies and everything is built into the object so it does not even need any constructor arguments.

<?php

//
// This is testBookDao.context.php
//

// Define the bookDao object.
$context->set('bookDao', array(
    'className' => 'TestBookDao',
));

?>

The Library class requires an object that implements IBookDao to do its work.

class Library {
    private $bookDao;
    public function __construct(IBookDao $bookDao) {
        $this->bookDao = $bookDao;
    }
    public function getAllBooks() {
        return $this->bookDao->findAll();
    }
    public function getAvailableBooks() {
        $books = array();
        foreach ( $this->bookDao->findAll() as $book ) {
            if ( ! $book['checkedOut'] ) {
                $books[] = $book;
            }
        }
        return $books;
    }
}

The Library context configuration file will be named library.context.php and will reference bookDao, but does no imports of its own. In this way, the library object will use whatever bookDao is loaded into the context by other means.

<?php
$context->set('library', array(
    'className' => 'Library',
    'constructorArgs' => array(
        'bookDao' => $context->ref('bookDao')
    )
));
?>

In the production application, we will use the PDO implementation.

// Load the Context class
require_once('substrate_Context.php');

// Create a new Substrate context from our pdoBookDao and
// library context configuration files.
$context = new substrate_Context(
    'pdoBookDao.context.php',
    'library.context.php'
);

// Execute the context (instantiates all objects that are not set to lazyLoad)
$context->execute();

// Get our object from the context.
$library = $context->get('library');

// Print out the available books.
print_r($library->getAvailableBooks());

In a testing environment, we will use the test implementation.

// Load the Context class
require_once('substrate_Context.php');

// Create a new Substrate context from our testBookDao and
// library context configuration files.
$context = new substrate_Context(
    'testBookDao.context.php',
    'library.context.php'
);

// Execute the context (instantiates all objects that are not set to lazyLoad)
$context->execute();

// Get our object from the context.
$library = $context->get('library');

// Since we are dealing with predefined data in the test implementation,
// we can make some reasonable assumptions here and test the results
// of our library calls.
$this->assertEquals(2, $library->getAllBooks());
$this->assertEquals(1, $library->getAvailableBooks());

The idea here is that the Library class and its context configuration are completely unaware of the IBookDao implementation that was used to create the bookDao object.

For the sake of completeness, the pdo.context.php file would look something like this.

Substrate provides an adapter for creating PDO objects. Some (all?) core PHP classes do not play super well with Reflection so in some cases adapters for those classes need to be built.

<?php
$context->set('pdo', array(
    'className' => 'substrate_dao_PdoAdapter',
    'constructorArgs' => array(
        'dsn' => 'mysql:dbname=myLibrary;host=localhost',
        'username' => 'libdbuser',
        'password' => 'lib328zs8',
    ),
));
?>
Comment by ionat...@gmail.com, Jan 7, 2009

hey you have an error in the PdoBookDao? class definition. It seems like you misspelled the 'return' keyword. It just a detail, nothing important. Thanks for the IoC container!

Comment by project member simen...@gmail.com, Jan 8, 2009

Updated! Thanks for the feedback. Did you download it and give it a try?

Comment by jbuhac...@gmail.com, Jan 29, 2012

The examples here show that the context is created with PHP statements... so the functionality really isn't different than just creating all the objects in PHP directly, like:

$context['myComplicatedService'] = new MyComplicatedService?(...); $context['exampleObjectUsingComplicatedService'] = new ExampleUsesComplicatedService?(

myComplicatedService=>$context['myComplicatedService'] );

I'm looking for a dependency injection framework to use in my project but from what I see in the example code here substrate doesn't actually provide me any benefits beyond what I already get "for free" in PHP. I haven't read through the source code to find out what other features you had because I'm assuming that what you advertise in the only documentation page in the project are the most important features available.

Is there a serializable format for the context? Can I write contexts in one or more YAML or XML and then instantiate and combine them them with substrate?


Sign in to add a comment
Powered by Google Project Hosting