Zend_Tool for the Developer – Part 2
So, now you have seen how to add commands to the zf tooling system, but what if you want to interact with a “Project”. I guess the bigger question to answer is, what is a “Project”? In general, a “project” is a planned endeavor or an initiative. In the computer world, projects generally are a collection of resources. These resources can be files, directories, databases, schemas, images, styles, and more.
This same concept applies to Zend Framework projects. In ZF projects, you have controllers, actions, views, models, databases and so on and so forth. In terms of Zend_Tool, we need a way to track these types of resources – thus Zend_Tool_Project.
Zend_Tool_Project is capable of tracking project resources throughout the development of a project. So, for example, if in one command you created a controller, and in the next command you wish to create an action within that controller, Zend_Tool_Project is gonna have to *know* about the controller file you created so that you can (in the next action), be able to append that action to it. This is what keeps our projects up to date and *stateful*.
Another important point to understand about projects is that typically, resources are organized in a hierarchical fashion. With that in mind, Zend_Tool_Project is capable of serializing the current project into a internal representation that allows it to keep track of not only *what* resources are part of a project at any given time, but also *where* they are in relation to one another.
So, with that out of the way, lets get to the code. In the following example, we will create a provider that will expose a command called “create mymodel” which will be a very simple model in our existing project.
As you might recall from Part 1, we first need to create a provider which we will name “My_ModelProvider”. At this point, since our new provider will be Zend_Tool_Project specific, we will extend Zend_Tool_Project’s special Provider Abstract. By extending this special provider abstract, we not only are implementing the Zend_Tool_Framework_Provider_Interface, but we are also gaining some common functionality for “Projects” that we will need.
class My_ModelProvider extends Zend_Tool_Project_Provider_Abstract
{
public function create($name)
{
}
}
As you can see, this is all we need to have in order to expose a “create” method for our “model” provider. (Remember, this file will have to exist inside your include_path, at the location My/ModelProvider.php to be found by the Zend_Tool system.)
At this point we have a provider that can be dispatched and essentially won’t do much of anything useful as you can see by the empty method body for create. Our next step is to provide that method body which will include some of our own custom logic as well as some logical that is specific to how *new resources are integrated with existing projects*.
Now is a good time to understand how projects are stored with respect to Zend_Tool_Project. To gain this insight, lets go back to our article on getting started with Zend_Tool. After the step in that article where we have run “zf create project”, lets have a look at the hidden file that was created. It can be found inside the project directory with a name of “.zfproject.xml”. It should look like this:
<?xml version="1.0"?>
<projectProfile>
<projectDirectory>
<projectProfileFile/>
<applicationDirectory>
<apisDirectory enabled="false"/>
<configsDirectory/>
<controllersDirectory>
<controllerFile controllerName="index"/>
<controllerFile controllerName="error"/>
</controllersDirectory>
<layoutsDirectory enabled="false"/>
<modelsDirectory/>
<modulesDirectory enabled="false"/>
<viewsDirectory>
<viewScriptsDirectory>
<viewControllerScriptsDirectory forControllerName="index">
<viewScriptFile scriptName="index"/>
</viewControllerScriptsDirectory>
<viewControllerScriptsDirectory forControllerName="error">
<viewScriptFile scriptName="error"/>
</viewControllerScriptsDirectory>
</viewScriptsDirectory>
<viewHelpersDirectory/>
<viewFiltersDirectory enabled="false"/>
</viewsDirectory>
<bootstrapFile/>
</applicationDirectory>
<dataDirectory enabled="false">
<cacheDirectory enabled="false"/>
<searchIndexesDirectory enabled="false"/>
<localesDirectory enabled="false"/>
<logsDirectory enabled="false"/>
<sessionsDirectory enabled="false"/>
<uploadsDirectory enabled="false"/>
</dataDirectory>
<libraryDirectory>
<zfStandardLibraryDirectory/>
</libraryDirectory>
<publicDirectory>
<publicStylesheetsDirectory enabled="false"/>
<publicScriptsDirectory enabled="false"/>
<publicImagesDirectory enabled="false"/>
<publicIndexFile/>
<htaccessFile/>
</publicDirectory>
<providersDirectory enabled="false"/>
</projectDirectory>
</projectProfile>
This file represents how Zend_Tool_Project manages resources in a hierarchical fashion. For the purposes of our example, we want to be able to add our new "Models" into the "ModelsDirectory" of this project. In addition, we want to make sure that our project understands the context of these new resources. What is meant by "context" is so that our project understands what "role" this new resources plays within our project. For example, first and foremost, our "Model" resource is a file, so the project knows that this can be written to disk when asked to. Also, it understand that its a "Model" resource, and can have some custom logic assigned to it so that is *acts* like a Model with respect to other resources.
To be able to assign a context to these resources when they are created, we will also need to create a context class that we can use.
require_once 'Zend/Tool/Project/Context/Filesystem/File.php';
class My_ModelFileContext extends Zend_Tool_Project_Context_Filesystem_File
{
protected $_modelName = null;
/**
* init() is called after resources have been assembled, this effectively
* gives a "resource" within a project a "context" to operate under.
*/
public function init()
{
$this->_modelName = $this->_resource->getAttribute('modelName');
$this->_filesystemName = ucfirst($this->_modelName) . '.php';
parent::init();
}
/**
* The name the system will use to identify resources by.
*
* @return unknown
*/
public function getName()
{
return 'ModelFile';
}
/**
* The attributes assigned to any given resource within
* a project. These aid in searching as well as distinguishing
* one resource of 'ModelFile' from another.
*
* @return unknown
*/
public function getPersistentAttributes()
{
return array(
'modelName' => $this->_modelName
);
}
/**
* getContents() will be called at creation time. This could be
* as simple as you see below or could use Zend_Tool_CodeGenerator
* for this task.
*
* @return string
*/
public function getContents()
{
$modelName = $this->_modelName;
$contents = <<<EOS
class {$modelName}
{
protected \$_data = null;
public function setData(\$data)
{
\$this->_data = \$data;
return \$this;
}
public function getData()
{
return \$this->_data;
}
}
EOS;
return $contents;
}
}
Now that we have a context, we can start interacting with our project when our create method is called. Lets walk through the code sample below line by line (there will be comments before each line).
require_once 'Zend/Tool/Project/Provider/Abstract.php';
require_once 'My/ModelFileContext.php';
class My_ModelProvider extends Zend_Tool_Project_Provider_Abstract
{
/**
* This method is static for 2 reasons:
* 1) so that its not identifiable by the framework as something to execute
* 2) so that other providers can utilize this method to be able to "create"
* model resources. For example, you might have a larger system that
* wishes to create models. Instead of proxying through the
* Zend_Tool_Framework client and calling a command, it can setup the
* environment an call this method on an already known profile.
*
* @param Zend_Tool_Project_Profile $profile
* @param string $modelName
* @return Zend_Tool_Project_Profile_Resource
*/
public static function createResource(Zend_Tool_Project_Profile $profile, $modelName)
{
if (!is_string($modelName)) {
require_once 'Zend/Tool/Project/Provider/Exception.php';
throw new Zend_Tool_Project_Provider_Exception(
'My_ModelProvider::createResource() expects \"modelName\" '
. 'is the name of a model resource to create.'
);
}
// check to see if a model already exists
$existingModelFile = $profile->search(array(
'modelsDirectory',
'modelFile' => array('modelName' => $modelName))
);
if ($existingModelFile !== false) {
require_once 'Zend/Tool/Project/Provider/Exception.php';
throw new Zend_Tool_Project_Provider_Exception(
'A model file named ' . $modelName
. ' already exists within the models directory.'
);
}
$newModel = $profile->createResourceAt(
'modelsDirectory', // where to create at
'ModelFile', // what to create
array('modelName' => $modelName) // attrs to initiate with
);
return $newModel;
}
/**
* This method returns the classes to load as contexts. Since this provider
* is creating 'ModelFile', it will need this context
*
* @return array
*/
public function getContextClasses()
{
return array('My_ModelFileContext');
}
/**
* This it the method exposed to the Zend_Tool_Framework client. Once
* a request is parsed, this method is executed. As you can see, this
* method allows for pretendability.
*
* @param string $name The name of the model
*/
public function create($name)
{
$profile = $this->_loadProfile();
$modelFile = self::createResource($profile, $name);
if ($this->_getRequest()->isPretend()) {
$this->_getResponse()->appendContent(
'Would create model at ' . $modelFile->getPath()
);
} else {
$this->_getResponse()->appendContent(
'Creating model at ' . $modelFile->getPath()
);
$modelFile->create();
$this->_storeProfile();
}
}
}
Now that we have those two file in place, its just a matter or running the command:
zf create model foo
Here is a screen shot of the above working in my local environment.
And there you go, now you have created both a provider and a project context so that you can add custom resources to an existing project! The files above can be downloaded from here. Ensure they are in your include_path and you will be able to use them.

