Skip to end of metadata
Go to start of metadata

<h1>Zend_Tool_Project</h1>

<h2>Goals</h2>

<p>The goals of Zend_Tool_Project is two fold:</p>
<ul>
<li>provide a suite of functionality that can build, modify, and serialize an object graph representation of various nodes/elements within a Zend Framework oriented project, also known as a "Project Profile".</li>
<li>a registry for project context nodes (ie the names of the nodes in the xml file)</li>
<li>provide a set of Zend_Tool_CommandSystem_Providers that will expose the interaction with a projects object graph to a Tooling Client through the Zend_Tool_CommandSystem.</li>
</ul>

<h2>The Serialization Format</h2>

<p>The primary serialization format needs to support recursive nodes with a varying amount of attributes further defining that nodes context. The primary format that fully meets this expectation is XML. Furthermore, PHP has basic classes in the SPL that facilitate and support XML as an object graph, specifically the RecursiveIterator. It is for this reason that the primary serialization medium will be XML. This is not to say that future formats will/can not be supported, only that XML will be the first and primary format.</p>

<p>An example of a serialized project profile:</p>

<p>This profile taken from <a href="http://framework.zend.com/wiki/display/ZFPROP/Zend+Framework+Default+Project+Structure+-+Wil+Sinclair">http://framework.zend.com/wiki/display/ZFPROP/Zend+Framework+Default+Project+Structure+-+Wil+Sinclair</a></p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

<?xml version="1.0"?>
<projectProfile>
<projectDirectory>
<projectProfileFile/>
<applicationDirectory name="application">
<apisDirectory enabled="false" />
<configDirectory enabled="false" />
<controllersDirectory name="controllers">
<controllerFile name="index"/>
<controllerFile name="error"/>
</controllersDirectory>
<modelsDirectory name="models"/>
<layoutsDirectory enabled="false" />
<modelsDirectory />
<modulesDirectory name="modules"/>
<viewsDirectory name="views">
<viewScriptsDirectory name="scripts">
<viewControllerScriptsDirectory name="index">
<viewScriptFile name="index"/>
</viewControllerScriptsDirectory>
</viewScriptsDirectory>
<viewHelpersDirectory name="helpers"/>
<viewFiltersDirectory name="filters"/>
</viewsDirectory>
<bootstrapFile name="bootstrap.php"/>
</applicationDirectory>
<dataDirectory enabled="false"/>
<libraryDirectory name="library">
<zendFrameworkStandardLibrary name="Zend"/>
</libraryDirectory>
<publicDirectory name="public">
<publicIndexFile name="index.php"/>
<htaccessFile name=".htaccess"/>
</publicDirectory>
<providersDirectory name="providers"/>
</projectDirectory>
</projectProfile>

]]></ac:plain-text-body></ac:macro>

<h2>The Object Graph</h2>

<p>The goal of the object graph (implemented as Zend_Tool_Project_ProjectProfile), is to be able to serialize and unserialize the project's structure (See above) into an object graph that can be modified in runtime. This object graph is made up of ProjectCOntext nodes, all of which extent a common abstract (Zend_Tool_Project_ProjectContextAbstract).</p>

<p>TO understand how these peices fit together, here is the class for ProjectProfile:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

<?php

class Zend_Tool_Provider_ZfProject_ProjectProfile implements IteratorAggregate
{

/**

  • @var Zend_Tool_Provider_ZfProject_ProjectContext_ProjectContextAbstract[]
    */
    protected static $_contexts = null;

protected $_projectContexts = array();

public static function getContextByName($name)
{
if (self::$_contexts === null)

Unknown macro: { self}

if (isset(self::$_contexts[$name]))

Unknown macro: { return clone self}

//Zend_Debug::dump(self::$_contexts);

die('couldnt find ' . $name);
}

/**

  • @todo public static function getContextByClass($className)
    */

/**

  • _loadContexts() - statically find and load the context files
    *
    */
    protected static function _loadContexts()
    {
    $pluginLoader = new Zend_Loader_PluginLoader(array(
    'Zend_Tool_Provider_ZfProject_ProjectContext_' => dirname(_FILE_) . '/ProjectContext/'
    ));

$classes = $pluginLoader->loadAll();

foreach ($classes as $class) {
$reflectionClass = new ReflectionClass($class);
if ($reflectionClass->isInstantiable() && $reflectionClass->isSubclassOf('Zend_Tool_Project_ProjectContextAbstract'))

Unknown macro: { $context = $reflectionClass->newInstance(); self}

}

}

public function __construct($projectProfileData = null){

Unknown macro: { $this->_unserializeXml($projectProfileData); }

public function getProjectContexts()

Unknown macro: { return $this->_projectContexts; }

public function findContext($searchContexts);

protected function _recursiveFindContext(Zend_Tool_Provider_ZfProject_ProjectContext_ProjectContextAbstract $haystackProjectContexts, $searchContextName, $searchContextParams);

public function appendProjectContext(Zend_Tool_Provider_ZfProject_ProjectContext_ProjectContextAbstract $projectContext)

Unknown macro: { $this->_projectContexts[$projectContext->getContextName()] = $projectContext; }

public function getIterator()

public function __isset($name)

Unknown macro: { return (array_key_exists($name, $this->_projectContexts)); }

public function __get($name)

Unknown macro: { return (isset($this->_projectContexts[$name]) ? $this->_projectContexts[$name] }

public function toString()

Unknown macro: { return $this->_serialize(); }

public function __toString()

Unknown macro: { return $this->toString(); }

public function create()
{
foreach ($this->_projectContexts as $projectContext)

Unknown macro: { $projectContext->create(); }

}

protected function _unserializeXml($xmlProjectProfile);

protected function _unserializeRecurser(SimpleXMLIterator $projectProfileIterator, Zend_Tool_Provider_ZfProject_ProjectContext_ProjectContextAbstract $context = null);

protected function _serialize();

protected function _serializeRecurser($context, SimpleXmlElement $xmlNode);

}

]]></ac:plain-text-body></ac:macro>

<p>And the context abstract:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

abstract class Zend_Tool_Project_ProjectContextAbstract implements RecursiveIterator, Countable

Unknown macro: { /** * @var string */ protected $_contextName = null; /** * @var int */ protected $_position = 0; /** * @var array */ protected $_subContexts = array(); /** * @var bool */ protected $_recurseSubContexts = true; protected $_persistentParameters = array('_name'); /** * @var bool */ protected $_enabled = true; protected $_isDeleted = false; abstract public function exists(); abstract public function create(); abstract public function delete(); public function setDeleted($deleted) public function isDeleted() public function setRecurseSubContexts($recurseSubContexts = true) public function getRecurseSubContexts() public function getContextName() public function setParameters(Array $parameters) public function getParameters() public function append(Zend_Tool_Project_ProjectContextAbstract $contextNode) public function hasSubContexts() public function getPersistentParameters() public function setEnabled($enabled = true) public function isEnabled() public function current() public function key() public function next() public function rewind() public function valid() public function hasChildren() public function getChildren() public function count() public function __isset($contextName) public function __get($contextName) } ]]></ac}

]]></ac:plain-text-body></ac:macro>

<h2>The Filesystem ProjectContext Nodes</h2>

<p>Assuming the above abstracts, the first order of business is to implement context nodes for the purposes of creating, modifying and deleting filesystem nodes. This includes (but is not limited to) controllers, bootstrap files, the Zend Framework library, view scripts, models and the .htaccess file to name a few. There exists a default abstract for thsi filesystem context:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

<?php

abstract class Zend_Tool_Project_ProjectContext_FilesystemAbstract extends Zend_Tool_Project_ProjectContextAbstract
{

protected $_baseDirectoryName = null;

protected $_name;

public function setBaseDirectoryName($baseDirectoryName)
{

$this->_baseDirectoryName = rtrim(str_replace('
', '/', $baseDirectoryName), '/');

if ($this->_subContexts) {
foreach ($this->_subContexts as $item)

Unknown macro: { $item->setBaseDirectoryName($this->getFullPath()); }

}

return $this;
}

public function getBaseDirectoryName()

Unknown macro: { return $this->_baseDirectoryName; }

public function setName($name)

Unknown macro: { $this->_name = $name; return $this; }

public function getName()

Unknown macro: { return $this->_name; }

public function getFullPath()
{
$fullPath = $this->_baseDirectoryName . '/';
if ($this->_name)

Unknown macro: { $fullPath .= $this->_name; }

return $fullPath;
}

public function append(Zend_Tool_Provider_ZfProject_ProjectContext_ProjectContextAbstract $contextNode)
{
parent::append($contextNode);

if ($this->_baseDirectoryName != '' && $contextNode instanceof Zend_Tool_Provider_ZfProject_ProjectContext_Filesystem_FilesystemAbstract)

Unknown macro: { $contextNode->setBaseDirectoryName($this->getFullPath()); }

}

}

]]></ac:plain-text-body></ac:macro>

<p>The nodes implemented would include the following list:</p>

<table><tbody>
<tr>
<th><p> Context Name </p></th>
<th><p> Where it Resides </p></th>
</tr>
<tr>
<td><p>ApplicationDirectory </p></td>
<td><p>Zend_Tool_Project_ContextNode_ApplicationDirectory</p></td>
</tr>
<tr>
<td><p>BootstrapFile </p></td>
<td><p>Zend_Tool_Project_ContextNode_BootstrapFile</p></td>
</tr>
<tr>
<td><p>ClassFileAbstract </p></td>
<td><p>Zend_Tool_Project_ContextNode_ClassFileAbstract</p></td>
</tr>
<tr>
<td><p>ControllerFile </p></td>
<td><p>Zend_Controller_Tool_ControllerFileContextNode</p></td>
</tr>
<tr>
<td><p>ControllersDirectory </p></td>
<td><p>Zend_Controller_Tool_ControllersDirectoryContextNode</p></td>
</tr>
<tr>
<td><p>Directory </p></td>
<td><p>Zend_Tool_Project_ContextNode_Directory</p></td>
</tr>
<tr>
<td><p>File </p></td>
<td><p>Zend_Tool_Project_ContextNode_File</p></td>
</tr>
<tr>
<td><p>HtaccessFile </p></td>
<td><p>Zend_Tool_Project_ContextNode_HtaccessFile</p></td>
</tr>
<tr>
<td><p>LibraryDirectory </p></td>
<td><p>Zend_Tool_Project_ContextNode_LibraryDirectory</p></td>
</tr>
<tr>
<td><p>ModelsDirectory </p></td>
<td><p>Zend_Tool_Project_ContextNode_ModelsDirectory</p></td>
</tr>
<tr>
<td><p>ModulesDirectory </p></td>
<td><p>Zend_Tool_Project_ContextNode_ModulesDirectory</p></td>
</tr>
<tr>
<td><p>ProjectDirectory </p></td>
<td><p>Zend_Tool_Project_ContextNode_ProjectDirectory</p></td>
</tr>
<tr>
<td><p>PublicDirectory </p></td>
<td><p>Zend_Tool_Project_ContextNode_PublicDirectory</p></td>
</tr>
<tr>
<td><p>PublicIndexFile </p></td>
<td><p>Zend_Tool_Project_ContextNode_PublicIndexFile</p></td>
</tr>
<tr>
<td><p>ViewControllerScriptsDirectory </p></td>
<td><p>Zend_View_Tool_ViewControllerScriptsDirectory</p></td>
</tr>
<tr>
<td><p>ViewFiltersDirectory </p></td>
<td><p>Zend_View_Tool_ViewFiltersDirectory</p></td>
</tr>
<tr>
<td><p>ViewHelpersDirectory </p></td>
<td><p>Zend_View_Tool_ViewHelpersDirectory</p></td>
</tr>
<tr>
<td><p>ViewScriptFile </p></td>
<td><p>Zend_View_Tool_ViewScriptFile</p></td>
</tr>
<tr>
<td><p>ViewScriptsDirectory </p></td>
<td><p>Zend_View_Tool_ViewScriptsDirectory</p></td>
</tr>
<tr>
<td><p>ViewsDirectory </p></td>
<td><p>Zend_View_Tool_ViewsDirectory</p></td>
</tr>
<tr>
<td><p>ZendFrameworkStandardLibrary </p></td>
<td><p>Zend_Tool_Project_ContextNode_ZendFrameworkStandardLibrary</p></td>
</tr>
</tbody></table>

<p>As you can see, the names of these project contexts would all extend the File (or Directory) project context, which consequently extends the FilesystemAbstract project context. You can also see that the names listed above match up with what is defined out inside our example serialization file (inside the first section of this proposal.) File and Directory would be responsible for implementing the actual calls to, for example, file_put_contents, unlink, mkdir and rmdir.</p>

<h2>The Providers</h2>

<p>To expose the manipulation of the object graph to the end user, this component makes use of the Zend_Tool_CommandSystem Provider hooks in order to extend these capabilities outward. If, for example, from the command line, a user wanted to create a new "user" controller and the resulting view script, it would be ideal if the user could do: zf create controller -n user. The way Zend_Tool_Project would expose that capability would be through implementing a provider for that action.</p>

<p>Below is the provider that would implement that command:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

<?php

class Zend_Tool_Project_ControllerProvider extends Zend_Tool_Project_ProviderAbstract
{

public function create($controllerName, $viewIncluded = true)
{

$projectProfile = $this->_getProjectProfile(); // returned from the abstract

$controllerDirectoryContext = $projectProfile->findContext('controllersDirectory');
$controllerFile = $projectProfile->getContextByName('controllerFile');
$controllerDirectoryContext->append($controllerFile);

$controllerFile->setName($controllerName);
$controllerFile->create();

if ($viewIncluded)

Unknown macro: { $viewScriptsDirectory = $projectProfile->findContext('viewScriptsDirectory'); $viewControllerScriptsDirectory = $projectProfile->getContextByName('viewControllerScriptsDirectory'); $viewScriptsDirectory->append($viewControllerScriptsDirectory); $viewControllerScriptsDirectory->setName($controllerName); $viewControllerScriptsDirectory->create(); $viewScriptFile = $projectProfile->getContextByName('viewScriptFile'); $viewControllerScriptsDirectory->append($viewScriptFile); $viewScriptFile->create(); }

$projectProfileFile = $projectProfile->findContext('projectProfileFile');
$projectProfileFile->refresh();

$this->_response->setContent('Controller \'' . $controllerName . '\' has been created' . (($viewIncluded) ? ' with proper view scripts.' : '.'));
}

public function delete($controllerName, $viewIncluded = true)
{
$projectProfile = $this->_getProjectProfile();
$controllerFile = $projectProfile->findContext(array('controllerFile' => array('name' => $controllerName)));
$controllerFile->delete();

if ($viewIncluded)

Unknown macro: { $viewScriptsDirectory = $projectProfile->findContext(array('viewControllerScriptsDirectory' => array('name' => $controllerName))); $viewScriptsDirectory->delete(); }

$projectProfileFile = $projectProfile->findContext('projectProfileFile');
$projectProfileFile->refresh();

$this->_response->setContent('Controller \'' . $controllerName . '\' has been deleted' . (($viewIncluded) ? ' with proper view scripts.' : '.'));
}

}

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.