Creating a Simple PHP Blog in Azure

In this post, I want to walk through creating a simple Azure application that will show a few pages, leverage Blob storage, Table storage and generally get you started doing PHP on Azure development. In short, we are going to write a very simple PHP Blog engine for Azure.

To be very clear, this is not a pro blog engine and I don’t recommend using it in production. It’s a lab for you to try some things out and play with PHP on Azure development.

If you feel like cheating, all of the source code is available in the zip file at https://joshholmes.com/downloads/pablogengine001.zip.

0. Before you get started, you need to make sure that you have the PHP on Azure development environment setup. If you don’t, please follow the instructions at Easy Setup for PHP On Azure Development.

image

Create a Windows Azure Web Project

1. To accomplish this, click on File | New | Project.

2. In the New PHP Azure Web Project page, name the project PABlogEngine (for PHP on Azure Blog Engine).

3. Next you need to select Windows Azure Data Storage. Notice that we’re not selecting SQL Storage. We are going to be using Table and Blob Storage for this project.

4. Click Finish

Creating a Table Entity in PHP

The next step is to get a little setup stuff done. We are going to be using Entities to put into our Table.

1. Create a file called PABlogPost.php. This is going to contain our Entity that we are going to put into our Azure Table storage.

2. Fill out the PABlogPost.php as follows:

<?php
/*
 * Include the Windows Azure Table Storage helper class from the 
 * Windows Azure for PHP SDK
 */
require_once 'Microsoft/WindowsAzure/Storage/Table.php';

class PABlogPost extends Microsoft_WindowsAzure_Storage_TableEntity
{
    /*
     * Notice the Doctrine style comments, some of which, 
     * such as $ImageType, have typing information. 
     * Anything that doesn't have a type is stored as a string. 
     */
    
    /**
     * @azure Title
     */
    public $Title;
    
    /**
     * @azure Description
     */
    public $Description;
    
    /**
     * @azure Author
     */
    public $Author;
    
    /**
     * @azure pubDate
     */
    public $pubDate;
    
    /**
     * @azure Image
     */
    public $Image;
    
    /**
     * @azure ImageUrlOriginal
     */
    public $ImageUrlOriginal;
    
    /**
     * @azure Type
     */
    public $ImageType;
    
    /**
     * @azure Size Edm.Int64
     */
    public $ImageSize;
    
    /**
     * @azure Visible Edm.Boolean
     */
    public $Visible = false;
}
?>

Setting up the utilities

The next step is to get a couple more utilities in place prior to actually writing our application. Specifically, we have two utility functions that we need to hit on and some system wide values.

1. Create a file called utility.php

2. Add the following two values to it.

$BLOG_TABLE = "blogposts";
$BLOG_POSTS_PARTITION = "posts";

We will use these whenever we are going to be accessing the table and or partition. This will let us quickly change them in one place if we ever need to.

The next thing that we need to do is have a consistent way to create our Table Storage Client as we’re using it on more than one page.

3. Add a function called createTableStorageClient as follows:

/**
 * Create Table Storage Client for table operations using account defined in ServiceConfiguration file
 *
 * @return Microsoft_WindowsAzure_Storage_Table New storageclient for Azure Storage Table
 */ 
function createTableStorageClient()
{
  if (isset($_SERVER['USERDOMAIN']) && $_SERVER['USERDOMAIN'] == 'CIS')
  {
    $host = Microsoft_WindowsAzure_Storage::URL_CLOUD_TABLE;
    $accountName = azure_getconfig('AzureCloudStorageAccountName');
    $accountKey = azure_getconfig('AzureCloudStorageAccountKey');
    $usePathStyleUri = true;
    
    $retryPolicy = Microsoft_WindowsAzure_RetryPolicy::retryN(10, 250);
    
    $tableStorageClient = new Microsoft_WindowsAzure_Storage_Table(
                              $host,
                              $accountName,
                              $accountKey,
                              $usePathStyleUri,
                              $retryPolicy
                              );
  }
  else
  {
    $tableStorageClient = new Microsoft_WindowsAzure_Storage_Table();
  }
        
	return $tableStorageClient;
}

4. Lastly, we will need a unique identifier. I’m going to follow Maarten’s lead from the sample code that he’s produced and create a UUID with the following function.

// Generate UUID
function generateUuid($prefix = '')
{
	$chars = md5(uniqid(mt_rand(), true));
	$uuid  = substr($chars,0,8) . '-';
	$uuid .= substr($chars,8,4) . '-';
	$uuid .= substr($chars,12,4) . '-';
	$uuid .= substr($chars,16,4) . '-';
	$uuid .= substr($chars,20,12);
	return $prefix . $uuid;
}

Saving to Windows Azure Table Storage and Windows Azure Blob Storage

There are actually several steps to creating a new post. The first is gathering the information. Next is inserting the post into the table. After that, inserting any images et all into the blob storage, then updating the table if needed with that new information.

1. Create a file called CreateNewPost.php.

2. Insert any template/html code that you want to make it look decent but the primary thing that you need in this page is a form that will accept the correct data do a post back to a file called newpost.php as follows:

<form enctype="multipart/form-data" method="post" action="NewPost.php">
<input value="1048576" type="hidden" name="MAX_FILE_SIZE" />
Title:
<input name="posttitle" /><br />
Description:
<textarea rows="5" name="postdescription" type="text"></textarea><br />
Author:
<input name="postauthor" /><br />
Choose an image to upload:
<input type="file" name="imageUpload" /><br />
<br />
<input value="Create Post" type="submit" />
</form>

3. Create a new file called newpost.php

<?php
// Note that this code is NOT safe against various attacks and should be
// used for demonstrating the concepts of the application only.
// NEVER deploy to production without building correct checks!

// 1. Specify include path and include Windows Azure SDK for PHP
set_include_path( get_include_path() . PATH_SEPARATOR . $_SERVER["RoleRoot"]);
require_once 'utility.php';
require_once 'PABlogPost.php';

require_once 'Microsoft/WindowsAzure/Storage/Table.php';
require_once 'Microsoft/WindowsAzure/Storage/Blob.php';

// 2. Instantiate services and make sure table and blob container exist
$tableStorageClient = new Microsoft_WindowsAzure_Storage_Table();
if (!$tableStorageClient->tableExists($BLOG_TABLE))
{
	$tableStorageClient->createTable($BLOG_TABLE);
}

$blobStorageClient = new Microsoft_WindowsAzure_Storage_Blob();
if (!$blobStorageClient->containerExists($BLOG_TABLE))
{
	$blobStorageClient->createContainer($BLOG_TABLE);
	$blobStorageClient->setContainerAcl($BLOG_TABLE, Microsoft_WindowsAzure_Storage_Blob::ACL_PUBLIC);
}

// 3. Add a record in Windows Azure Table Storage
$newpost = new PABlogPost($BLOG_POSTS_PARTITION, generateUuid());
$newpost->Title = $_POST["posttitle"]; 
$newpost->Description = $_POST["postdescription"]; 
$newpost->Author = $_POST["postauthor"]; 
$newpost->Image = $_FILES['imageUpload']['name'];
$newpost->ImageType = $_FILES['imageUpload']['type'];
$newpost->ImageSize = $_FILES['imageUpload']['size'];
$newpost->UrlOriginal = '';
$newpost = $tableStorageClient->insertEntity($BLOG_TABLE, $newpost);

// 4. Upload the image to blob storage
$blob = $blobStorageClient->putBlob($BLOG_TABLE, $newpost->getRowKey(), $_FILES['imageUpload']['tmp_name']);

// 5. Update the post to reflect the new image URL in the table
$newpost->ImageUrlOriginal = $blob->Url;
$newpost= $tableStorageClient->updateEntity($BLOG_TABLE, $newpost);

?>
<h1>New Post up!</h1>
    
<p>
	Your post has been created. Navigate to
	<!-- 6. Show the results -->
	<a href="post.php?id=<?php echo $newpost->getRowKey(); ?>"><?php echo $newpost->Title; ?></a>
	to see your new post.
</p>

Reading from Windows Azure Table Storage

We are almost done. The last thing that we need to do is to show the results for a specific post and to modify the index.php to show all of the posts.

1. Create a new file called post.php. We already referred to this page in the newpost.php file.

2. Fill out this post.php as follows:

<?php
set_include_path( get_include_path() . PATH_SEPARATOR . $_SERVER["RoleRoot"]);
require_once 'utility.php';
require_once 'PABlogPost.php';
// 1. Include the table storage information

require_once 'Microsoft/WindowsAzure/Storage/Table.php'; // 2. Instantiate services $tableStorageClient = new Microsoft_WindowsAzure_Storage_Table(); // 3. Fetch post details $Id = $_REQUEST['id']; $post = $tableStorageClient->retrieveEntityById($BLOG_TABLE, $BLOG_POSTS_PARTITION, $Id); ?> <h1><?php echo $post->Title; ?></h1> <table border="0" cellspacing="0" cellpadding="2"> <tr> <td rowspan="2"> <img src="<?php echo $post->ImageUrlOriginal; ?>" alt="<?php echo $post->Title; ?>" /> </td> <td> <?php echo ' - Title: <b>' . $post->Title . "</b><br/>"; echo ' - Description: ' . $post->Description . "<br/>"; echo ' - Author: ' . $post->Author . "<br/>"; echo ' - Author: ' . $post->Author . "<br/>"; echo ' - pubDate ' - $post->pubDate . "<br/>"; ?> </td> </tr> </table> <a href='/'>Home</a>

There’s a couple of things to notice about this code.

First, notice that we’re not talking to blob storage in the PHP code. All we are doing is putting in a link to the blob with the $post->ImageUrlOriginal. This is because the blob storage is giving us a restful endpoint that we need.

Another thing to notice is that it’s not doing any checks for exceptions. You will want to do that in your code.

3. The last thing that we need to do is the index.php. Fill it out as follows:

<?php
set_include_path(get_include_path() . PATH_SEPARATOR . $_SERVER["RoleRoot"] . "\\approot\\");

require_once 'utility.php';
require_once 'PABlogPost.php';
/**
 * Refer PHP Azure SDK library files for Azure Storage Services Operations
 */
require_once 'Microsoft/WindowsAzure/Storage.php';
require_once 'Microsoft/WindowsAzure/Storage/Table.php';

$tableStorageClient = createTableStorageClient();

if ($tableStorageClient->tableExists($BLOG_TABLE))
{
	/**
	 * Performing queries. Notice that we are not using a filter. 
	 */
	$posts = $tableStorageClient->retrieveEntities(
		$BLOG_TABLE,
		'',
		'PABlogPost'
	);

	echo "Blog posts:<br />";
	foreach ($posts as $post)
	{
		echo '<p>';
		echo '<b><a href="post.php?id=' . $post->getRowKey() . 
			'">' .$post->Title . '</a></b>';
		echo ' - Description: ' . $post->Description . "<br/>";
		echo ' - Author: ' . $post->Author . "<br/>";
		echo ' - Author: ' . $post->Author . "<br/>";
		echo '</p>';
	}
}
?>

imageConclusion

At this point, we have a very rudimentary blog written in PHP on the Azure platform that is using Table Storage and Blob Storage for all of it’s data. Couple of key points to hit on are that this is not using a traditional database.

I will very likely continue to enhance this little toy application over time as I try showing off more and more things in PHP on Azure.

Again, all of the source code is available in the zip file at https://joshholmes.com/downloads/pablogengine001.zip.

*Update* Someone was having issues with this one and emailed me. The error that they were getting was:

Fatal error: Uncaught exception ‘Microsoft_Http_Transport_Exception’ with message ‘cURL error occured during request for http://127.0.0.1:10002/devstoreaccount1/Tables?NextTableName=test: 7 – couldn’t connect to host’

Turns out that they had not started the Development Storage service so there was nothing for cURL to connect to in the first place. To start it, start the Development Fabric (the computation engine). Once the Development Fabric is up and running, you will need to right click on the Dev Fabric icon in the system tray and select Start Development Storage Service.

clip_image001

At this point you should be good to go.

BTW – if you actually play with this code, I’d love to hear about your experiences with it either in email at josh (dot) holmes (at) microsoft (dot) com or in the comments below.

Leave a Reply

Your email address will not be published. Required fields are marked *