Source for file Table.php
Documentation is available at Table.php
* Copyright (c) 2009 - 2011, RealDolmen
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of RealDolmen nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY RealDolmen ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL RealDolmen BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* @package Microsoft_WindowsAzure
* @copyright Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com)
* @license http://phpazure.codeplex.com/license
* @version $Id: Blob.php 14561 2009-05-07 08:05:12Z unknown $
* @see Microsoft_AutoLoader
require_once dirname(__FILE__ ) . '/../../AutoLoader.php';
* @package Microsoft_WindowsAzure
* @copyright Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com)
* @license http://phpazure.codeplex.com/license
* Throw Microsoft_WindowsAzure_Exception when a property is not specified in Windows Azure?
* Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET.
* Throw Microsoft_WindowsAzure_Exception when a property is not specified in Windows Azure?
* Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET.
* Throw Microsoft_WindowsAzure_Exception when a property is not specified in Windows Azure?
* Creates a new Microsoft_WindowsAzure_Storage_Table instance
* @param string $host Storage host name
* @param string $accountName Account name for Windows Azure
* @param string $accountKey Account key for Windows Azure
* @param boolean $usePathStyleUri Use path-style URI's
* @param Microsoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
public function __construct($host = Microsoft_WindowsAzure_Storage::URL_DEV_TABLE, $accountName = Microsoft_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_ACCOUNT, $accountKey = Microsoft_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_KEY, $usePathStyleUri = false, Microsoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null)
parent::__construct($host, $accountName, $accountKey, $usePathStyleUri, $retryPolicy);
// Always use SharedKeyLite authentication
* Check if a table exists
* @param string $tableName Table name
$tables = $this->listTables(); // 2009-09-19 does not support $this->listTables($tableName); all of a sudden...
foreach ($tables as $table) {
if ($table->Name == $tableName) {
* @param string $nextTableName Next table name, used for listing tables when total amount of tables is > 1000.
* @throws Microsoft_WindowsAzure_Exception
if ($nextTableName != '') {
$queryString[] = 'NextTableName=' . $nextTableName;
$queryString = self::createQueryStringFromArray($queryString);
if ($response->isSuccessful()) {
if (!$result || !$result->entry) {
if (count($result->entry) > 1) {
$entries = $result->entry;
$entries = array($result->entry);
foreach ($entries as $entry) {
$tableName = $entry->xpath('.//m:properties/d:TableName');
$tableName = (string) $tableName[0];
(string) $entry->link['href'],
if (!is_null($response->getHeader('x-ms-continuation-NextTableName'))) {
$returnValue = array_merge($returnValue, $this->listTables($response->getHeader('x-ms-continuation-NextTableName')));
* @param string $tableName Table name
* @return Microsoft_WindowsAzure_Storage_TableInstance
* @throws Microsoft_WindowsAzure_Exception
$requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<updated>{tpl:Updated}</updated>
<content type="application/xml">
<d:TableName>{tpl:TableName}</d:TableName>
// Add header information
$headers['Content-Type'] = 'application/atom+xml';
$headers['DataServiceVersion'] = '1.0;NetFx';
$headers['MaxDataServiceVersion'] = '1.0;NetFx';
if ($response->isSuccessful()) {
$tableName = $entry->xpath('.//m:properties/d:TableName');
$tableName = (string) $tableName[0];
(string) $entry->link['href'],
* Create table if it does not exist
* @param string $tableName Table name
* @throws Microsoft_WindowsAzure_Exception
* @param string $tableName Table name
* @throws Microsoft_WindowsAzure_Exception
// Add header information
$headers['Content-Type'] = 'application/atom+xml';
if (!$response->isSuccessful()) {
* Insert entity into table
* @param string $tableName Table name
* @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to insert
* @return Microsoft_WindowsAzure_Storage_TableEntity
* @throws Microsoft_WindowsAzure_Exception
public function insertEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null)
$requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
<updated>{tpl:Updated}</updated>
<content type="application/xml">
// Add header information
$headers['Content-Type'] = 'application/atom+xml';
if ($response->isSuccessful()) {
$timestamp = $result->xpath('//m:properties/d:Timestamp');
$etag = $result->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
$etag = (string) $etag['etag'];
$entity->setTimestamp($timestamp);
* Delete entity from table
* @param string $tableName Table name
* @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to delete
* @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
* @throws Microsoft_WindowsAzure_Exception
public function deleteEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
// Add header information
// http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/9e255447-4dc7-458a-99d3-bdc04bdc5474/
$headers['Content-Type'] = 'application/atom+xml';
$headers['Content-Length'] = 0;
$headers['If-Match'] = '*';
$headers['If-Match'] = $entity->getEtag();
$this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Microsoft_Http_Client::DELETE, $headers, true, null);
$response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Microsoft_Http_Client::DELETE, $headers, true, null);
if (!$response->isSuccessful()) {
* Retrieve entity from table, by id
* @param string $tableName Table name
* @param string $partitionKey Partition key
* @param string $rowKey Row key
* @param string $entityClass Entity class name*
* @return Microsoft_WindowsAzure_Storage_TableEntity
* @throws Microsoft_WindowsAzure_Exception
public function retrieveEntityById($tableName, $partitionKey, $rowKey, $entityClass = 'Microsoft_WindowsAzure_Storage_DynamicTableEntity')
if (is_null($tableName) || $tableName === '') {
if (is_null($partitionKey) || $partitionKey === '') {
if (is_null($rowKey) || $rowKey === '') {
if (is_null($entityClass) || $entityClass === '') {
// Check for combined size of partition key and row key
// http://msdn.microsoft.com/en-us/library/dd179421.aspx
if (strlen($partitionKey . $rowKey) >= 256) {
// Start a batch if possible
throw new Microsoft_WindowsAzure_Exception('Entity cannot be retrieved. A transaction is required to retrieve the entity, but another transaction is already active.');
// Fetch entities from Azure
->wherePartitionKey($partitionKey)
if (count($result) == 1) {
* Create a new Microsoft_WindowsAzure_Storage_TableEntityQuery
* @return Microsoft_WindowsAzure_Storage_TableEntityQuery
* Retrieve entities from table
* @param string $tableName|Microsoft_WindowsAzure_Storage_TableEntityQuery Table name -or- Microsoft_WindowsAzure_Storage_TableEntityQuery instance
* @param string $filter Filter condition (not applied when $tableName is a Microsoft_WindowsAzure_Storage_TableEntityQuery instance)
* @param string $entityClass Entity class name
* @param string $nextPartitionKey Next partition key, used for listing entities when total amount of entities is > 1000.
* @param string $nextRowKey Next row key, used for listing entities when total amount of entities is > 1000.
* @return array Array of Microsoft_WindowsAzure_Storage_TableEntity
* @throws Microsoft_WindowsAzure_Exception
public function retrieveEntities($tableName = '', $filter = '', $entityClass = 'Microsoft_WindowsAzure_Storage_DynamicTableEntity', $nextPartitionKey = null, $nextRowKey = null)
if ($entityClass === '') {
// Option 1: $tableName is a string
if (strpos($tableName, '()') === false) {
$queryString = '?' . implode('&', $query);
} else if (get_class($tableName) == 'Microsoft_WindowsAzure_Storage_TableEntityQuery') {
// Option 2: $tableName is a Microsoft_WindowsAzure_Storage_TableEntityQuery instance
$queryString = $tableName->assembleQueryString(true);
$tableName = $tableName->assembleFrom(true);
// Add continuation querystring parameters?
if ($queryString !== '') {
// Get inner response (multipart)
$innerResponse = $response->getBody();
$innerResponse = substr($innerResponse, strpos($innerResponse, 'HTTP/1.1 200 OK'));
$innerResponse = substr($innerResponse, 0, strpos($innerResponse, '--batchresponse'));
if ($response->isSuccessful()) {
if (count($result->entry) > 1) {
$entries = $result->entry;
$entries = array($result->entry);
// This one is tricky... If we have properties defined, we have an entity.
$properties = $result->xpath('//m:properties');
$entries = array($result);
foreach ($entries as $entry) {
$properties = $entry->xpath('.//m:properties');
$properties = $properties[0]->children('http://schemas.microsoft.com/ado/2007/08/dataservices');
$entity = new $entityClass('', '');
// If we have a Microsoft_WindowsAzure_Storage_DynamicTableEntity, make sure all property types are set
foreach ($properties as $key => $value) {
$attributes = $value->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
$type = (string) $attributes['type'];
$entity->setAzureProperty($key, (string) $value, $type);
$etag = $entry->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
$etag = (string) $etag['etag'];
$returnValue[] = $entity;
if (!is_null($response->getHeader('x-ms-continuation-NextPartitionKey')) && !is_null($response->getHeader('x-ms-continuation-NextRowKey'))) {
if (strpos($queryString, '$top') === false) {
$returnValue = array_merge($returnValue, $this->retrieveEntities($tableName, $filter, $entityClass, $response->getHeader('x-ms-continuation-NextPartitionKey'), $response->getHeader('x-ms-continuation-NextRowKey')));
* Update entity by replacing it
* @param string $tableName Table name
* @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to update
* @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
* @throws Microsoft_WindowsAzure_Exception
public function updateEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
* Update entity by adding or updating properties
* @param string $tableName Table name
* @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to update
* @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
* @param array $properties Properties to merge. All properties will be used when omitted.
* @throws Microsoft_WindowsAzure_Exception
public function mergeEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false, $properties = array())
// Keep only values mentioned in $properties
$azureValues = $entity->getAzureValues();
foreach ($azureValues as $key => $value) {
if (in_array($value->Name, $properties)) {
$mergeEntity->setAzureProperty($value->Name, $value->Value, $value->Type);
// Ensure entity timestamp matches updated timestamp
$entity->setTimestamp(new DateTime());
* Get error message from Microsoft_Http_Response
* @param Microsoft_Http_Response $response Repsonse
* @param string $alternativeError Alternative error message
protected function _getErrorMessage(Microsoft_Http_Response $response, $alternativeError = 'Unknown error.')
if ($response && $response->message) {
return (string) $response->message;
return $alternativeError;
* Update entity / merge entity
* @param string $httpVerb HTTP verb to use (PUT = update, MERGE = merge)
* @param string $tableName Table name
* @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to update
* @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
* @throws Microsoft_WindowsAzure_Exception
protected function _changeEntity($httpVerb = Microsoft_Http_Client::PUT, $tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
// Add header information
$headers['Content-Type'] = 'application/atom+xml';
$headers['Content-Length'] = 0;
$headers['If-Match'] = '*';
$headers['If-Match'] = $entity->getEtag();
$requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
<updated>{tpl:Updated}</updated>
<content type="application/xml">
// Attempt to get timestamp from entity
$timestamp = $entity->getTimestamp();
// Add header information
$headers['Content-Type'] = 'application/atom+xml';
$headers['If-Match'] = '*';
$headers['If-Match'] = $entity->getEtag();
$this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
$response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
if ($response->isSuccessful()) {
$entity->setEtag($response->getHeader('Etag'));
$entity->setTimestamp( $this->_convertToDateTime($response->getHeader('Last-modified')) );
* Generate RFC 1123 compliant date string
return gmdate('D, d M Y H:i:s', time()) . ' GMT'; // RFC 1123
* Fill text template with variables from key/value array
* @param string $templateText Template text
* @param array $variables Array containing key/value pairs
protected function _fillTemplate($templateText, $variables = array())
foreach ($variables as $key => $value) {
$templateText = str_replace('{tpl:' . $key . '}', $value, $templateText);
* Generate Azure representation from entity (creates atompub markup from properties)
* @param Microsoft_WindowsAzure_Storage_TableEntity $entity
// Generate Azure representation from entity
$azureRepresentation = array();
$azureValues = $entity->getAzureValues();
foreach ($azureValues as $azureValue) {
$value[] = '<d:' . $azureValue->Name;
if ($azureValue->Type != '') {
$value[] = ' m:type="' . $azureValue->Type . '"';
$value[] = ' m:null="true"';
if (!is_null($azureValue->Value)) {
if (strtolower($azureValue->Type) == 'edm.boolean') {
$value[] = ($azureValue->Value == true ? '1' : '0');
} else if (strtolower($azureValue->Type) == 'edm.datetime') {
$value[] = '</d:' . $azureValue->Name . '>';
$azureRepresentation[] = implode('', $value);
return implode('', $azureRepresentation);
* Perform request using Microsoft_Http_Client channel
* @param string $path Path
* @param string $queryString Query string
* @param string $httpVerb HTTP verb the request will use
* @param array $headers x-ms headers to add
* @param boolean $forTableStorage Is the request for table storage?
* @param mixed $rawData Optional RAW HTTP data to be sent over the wire
* @param string $resourceType Resource type
* @param string $requiredPermission Required permission
* @return Microsoft_Http_Response
$httpVerb = Microsoft_Http_Client::GET,
$forTableStorage = false,
$resourceType = Microsoft_WindowsAzure_Storage::RESOURCE_UNKNOWN,
$requiredPermission = Microsoft_WindowsAzure_Credentials_CredentialsAbstract::PERMISSION_READ
$headers['DataServiceVersion'] = '1.0;NetFx';
$headers['MaxDataServiceVersion'] = '1.0;NetFx';
return parent::_performRequest(
* Converts a string to a DateTime object. Returns false on failure.
* @param string $value The string value to parse
* @return DateTime|boolean
if ($value instanceof DateTime) {
if (substr($value, - 1) == 'Z') {
return new DateTime($value, new DateTimeZone('UTC'));
* Converts a DateTime object into an Edm.DaeTime value in UTC timezone,
* represented as a string.
$cloned->setTimezone(new DateTimeZone('UTC'));
return str_replace('+0000', 'Z', $cloned->format(DateTime::ISO8601));
|