Zend SimpleCloud and Azure

image I’ve been playing with Zend’s SimpleCloud API for the webcast that I’m doing with Zend today. I started with the Zend Framework Quickstart tutorial but changed out the backend to hit the Azure Tables and such (well kinda – I used Zend Studio 8 Beta 2 and didn’t use the ZF tool but I still created a little guestbook). I’m going to expand this example to include blob storage and queues as well in the near future but at the moment, I’m just going to hit the Azure Tables.

update – here are the slides for the presentation:

To get started, I downloaded and installed the Zend Framework CE 1.10 and Zend Studio 8 Beta 2. Then I downloaded and installed the Windows Azure SDK. imageThe last bit that I needed was the Windows Azure 4 Eclipse which will install inside of Zend Studio since it’s built on Eclipse. To install it, open up Zend Studio/Eclipse and select Help | Install New Software to open up the dialog. Then click Add… and fill in the location as http://www.windowsazure4e.org/update. Click OK, select the Windows Azure for Eclipse Toolkit and follow the rest of the wizard to install it. At this point, I’ve got all of the software installed that I need to install and am ready to start coding.

Creating the Project

Before I create the project, a quick tip is that it’s a lot easier to work with IIS if you move the your Eclipse Workspace to c:\users\public\ZendWorkspace (I’m on Windows 7 so that’s where my public documents are). One more quick step is that I give IUSER Read and Execute permissions on the workspace.

imageOnce I’ve moved my workspace, in Zend Studio, select File, New Zend Framework Project.

Name the project SimpleCloudDemo.

Select “Create new project in Workspace”. I tried creating the project on a local server to skip a few steps but that didn’t work so well as you have to be an administrator to write to the c:\inetpub\wwwroot location. Instead, we’ll just map a virtual directory in IIS in a few moments.

Make sure that Zend Framework default project structure is selected (should be the default).

Click Finish. This will create basic project structure that you’ll need to get started. The Zend Framework is a MVC style framework.

To finish setting up the project we need to include the framework bits and the API bits so that we have everything in a nice portable folder. Copy in the C:\Program Files\Zend\ZendServer\GUI\library\Zend directory to [project dir]\library.

Lastly, download the SimpleCloud Api from http://simplecloud.org/download and unzip it to the [project dir]\library directory.

Mapping the IIS Virtual Directory

Now we want to be able to test and make sure that everything is installed correctly and that the project works. To do this, we’re going to map a IIS virtual directory.

image Open Internet Information Services (IIS) Manager and expand the tree on the left hand side until you find the default web site.

Right Click on the Default Web Site and select Add Virtual Directory…

Fill out the Alias with something simple to remember such as simpleclouddemo and fill in the Physical path with the directory to [your project directory]\public. Since I moved my workspace up above, the full Physical path that I entered is c:\users\Public\ZendWorkspace\SimpleCloudDemo\public

image Now, browse to the virtual directory at  http://localhost/simpleclouddemo.

The one other thing that I’ll do that’s IIS specific is create a URL_Redirect rule that will make sure that the Zend Framework actually gets all of the calls rather than the calls just going into the IIS bit bucket. The easiest way to do that is to create a file called web.config in the public directory.

<?xml version="1.0"?>
<?configuration>
  <?system.webserver>
    <?rewrite>
      <?rules>
        <?rule name="Main URL Rewrite Rule" patternsyntax="Wildcard">
          <?match url="*" />
          <?conditions>
            <?add negate="true" input="{REQUEST_FILENAME}" matchtype="IsFile" />
            <?add negate="true" input="{REQUEST_FILENAME}" matchtype="IsDirectory" />
          <?/conditions>
          <?action url="index.php" type="Rewrite" />
        <?/rule>
      <?/rules>
    <?/rewrite>
  <?/system.webserver>
<?/configuration>

At this point you should have the project up and running. Now we’re ready to start slinging code.

Creating the Model and Azure Table

We’re going to dive right in and start creating the model and the Azure Table.

The first thing that we’re going to create is the Guestbook model class itself. Create a new PHP file named GuestBookModel.php in the /application/models directory as follows.

<?php
// application/models/GuestbookModel.php
 
class Application_Model_Guestbook extends Zend_Service_WindowsAzure_Storage_TableEntity
{
	/**
	* @azure comment Edm.String
	*/
	public $comment;	
	/**
	* @azure created Edm.String
	*/
	public $created;
	/**
	* @azure email Edm.String
	*/
	public $email;
}

Couple of things that are interesting here.

  1. Notice that the class extends (read inherits for those of you that don’t do PHP heavily) Zend_Service_WindowsAzure_Storage_TableEntity. This parent class gives us the other required fields such as a PartitionKey, RowKey, TimeStamp and the like.
  2. Next, notice the @azure comments in front of each of the variables. This gives typing information to the Azure Table storage engine for comparisons, sorting and storage optimization. The possible choices there are:
Property Type Details
Edm.Binary An array of bytes up to 64 KB in size.
Edm.Boolean A Boolean value.
Edm.DateTime A 64-bit value expressed as UTC time. The supported range of values is 1/1/1601 to 12/31/9999.
Edm.Double A 64-bit floating point value.
Edm.Guid A 128-bit globally unique identifier.
Edm.Int32 A 32-bit integer.
Edm.Int64 A 64-bit integer.
Edm.String A UTF-16-encoded value. String values may be up to 64 KB in size.

The next thing to do is create the GuestBookMapper in the application\models directory.

<?php
// application/models/GuestbookMapper.php
 
class Application_Model_GuestbookMapper
{
	protected $_cloudTable;
	protected $TABLE_NAME = "guestbook";
	protected $PARTITION_KEY = "guests";
    
    public function setCloudTable()
    {
    	//This constructor takes account parameters for the live azure account
    	//Goes to Dev Storage if you don't pass in any parameters. 
    	$tableStorageClient = new Zend_Service_WindowsAzure_Storage_Table();
		$this->_cloudTable = $tableStorageClient;
    	
        return $this;
    }
    
    public function getTableStorageClient()
    {
    	if (null == $this->_cloudTable) {
            $this->setCloudTable();
    	}
        return $this->_cloudTable;
    }
 
    public function fetchAll()
    {
    	$tableStorageClient = $this->getTableStorageClient();

        if ($tableStorageClient->tableExists($this->TABLE_NAME))
        {
    		return $tableStorageClient->retrieveEntities($this->TABLE_NAME, "", "Application_Model_Guestbook");
        }
    	return null;
    }
    
    public function save(Application_Model_Guestbook $guestbook)
    {
    	$tableStorageClient = $this->getTableStorageClient();
        if (!$tableStorageClient->tableExists($this->TABLE_NAME))
    	{
    		$tableStorageClient->createTable($this->TABLE_NAME);    		
    	}
    	
    	$guestbook->setPartitionKey($this->PARTITION_KEY);
    	if ($guestbook->getRowKey() == null) {
    		$guestbook->setRowKey(uniqid ());
    	}
    	
	$tableStorageClient->insertEntity($this->TABLE_NAME, $guestbook);
    }
  }

As you look at that code, there’s a couple of things to point out.

  1. I could pass in parameters to the constructor of the Zend_Service_WindowsAzure_Storage_Table constructor to give my account information for a Windows Azure storage account but if you don’t pass anything in it defaults to the dev storage account.
  2. In the fetchAll() function,  the second parameter being passed to the retrieveEntities function is a filter parameter. This is where you could pass in the search parameters and/or a partition to retrieve and so on. Regardless of what you pass in, the function attempts to match on those items and returns a collection of items that match.

The next thing is to create the GuestBookController.php in the application\controllers folder.

<?php
/**
 * GuestbookController
 * 
 * @author
 * @version 
 */
require_once 'Zend/Controller/Action.php';
class GuestbookController extends Zend_Controller_Action
{
    /**
     * The default action - show the home page
     */
    public function indexAction ()
    {
        $guestbook = new Application_Model_GuestbookMapper();
        $this->view->entries = $guestbook->fetchAll();
    }
    
    public function signAction()
    {
        $request = $this->getRequest();
        $form    = new Application_Form_Guestbook();
 
        if ($this->getRequest()->isPost()) {
            if ($form->isValid($request->getPost())) {
                $comment = new Application_Model_Guestbook();
                $comment->comment = $form->getValue("comment");
	       $comment->email = $form->getValue("email");
                
	       $mapper  = new Application_Model_GuestbookMapper();
                $mapper->save($comment);
                return $this->_helper->redirector('index');
            }
        }
 
        $this->view->form = $form;
    }
}

Couple of things to point out here as well.

  1. In the indexAction, the fetchAll() call returns the list of entries as a collection and  hands it off to the view.
  2. In the signAction, we’re using the Application_Form_Guestbook which we are about to create. It’s got a little bit of validation but I wouldn’t rely on just that for my business logic but I’m a belt and suspenders kind of guy when it comes to data validation.

The next step is to create the Guestbook.php in application\forms directory.

<?php
class Application_Form_Guestbook extends Zend_Form
{
    public function init()
    {
        // Set the method for the display form to POST
        $this->setMethod('post');
 
        // Add an email element
        $this->addElement('text', 'email', array(
            'label'      => 'Your email address:',
            'required'   => true,
            'filters'    => array('StringTrim'),
            'validators' => array(
                'EmailAddress',
            )
        ));
 
        // Add the comment element
        $this->addElement('textarea', 'comment', array(
            'label'      => 'Please Comment:',
            'required'   => true,
            'validators' => array(
                array('validator' => 'StringLength', 'options' => array(0, 1000))
                )
        ));
 
        // Add a captcha
        $this->addElement('captcha', 'captcha', array(
            'label'      => 'Please enter the 5 letters displayed below:',
            'required'   => true,
            'captcha'    => array(
                'captcha' => 'Figlet',
                'wordLen' => 5,
                'timeout' => 300
            )
        ));
 
        // Add the submit button
        $this->addElement('submit', 'submit', array(
            'ignore'   => true,
            'label'    => 'Sign Guestbook',
        ));
 
        // And finally add some CSRF protection
        $this->addElement('hash', 'csrf', array(
            'ignore' => true,
        ));
    }
}

The only thing to point out here is that this is a standard Zend Framework form.

The last thing that we absolutely need to do is create the two views, one for viewing and one for signing. The first one that we’ll create is index.phtml in application\views\scripts\Guestbook

<!-- application/views/scripts/guestbook/index.phtml -->
 
<p><a href="<?php echo $this->url(
array(
'controller' => 'guestbook',
'action' => 'sign' ),
'default',
true)
?>">Sign Our Guestbook</a></p> Guestbook Entries (<?php echo count($this->entries) ?>): <br /> <table>
<tbody>
<?php foreach ($this->entries as $entry): ?>
<tr>
<td><?php echo $this->escape($entry->email)?></td>
<td><?php echo $this->escape($entry->comment)?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>

And next we’ll create the sign.phtml in application\views\scripts\Guestbook.

<?!-- application/views/scripts/guestbook/sign.phtml -->
 
Please use the form below to sign our guestbook!
 
<?php
$this->form->setAction($this->url());
echo $this->form;

There are other things that we could do but that all that we need to do for the moment. I’ll extend this example in the future.

Now, if you run the application and browse to http://localhost/simpleclouddemo/guestbook

If you get the error “Application errorexception ‘Zend_Http_Client_Adapter_Exception’ with message ‘Unable to Connect to tcp://127.0.0.1:10002. Error #10060: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.” – it’s because you need to launch the Development Fabric and specifically start the storage bit.

Once that’s done, you should see the guestbook as follows:

Empty

Then you can sign the guestbook as follows:

sign

And then it will redirect you back the guestbook which will look as follows:

firstentry

This is the bulk of what I demoed during the webinar. I’m going to continue to expand this demo as time goes on

Webinar on Oct 20, 2010 with Microsoft/Zend

imageI’m looking forward to starting this new webinar series that I’m launching in partnership with Zend about running PHP in general and Zend in particular on Microsoft Azure.

My goal, as I’m writing the demos today after posting this, is to go through the Zend Guestbook quick start and then port that to run in Azure and leverage that platform. I’ll be posting the full technical write-up here once I get done.

From the Zend web site:

Join a webinar on Cloud Computing with Zend Framework and Windows Azure. In this session, we’ll take a technical overview of Windows Azure Data Storage which can be used both inside and outside of your cloud application and the Windows Azure computing which can be leveraged to scale your application horizontally. We’ll write a small application with the Zend Framework and get it up and running in Azure so that we can dive deep into the individual parts in future webinars.
Join this webinar to learn how to take your application to the next level.

Register

It’s important to note that this is the first of a series so we’re going to do an overview across the board this time and then we’ll dive deep into things such as the data access layers or architecting for scaling horizontally across multiple instances in the future.