Wednesday, February 07, 2007

Using Zend framework Components In CakePHP

原文

Before we can start baking we have to do some preparations. First of all, this article is for those who are at least familiar with using CakePHP at a very basic level. If you are not yet introduced to CakePHP, please check out the Overview on CakePHP by Fabio Cevasco here at A/R/T. Now, onto the actual preparations:

1. Make sure you installed PHP 5 as the Zend framework requires PHP 5.

2. I assume you have already installed CakePHP. If that is not the case, download it from http://cakeforge.org/projects/cakephp/ and follow the installation instructions on http://manual.cakephp.org/chapter/3. I used version 1.1.6.3264 of CakePHP for this article.

3. Download the Zend framework from http://framework.zend.com/download.

4. Copy the content of the directory ZendFramework-0.1.5/library from the zipped file to /app/vendors.

With that, the preparations are done. We are ready for baking our cake.

Baking, first iteration
In our first iteration we will build the skeleton of our application. Our goal is to get an empty page when calling our controller. Yes, I know, the result of an iteration should be something of value for the user, so this iteration is an exception.

We start with the model which we call simply “Flickr”. What functionality should our model provide? For our simple application we only need one method which returns images for a tag. So we get the following class:

Listing 1 – Toggle plain text mode – Select listing

// app/models/flickr.php
class Flickr extends AppModel
{
var $useTable = false;

function getImagesByTag($tag)
{
return array();
}
}

Notice that we have to add “var $useTable = false;” as our model will use a webservice and not a table.

Our model will be used, of course, by a controller. To make use of CakePHP's magic we name it “FlickrController”. The functionality of our controller is simple: it needs a method which gets the data from the Flickr model and hands the data over to the view. So we can write our controller:

Listing 2 – Toggle plain text mode – Select listing

// app/controllers/flickr_controller.php
class FlickrController extends AppController
{
function show($tag = null)
{
$this->set('images', $this->Flickr->getImagesByTag($tag));
}
}

The last step is the view. It is self-explanatory.

Listing 3 – Toggle plain text mode – Select listing

// app/views/flickr/show.thtml
foreach ($images as $image)
{
}

Our first iteration is almost done. Time to test what we have done. Open /flickr/show/tree in your browser and you should get an empty page. Not really interesting, is it? So we move directly to the second iteration.

Baking, second iteration
The goal of the second iteration is to retrieve images from Flickr and to display them. To include third-party libraries in CakePHP the function “vendor()” is used. We give it a try and add it to our model.

Listing 4 – Toggle plain text mode – Select listing

// app/models/flickr.php
vendor('Zend/Service/Flickr');

class Flickr extends AppModel
{
var $useTable = false;

function getImagesByTag($tag)
{
return array();
}
}

To check if the library can be included we swap to our browser and reload the page. Huch, an error:

Listing 5 – Toggle plain text mode – Select listing

Warning: require_once(Zend/Service/Rest.php) [function.require-once]
:failed to open stream: No such file or directory in
/home/dho/projects/test/app/vendors/Zend/Service/Flickr.php
on line 26

Fatal error: require_once() [function.require]:
Failed opening required 'Zend/Service/Rest.php'
(include_path='.:/usr/share/php:/usr/share/pear:
/home/dho/projects/test:/home/dho/projects/test/app/') in
/home/dho/projects/test/app/vendors/Zend/Service/Flickr.php
on line 26

Hmm, a problem with the include_path. We could add something like “ini_set('include_path',ini_get('include_path').PATH_SEPARATOR . dirname(APP.'vendors/Zend.php'));” to our model, but that is not very elegant. So we write a small helper script we can place in the vendors directory:

Listing 6 – Toggle plain text mode – Select listing

// app/vendors/zend_flickr.php
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR .
dirname(__FILE__));
require_once('Zend/Service/Flickr.php');

In our model we have to adapt the parameter for the “vendor()” function. At the same time we can implement the “getImagesByTag()” method.

Listing 7 – Toggle plain text mode – Select listing

// app/models/flickr.php
vendor('zend_flickr');

class Flickr extends AppModel
{
var $useTable = false;

function getImagesByTag($tag)
{
$result = array();

if (!empty($tag))
{
$flickr = new Zend_Service_Flickr('YOUR_API_KEY');
$result = $flickr->tagSearch($tag);
}

return $result;
}
}

In our controller we do not have to change anything, so we can move directly to our view. In our view we want to display thumbnails of the images with a link to the original image.

The handling of the results from the model is a little bit different from what we are used to with CakePHP. In CakePHP, the data are stored in multi-dimensional arrays, whereas the Zend framework uses arrays of objects.

Listing 8 – Toggle plain text mode – Select listing

// app/views/flickr/show.thtml
foreach ($images as $image)
{
if (is_object($image->Original))
{
echo $html->link($html->image($image->Thumbnail->uri),
$image->Original->uri, null, false, false);
}
}

Notice that we have to add “if (is_object($image->Original))” as for some reason this object is not always set. Now comes the moment of truth, does our application work? We swap to our browser and reload the page. You should see something like:

Time to celebrate our great application. Ehm, wait a moment. There is still some work we have to do.

Baking, third iteration
So we come to the third iteration. The goal of this iteration is to replace our helper script by a more flexible solution.

The helper script works fine for our simple application. But if we want to use multiple Zend framework components it is tedious to write such a helper script for each component we want to use. So we write a simple class to replace our helper script.

Listing 9 – Toggle plain text mode – Select listing

// app/vendors/zend_loader.php
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR .
dirname(__FILE__));

class ZendLoader
{
static function load($lib)
{
require_once('Zend'.DS.$lib.'.php');
}
}

To use our new ZendLoader class we have to make some changes in our model. We have to use “zend_loader” as parameter for the vendor function, and we have to load the component(s) we want to use in the constructor.

Listing 10 – Toggle plain text mode – Select listing

// app/models/flickr.php
vendor('zend_loader');

class Flickr extends AppModel
{
var $useTable = false;

function __construct()
{
ZendLoader::load('Service/Flickr');
}

function getImagesByTag($tag)
{
$result = array();

if (!empty($tag))
{
$flickr = new Zend_Service_Flickr('YOUR_API_KEY');
$result = $flickr->tagSearch($tag);
}

return $result;
}
}

With that we have finished our simple application. Hooray!

Conclusion
As we have seen it is relatively easy to use Zend framework components in a CakePHP application. It allows CakePHP users to profit from the growing number of Zend framework components. A minor disadvantage is that the two frameworks use partly different coding standards, and that the data handling is different: CakePHP uses multi-dimensional arrays whereas the Zend framework uses arrays of objects. But in the final analysis, integration of these two powerful frameworks is a plus for all PHP developers!

Happy baking :)

Daniel Hofstetter is a software engineer from Switzerland. He writes about his experiences using the CakePHP web framework, the CakePHP world, and everything that could be interesting for a cake baker.

No comments :