View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFDEV:Zend Proposal Zone Template}

{zone-data:component-name}
Zend_Application_Resource
{zone-data}

{zone-data:proposer-list}
[Bill Karwin|mailto:bill.k@zend.com]
{zone-data}

{zone-data:revision}
1.1 - 13 August 2007: Initial proposal.
{zone-data}

{zone-data:overview}

This is a proposal for configuration and instantiation of application-wide resources such as database connection, authentication, cache, log, and others.

The intended use cases of this solution are (1) to enable developers or tools declare some types of objects for their application in a configuration file or an array, and (2) to create concrete instances of resource classes declared in a configuration file or array, as part of an application's bootstrap logic.
{zone-data}

{zone-data:references}
* tbd
{zone-data}

{zone-data:requirements}

* The component will manage a collection of application resources.
* The component will allow a new resource to be added to the collection.
* The component will provide manager classes for a number of common resource types in Zend Framework.
* The component will provide an abstract class for resource type managers.
* The component will support class name prefixes so custom resource classes can be implemented in class heirarchies other than Zend_Application_Resource.
* The component will provide methods to create instances of one or all managed resource classes.
* The component will save instances of managed classes in the Zend_Registry, unless requested not to do so.
* The resource classes will support Zend_Config and interpret a Zend_Config object to use when instantiating a managed class.
* The resource classes will define conventions for recognized config properties.
* The resource classes will support a default configuration for their managed class.
* The resource classes will return the final config object used to instantiate its managed class.

{zone-data}

{zone-data:dependencies}

* Zend_Config
* Zend_Exception
* Zend_Loader
* Zend_Registry

{zone-data}

{zone-data:operation}

We use the term *resource* in this proposal to mean an instance of a Zend Framework component that is likely to be used in an application-wide manner. It is recommended to create resources in the bootstrap script of an MVC application, to make these objects available globally. We can achieve this by storing the objects in the Zend_Registry. It may be convenient to create the resource objects based on values declared in an application's configuration file.

The Zend_Application_Resource component functions as a collection of resource specifications, and also as a factory for resources based on those specifications.

h3. Specifying resources

You can add resource specifications to an object of the Zend_Application_Resource class, with the set($id, $type, $config) method. The first argument is an identifier for the resource.

The second argument is a string that names the type of resource. A class of the name {{Zend_Application_Resource_$type}} should exist, where $type is the string containing the type argument, transformed to initial-capital format. The class must be a subclass of Zend_Application_Resource_Abstract.

The third argument is an optional Zend_Config object. This is passed to the constructor of the resource-type class, and that class is responsible for interpreting the config object. The config is used during instantiation or configuration of the corresponding object.

{code}
$res = new Zend_Application_Resource();
$res->set('db-1', 'db', $config);
{code}

If no class can be found corresponding to the type argument, the set() method throws an exception. There is no restriction on the key format, except that it must be a string legal to use as a key in a PHP associative array. But see below for the convention of key format used in configuration file processing.

h3. Using configuration files

The Zend_Application_Resource class also has a method setConfig($config), which iterates recursively through its Zend_Config argument and finds entries that correspond to resource plugin classes. A key must match the pattern of two strings separated by a dash (e.g. "foo-1").

The first part of this pattern corresponds to a class name for a resource manager class. The class must exist and be loadable with the current application's include_path. For example, if the key is "foo-1", then the resource manager class is Zend_Application_Resource_Foo. The setConfig() method calls $this->set('foo-1', 'foo', $subConfig), where $subConfig is the subtree of the config heirarchy rooted at the current config entry.

The second part of the key is arbitrary and serves as a way to identify resources uniquely. This part of the key is not required to be numeric, though the examples in this proposal show it as numeric. It can be virtually any string of characters except for whitespace, '=' or '.'.

The dash character is used as a separator because this is permitted in configuration files but it is not a normal part of a PHP class identifier.

config.ini:

{code}
[production]
myapp.db-1.adapter = mysqli
myapp.db-1.params.hostname = localhost
myapp.db-1.params.dbname = test
myapp.db-1.params.username = root
myapp.db-1.params.password = secret
{code}

Code:

{code}
$config = new Zend_Config_Ini('config.ini', 'production');
$res = new Zend_Application_Resource();
$res->setConfig($config);
{code}

You can also pass the $config argument to the constructor. This processes the config object in an identical way to using the setConfig() method.

{code}
$res = new Zend_Application_Resource($config);
{code}

h3. Instantiating resources

After your resource object has all the resource specifications you need, you can use the create() method or createAll() method to request that the resource specifications be used to instantiate resource objects. The create() method accepts a string argument that names the key of one resource.

{code}
$config = new Zend_Config_Ini('config.ini', 'production');
$res = new Zend_Application_Resource($config);
$success = $res->create('foo-1');
{code}

This calls the create() method in the corresponding resource manager class, which is responsible for interpreting the Zend_Config object and using it to create an instance of the proper concrete object. Thus the config properties recognized must be defined and documented by convention for each managed class.

The createAll() method iterates over all known resources keys and creates them all.

{code}
$success = $res->createAll();
{code}

By default, the create() and createAll() methods store the created objects in the Zend_Registry, using the keys by which the resources are identified.

Each method accepts an optional boolean argument. If this argument is false, the objects are created without saving them in the registry.

{code}
$success = $res->createAll(false);
{code}

You can also fetch the created resource object with the get($key) method.

{code}
$object = $res->get('foo-1');
{code}

h3. Configuration file life cycle

You can load a config file, add a new resource, and return the config object containing the previous data combined with the new resource entry. Use the getConfig() method to retrieve the Zend_Config object.

{code}
$config = new Zend_Config_Ini('config.ini', 'production');
$res = new Zend_Application_Resource($config);
$success = $res->set('db-1', 'db', $dbConfig);
$newConfig = $res->getConfig();
{code}

The Zend_Config object returned should be of the same class type as the original config object used to initialize the resource data. If no initial config object was given, then the default class type is Zend_Config_Ini.

{note}
A Zend_Config object should have some method to render its data as a config file in the appropriate format. Each subclass of Zend_Config should implement this method. That is, Zend_Config_Ini::dump() returns a string that is the content of a config.ini file, and Zend_Config_Xml::dump() returns a string that is the content of a config.xml file. In the case of the base Zend_Config class, the dump() method might be a synonym for toArray().
{note}

h3. Proposed resource wrappers for Zend Framework components

Some components in Zend Framework are natural choices for creating Zend_Application_Resource wrapper classes to instantiate them based on config data. The scope of this proposal includes defining config property conventions and implementing resource classes for the following Zend Framework components:

* Zend_Acl
* Zend_Cache
* Zend_Controller_Front
* Zend_Db
* Zend_Filter_Input
* Zend_Http_Client
* Zend_Log
* Zend_Translate

Other Zend Framework component may be given resource wrapper classes in the future.

h3. Creating custom resource adapters

A new resource type "{{bar}}" can be created in a class Zend_Application_Resource_Bar, which extends the abstract class Zend_Application_Resource_Abstract.

You can also define the class in another class prefix, and then declare the namespace to Zend_Application_Resource with the addNamespace($namespace) method.

{code}
$res->addNamespace('MyApp_Resource');
$res->set('bar-1', 'bar');
// searches for MyApp_Resource_Bar,
// then Zend_Application_Resource_Bar.
{code}

h3. Supporting module-oriented MVC applications

Zend Framework's MVC architecture includes support for _modules_ which are reusable sets of application functionality, complete with controllers, models, and views. Since these modules are reusable, they should be easy to drop into a modular MVC application without conflicting with other modules.

The solution here is to create a new Zend_Registry object for each module, and store this registry as an object in the singleton instance of Zend_Registry. Then use the sub-registry object with Zend_Application_Resource to store objects with confidence that the key names won't conflict with key names used by another module.

Zend_Application_Resource has a method setRegistry(Zend_Registry $registry) to support this. You can specify the registry object in which managed resources will be saved. If you don't specify a registry object, it defaults to use the singleton instance returned by Zend_Registry::getInstance().

It is recommended that an application module create a new registry object and save it in the singleton registry using the name of the module as the key. Then use the new registry object with the Zend_Application_Resource::setRegistry() method, so that subsequent objects are saved in this new registry.

{code}
$registry = new Zend_Registry();
Zend_Registry::set('modulename', $registry);
$res->setRegistry($registry);
{code}

In a module's application code, you need to use one extra layer of indirection to retrieve objects saved in that module's respective registry.

{code}
$db = Zend_Registry::get('modulename')->get('db-1');
{code}

{zone-data}

{zone-data:milestones}

Milestone 1: [DONE] Publish proposal.
Milestone 2: Revise proposal, approve for Incubator development.
Milestone 3: Commit working prototype to Incubator.
Milestone 4: Commit working unit tests.
Milestone 5: Write end-user documentation.
Milestone 6: Release prototype in incubator.
Milestone 7: Revise implementation, tests, and documentation based on feedback.
Milestone 8: Merge changes from Incubator to Core.

{zone-data}

{zone-data:class-list}

* Zend_Application_Resource
* Zend_Application_Resource_Abstract
* Zend_Application_Resource_Acl
* Zend_Application_Resource_Cache
* Zend_Application_Resource_Controller_Front
* Zend_Application_Resource_Db
* Zend_Application_Resource_Filter_Input
* Zend_Application_Resource_Http_Client
* Zend_Application_Resource_Log
* Zend_Application_Resource_Translate

{zone-data}

{zone-data:use-cases}

The use cases intended to be addressed by Zend_Application_Resource are to be called from an MVC application bootstrap script, or from code prototyping tools.

|| UC01: Create an empty collection of resources ||

{code}
$res = new Zend_Application_Resource();
{code}

|| UC02: Add a resource to a collection using a config array ||

{code}
$res = new Zend_Application_Resource();
$configData = array(
'adapter' => 'mysqli',
'params' => array(
'hostname' => 'localhost',
'dbname' => 'test',
'username' => 'root',
'password' => 'secret'
)
);
$dbConfig = new Zend_Config($configData);
$res->set('db-1', 'db', $dbConfig);
{code}

|| UC03: Add a resource to a collection using a config file ||

Config.ini:

{code}
[production]
db-1.adapter = mysqli
db-1.params.hostname = localhost
db-1.params.dbname = test
db-1.params.username = root
db-1.params.password = secret
{code}

Code:

{code}
$res = new Zend_Application_Resource();
$config = new Zend_Config('config.ini', 'production');
$res->set('db-1', 'db', $config->{'db-1'});
{code}

|| UC04: Create a collection of resources from an array ||

{code}
$configData = array(
'db-1' => array(
'adapter' => 'mysqli',
'params' => array(
'hostname' => 'localhost',
'dbname' => 'test',
'username' => 'root',
'password' => 'secret'
)
)
);
$config = new Zend_Config($configData);
$res = new Zend_Application_Resource($config);
{code}

|| UC05: Create a collection of resources from a config file ||

{code}
$config = new Zend_Config('config.ini', 'production');
$res = new Zend_Application_Resource($config);
{code}

|| UC06: Create a single resource object and save it in the registry ||

{code}
$config = new Zend_Config('config.ini', 'production');
$res = new Zend_Application_Resource($config);
$success = $res->create('db-1');
{code}

|| UC07: Create a single resource object and retrieve it from the registry ||

{code}
$config = new Zend_Config('config.ini', 'production');
$res = new Zend_Application_Resource($config);
$success = $res->create('db-1');
$object = Zend_Registry::get('db-1');
{code}

|| UC06: Create a single resource object, don't save it in the registry, retrieve the object ||

{code}
$config = new Zend_Config('config.ini', 'production');
$res = new Zend_Application_Resource($config);
$success = $res->create('db-1', false);
$object = $res->get('db-1');
{code}

|| UC06: Create all resource objects in the collection ||

{code}
$config = new Zend_Config('config.ini', 'production');
$res = new Zend_Application_Resource($config);
$success = $res->createAll();
{code}

|| UC08: Retrieve the combined config object ||

{code}
$config = new Zend_Config('config.ini', 'production');
$res = new Zend_Application_Resource($config);
$res->set('foo-1', 'foo', $fooConfig);
$newConfig = $res->getConfig();
{code}

|| UC09: Define and use a custom resource type ||

{code}
class MyResource_Bar extends Zend_Application_Resource_Abstract
{
public function create(){ /* impl */ }
public function getDefaultConfig() { /* impl */ }
}

$res = new Zend_Application_Resource();
$res->addNamespace('MyResource');

// instantiate MyResource_Bar
// and pass $barConfig to its constructor
$res->set('bar-1', 'bar', $barConfig);

// instantiate the object managed by MyResource_Bar
// and save it in the registry
$res->create('bar-1');

// get concrete object
$barObject = $res->get('bar-1');

// get Zend_Config object including bar resource spec
$newConfig = $res->getConfig();
{code}

|| UC10: Using Zend_Application_Resource in a bootstrap script ||

{code}
<?php
// index.php

$appDir = dirname(dirname(__FILE__)) . '/app';
$config = new Zend_Config_Ini("$appDir/etc/config.ini", 'production');
$res = new Zend_Application_Resource($config);
$res->createAll();

Zend_Controller_Front::run("$appDir/controllers");
{code}

|| UC11: Managing resources for multiple modules in a bootstrap script ||

{code}
<?php
// index.php

$appDir = dirname(dirname(__FILE__)) . '/app';

$front = Zend_Controller_Front::getInstance();
$front->setModuleDirectory("$appDir/modules");

// Declare which section to load from config files.
$configSection = 'production';

// Process all config.ini files in module dirs.
// NOTE: look for a separate proposal for a
// solution that handles this logic.
$it = new DirectoryIterator("$appDir/modules");
while ($it->valid()) {
if (!$it->isDir()) { continue; }

// create a registry for the current module
$registry = new Zend_Registry();
Zend_Registry::set($it->getBasename(), $registry);

$configPath = $it->getPathname() . '/etc/config.ini';
if (is_file($configPath)) {
$config = new Zend_Config_Ini($configPath, $configSection);

// save config data in registry
$registry->set('config', $config);

// instantiate resources specified in config file
// and save objects in the module-specific registry
$res = new Zend_Application_Resource($config);
$res->setRegistry($registry);
$res->createAll();
}
}

$front->dispatch();
{code}

In your application code, you can access an entry 'db-1' in a module-specific registry in the following way:

{code}
class SomeController extends Zend_Controller_Action
{
/**
* @var Zend_Db_Adapter_Abstract
*/
protected $_db;

public function init()
{
$mod = $this->getRequest()->getModuleName();
$this->_db = Zend_Registry::get($mod)->get('db-1');
}
}
{code}

{zone-data}

{zone-data:skeletons}

{code}
class Zend_Application_Resource
{
/**
* List of class name prefixes to search.
*
* @var array $_namespaces
*/
protected $_namespaces = array('Zend_Application_Resource');

/**
* Associative array of resources. The keys of this array
* are property names from the configuration data.
*
* Each value is an associative array with the
* following keys:
*
* 'resource' => Zend_Application_Resource_Abstract object
* 'object' => concrete object set after calling the
* resource's create() method
*
* @var array $_resources
*/
protected $_resources = array();

/**
* Defaults to the singleton Zend_Registry::getInstance().
*
* @var Zend_Registry $_registry
*/
protected $_registry = null;

/**
* @param Zend_Config $config
*/
public function __construct(Zend_Config $config = null);

/**
* @param string $key
* @param string $type
* @param Zend_Config $config OPTIONAL
* @return bool
*/
public function set($key, $type, Zend_Config $config = null);

/**
* @param Zend_Config $config
* @return bool
*/
public function setConfig(Zend_Config $config);

/**
* @param string $key
* @param bool $saveInRegistry
* @return bool
*/
public function create($key, $saveInRegistry = true);

/**
* @param bool $saveInRegistry
* @return bool
*/
public function createAll($saveInRegistry = true);

/**
* @param string $key
* @return mixed
*/
public function get($key);

/**
* @return Zend_Config
*/
public function getConfig();

/**
* @param string $namespace
* @return void
*/
public function addNamespace($namespace);

/**
* @param Zend_Registry $registry
* @return void
*/
public function setRegistry(Zend_Registry $registry);

/**
* @param string $type
* @return Zend_Application_Resource_Abstract
*/
protected function _getResourceClass($type);
}
{code}

{code}
abstract class Zend_Application_Resource_Abstract
{
/**
* @var Zend_Config $_config;
*/
protected $_config = null;

/**
* Create a resource manager for one resource.
* Record the $config object to be used later.
*
* @param Zend_Config
*/
public function __construct(Zend_Config $config = null)
{
if ($config === null) {
$config = $this->getDefaultConfig();
}
$this->_config = $config;
}

/**
* Create a concrete instance of the object that this
* class manages.
*
* @return mixed
*/
abstract public function create();

/**
* Return a Zend_Config object populated with appropriate
* properties and reasonable default values for this
* resource type.
*
* @return Zend_Config
*/
abstract public function getDefaultConfig();

/**
* Return the saved config object for this resource.
*
* @return Zend_Config
*/
public function getConfig()
{
return $this->_config;
}

}
{code}

{code}
class Zend_Application_Resource_Db extends Zend_Appliation_Resource_Abstract
{

/**
* @return mixed
*/
public function create()
{
$db = Zend_Db::factory($this->_config);
return $db;
}

/**
* @return Zend_Config
*/
public function getDefaultConfig()
{
$defaults = array(
'adapter' => 'mysqli',
'params' => array(
'hostname' => 'localhost',
'dbname' => 'test',
'username' => 'root',
'password' => 'secret',
'port' => 3306,
'options' => array(),
'driver_options' => array()
)
);
$config = new Zend_Config($defaults);
return $config;
}

}
{code}

{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>