/PhpSiteGenerator/PhpSiteGenerator/Microsoft/WindowsAzure/Storage/Table.php
PHP | 973 lines | 562 code | 109 blank | 302 comment | 87 complexity | 0bf24b625c998e3ce1556b62bfd91be5 MD5 | raw file
- <?php
- /**
- * Copyright (c) 2009, RealDolmen
- * All rights reserved.
- *
- * 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.
- *
- * @category Microsoft
- * @package Microsoft_WindowsAzure
- * @subpackage Storage
- * @copyright Copyright (c) 2009, 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_WindowsAzure_Credentials
- */
- require_once 'Microsoft/WindowsAzure/Credentials.php';
-
- /**
- * @see Microsoft_WindowsAzure_SharedKeyCredentials
- */
- require_once 'Microsoft/WindowsAzure/SharedKeyCredentials.php';
-
- /**
- * @see Microsoft_WindowsAzure_SharedKeyLiteCredentials
- */
- require_once 'Microsoft/WindowsAzure/SharedKeyLiteCredentials.php';
-
- /**
- * @see Microsoft_WindowsAzure_RetryPolicy
- */
- require_once 'Microsoft/WindowsAzure/RetryPolicy.php';
-
- /**
- * @see Microsoft_Http_Transport
- */
- require_once 'Microsoft/Http/Transport.php';
-
- /**
- * @see Microsoft_Http_Response
- */
- require_once 'Microsoft/Http/Response.php';
-
- /**
- * @see Microsoft_WindowsAzure_Storage
- */
- require_once 'Microsoft/WindowsAzure/Storage.php';
-
- /**
- * @see Microsoft_WindowsAzure_Storage_BatchStorage
- */
- require_once 'Microsoft/WindowsAzure/Storage/BatchStorage.php';
-
- /**
- * @see Microsoft_WindowsAzure_Storage_TableInstance
- */
- require_once 'Microsoft/WindowsAzure/Storage/TableInstance.php';
-
- /**
- * @see Microsoft_WindowsAzure_Storage_TableEntity
- */
- require_once 'Microsoft/WindowsAzure/Storage/TableEntity.php';
-
- /**
- * @see Microsoft_WindowsAzure_Storage_DynamicTableEntity
- */
- require_once 'Microsoft/WindowsAzure/Storage/DynamicTableEntity.php';
-
- /**
- * @see Microsoft_WindowsAzure_Storage_TableEntityQuery
- */
- require_once 'Microsoft/WindowsAzure/Storage/TableEntityQuery.php';
-
- /**
- * @see Microsoft_WindowsAzure_Exception
- */
- require_once 'Microsoft/WindowsAzure/Exception.php';
-
-
- /**
- * @category Microsoft
- * @package Microsoft_WindowsAzure
- * @subpackage Storage
- * @copyright Copyright (c) 2009, RealDolmen (http://www.realdolmen.com)
- * @license http://phpazure.codeplex.com/license
- */
- class Microsoft_WindowsAzure_Storage_Table extends Microsoft_WindowsAzure_Storage_BatchStorage
- {
- /**
- * ODBC connection string
- *
- * @var string
- */
- protected $_odbcConnectionString;
-
- /**
- * ODBC user name
- *
- * @var string
- */
- protected $_odbcUsername;
-
- /**
- * ODBC password
- *
- * @var string
- */
- protected $_odbcPassword;
-
- /**
- * 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 $retryPolicy Retry policy to use when making requests
- */
- public function __construct($host = Microsoft_WindowsAzure_Storage::URL_DEV_TABLE, $accountName = Microsoft_WindowsAzure_Credentials::DEVSTORE_ACCOUNT, $accountKey = Microsoft_WindowsAzure_Credentials::DEVSTORE_KEY, $usePathStyleUri = false, Microsoft_WindowsAzure_RetryPolicy $retryPolicy = null)
- {
- parent::__construct($host, $accountName, $accountKey, $usePathStyleUri, $retryPolicy);
-
- // Always use SharedKeyLite authentication
- $this->_credentials = new Microsoft_WindowsAzure_SharedKeyLiteCredentials($accountName, $accountKey, $this->_usePathStyleUri);
-
- // API version
- $this->_apiVersion = '2009-04-14';
- }
-
- /**
- * Set ODBC connection settings - used for creating tables on development storage
- *
- * @param string $connectionString ODBC connection string
- * @param string $username ODBC user name
- * @param string $password ODBC password
- */
- public function setOdbcSettings($connectionString, $username, $password)
- {
- $this->_odbcConnectionString = $connectionString;
- $this->_odbcUserame = $username;
- $this->_odbcPassword = $password;
- }
-
- /**
- * Generate table on development storage
- *
- * Note 1: ODBC settings must be specified first using setOdbcSettings()
- * Note 2: Development table storage MST BE RESTARTED after generating tables. Otherwise newly create tables will NOT be accessible!
- *
- * @param string $entityClass Entity class name
- * @param string $tableName Table name
- */
- public function generateDevelopmentTable($entityClass, $tableName)
- {
- // Check if we can do this...
- if (!function_exists('odbc_connect'))
- throw new Microsoft_WindowsAzure_Exception('Function odbc_connect does not exist. Please enable the php_odbc.dll module in your php.ini.');
- if ($this->_odbcConnectionString == '')
- throw new Microsoft_WindowsAzure_Exception('Please specify ODBC settings first using setOdbcSettings().');
- if ($entityClass === '')
- throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
-
- // Get accessors
- $accessors = Microsoft_WindowsAzure_Storage_TableEntity::getAzureAccessors($entityClass);
-
- // Generate properties
- $properties = array();
- foreach ($accessors as $accessor)
- {
- if ($accessor->AzurePropertyName == 'Timestamp'
- || $accessor->AzurePropertyName == 'PartitionKey'
- || $accessor->AzurePropertyName == 'RowKey')
- {
- continue;
- }
-
- switch (strtolower($accessor->AzurePropertyType))
- {
- case 'edm.int32':
- case 'edm.int64':
- $sqlType = '[int] NULL'; break;
- case 'edm.guid':
- $sqlType = '[uniqueidentifier] NULL'; break;
- case 'edm.datetime':
- $sqlType = '[datetime] NULL'; break;
- case 'edm.boolean':
- $sqlType = '[bit] NULL'; break;
- case 'edm.double':
- $sqlType = '[decimal] NULL'; break;
- default:
- $sqlType = '[nvarchar](1000) NULL'; break;
- }
- $properties[] = '[' . $accessor->AzurePropertyName . '] ' . $sqlType;
- }
-
- // Generate SQL
- $sql = 'CREATE TABLE [dbo].[{tpl:TableName}](
- {tpl:Properties} {tpl:PropertiesComma}
- [Timestamp] [datetime] NULL,
- [PartitionKey] [nvarchar](1000) NOT NULL,
- [RowKey] [nvarchar](1000) NOT NULL
- );
- ALTER TABLE [dbo].[{tpl:TableName}] ADD PRIMARY KEY (PartitionKey, RowKey);';
-
- $sql = $this->fillTemplate($sql, array(
- 'TableName' => $tableName,
- 'Properties' => implode(',', $properties),
- 'PropertiesComma' => count($properties) > 0 ? ',' : ''
- ));
-
- // Connect to database
- $db = @odbc_connect($this->_odbcConnectionString, $this->_odbcUserame, $this->_odbcPassword);
- if (!$db)
- {
- throw new Microsoft_WindowsAzure_Exception('Could not connect to database via ODBC.');
- }
-
- // Create table
- odbc_exec($db, $sql);
-
- // Close connection
- odbc_close($db);
- }
-
- /**
- * Check if a table exists
- *
- * @param string $tableName Table name
- * @return boolean
- */
- public function tableExists($tableName = '')
- {
- if ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
-
- // List tables
- $tables = $this->listTables($tableName);
- foreach ($tables as $table)
- {
- if ($table->Name == $tableName)
- return true;
- }
-
- return false;
- }
-
- /**
- * List tables
- *
- * @param string $nextTableName Next table name, used for listing tables when total amount of tables is > 1000.
- * @return array
- * @throws Microsoft_WindowsAzure_Exception
- */
- public function listTables($nextTableName = '')
- {
- // Build query string
- $queryString = '';
- if ($nextTableName != '')
- {
- $queryString = '?NextTableName=' . $nextTableName;
- }
-
- // Perform request
- $response = $this->performRequest('Tables', $queryString, Microsoft_Http_Transport::VERB_GET, null, true);
- if ($response->isSuccessful())
- {
- // Parse result
- $result = $this->parseResponse($response);
-
- if (!$result || !$result->entry)
- return array();
-
- $entries = null;
- if (count($result->entry) > 1)
- {
- $entries = $result->entry;
- }
- else
- {
- $entries = array($result->entry);
- }
-
- // Create return value
- $returnValue = array();
- foreach ($entries as $entry)
- {
- $tableName = $entry->xpath('.//m:properties/d:TableName');
- $tableName = (string)$tableName[0];
-
- $returnValue[] = new Microsoft_WindowsAzure_Storage_TableInstance(
- (string)$entry->id,
- $tableName,
- (string)$entry->link['href'],
- (string)$entry->updated
- );
- }
-
- // More tables?
- if (!is_null($response->getHeader('x-ms-continuation-NextTableName')))
- {
- $returnValue = array_merge($returnValue, $this->listTables($response->getHeader('x-ms-continuation-NextTableName')));
- }
-
- return $returnValue;
- }
- else
- {
- throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
- }
- }
-
- /**
- * Create table
- *
- * @param string $tableName Table name
- * @return Microsoft_WindowsAzure_Storage_TableInstance
- * @throws Microsoft_WindowsAzure_Exception
- */
- public function createTable($tableName = '')
- {
- if ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
-
- // Generate request body
- $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
- <entry xml:base="{tpl:BaseUrl}" 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">
- <id>{tpl:BaseUrl}/Tables(\'{tpl:TableName}\')</id>
- <title type="text"></title>
- <updated>{tpl:Updated}</updated>
- <author>
- <name />
- </author>
- <link rel="edit" title="Tables" href="Tables(\'{tpl:TableName}\')" />
- <category term="{tpl:AccountName}.Tables" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
- <content type="application/xml">
- <m:properties>
- <d:TableName>{tpl:TableName}</d:TableName>
- </m:properties>
- </content>
- </entry>';
-
- $requestBody = $this->fillTemplate($requestBody, array(
- 'BaseUrl' => $this->getBaseUrl(),
- 'TableName' => $tableName,
- 'Updated' => $this->isoDate(),
- 'AccountName' => $this->_accountName
- ));
-
- // Add header information
- $headers = array();
- $headers['Content-Type'] = 'application/atom+xml';
-
- // Perform request
- $response = $this->performRequest('Tables', '', Microsoft_Http_Transport::VERB_POST, $headers, true, $requestBody);
- if ($response->isSuccessful())
- {
- // Parse response
- $entry = $this->parseResponse($response);
-
- $tableName = $entry->xpath('.//m:properties/d:TableName');
- $tableName = (string)$tableName[0];
-
- return new Microsoft_WindowsAzure_Storage_TableInstance(
- (string)$entry->id,
- $tableName,
- (string)$entry->link['href'],
- (string)$entry->updated
- );
- }
- else
- {
- throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
- }
- }
-
- /**
- * Delete table
- *
- * @param string $tableName Table name
- * @throws Microsoft_WindowsAzure_Exception
- */
- public function deleteTable($tableName = '')
- {
- if ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
-
- // Add header information
- $headers = array();
- $headers['Content-Type'] = 'application/atom+xml';
-
- // Perform request
- $response = $this->performRequest('Tables(\'' . $tableName . '\')', '', Microsoft_Http_Transport::VERB_DELETE, $headers, true, null);
- if (!$response->isSuccessful())
- {
- throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
- }
- }
-
- /**
- * 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)
- {
- if ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
- if (is_null($entity))
- throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
-
- // Generate request body
- $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">
- <title />
- <updated>{tpl:Updated}</updated>
- <author>
- <name />
- </author>
- <id />
- <content type="application/xml">
- <m:properties>
- {tpl:Properties}
- </m:properties>
- </content>
- </entry>';
-
- $requestBody = $this->fillTemplate($requestBody, array(
- 'Updated' => $this->isoDate(),
- 'Properties' => $this->generateAzureRepresentation($entity)
- ));
-
- // Add header information
- $headers = array();
- $headers['Content-Type'] = 'application/atom+xml';
-
- // Perform request
- $response = null;
- if ($this->isInBatch())
- {
- $this->getCurrentBatch()->enlistOperation($tableName, '', Microsoft_Http_Transport::VERB_POST, $headers, true, $requestBody);
- return null;
- }
- else
- {
- $response = $this->performRequest($tableName, '', Microsoft_Http_Transport::VERB_POST, $headers, true, $requestBody);
- }
- if ($response->isSuccessful())
- {
- // Parse result
- $result = $this->parseResponse($response);
-
- $timestamp = $result->xpath('//m:properties/d:Timestamp');
- $timestamp = (string)$timestamp[0];
-
- $etag = $result->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
- $etag = (string)$etag['etag'];
-
- // Update properties
- $entity->setTimestamp($timestamp);
- $entity->setEtag($etag);
-
- return $entity;
- }
- else
- {
- throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
- }
- }
-
- /**
- * 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)
- {
- if ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
- if (is_null($entity))
- throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
-
- // Add header information
- $headers = array();
- if (!$this->isInBatch()) // 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;
- if (!$verifyEtag)
- {
- $headers['If-Match'] = '*';
- }
- else
- {
- $headers['If-Match'] = $entity->getEtag();
- }
-
- // Perform request
- $response = null;
- if ($this->isInBatch())
- {
- $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Microsoft_Http_Transport::VERB_DELETE, $headers, true, null);
- return null;
- }
- else
- {
- $response = $this->performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Microsoft_Http_Transport::VERB_DELETE, $headers, true, null);
- }
- if (!$response->isSuccessful())
- {
- throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
- }
- }
-
- /**
- * 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 ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
- if ($partitionKey === '')
- throw new Microsoft_WindowsAzure_Exception('Partition key is not specified.');
- //if ($rowKey === '')
- // throw new Microsoft_WindowsAzure_Exception('Row key is not specified.');
- if ($entityClass === '')
- throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
-
-
- // 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
- if ($this->isInBatch())
- throw new Microsoft_WindowsAzure_Exception('Entity cannot be retrieved. A transaction is required to retrieve the entity, but another transaction is already active.');
-
- $this->startBatch();
- }
-
- // Fetch entities from Azure
- $result = $this->retrieveEntities(
- $this->select()
- ->from($tableName)
- ->wherePartitionKey($partitionKey)
- ->whereRowKey($rowKey),
- '',
- $entityClass
- );
-
- // Return
- if (count($result) == 1)
- {
- return $result[0];
- }
-
- return null;
- }
-
- /**
- * Create a new Microsoft_WindowsAzure_Storage_TableEntityQuery
- *
- * @return Microsoft_WindowsAzure_Storage_TableEntityQuery
- */
- public function select()
- {
- return new 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 ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
- if ($entityClass === '')
- throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
-
- // Convenience...
- if (class_exists($filter))
- {
- $entityClass = $filter;
- $filter = '';
- }
-
- // Query string
- $queryString = '';
-
- // Determine query
- if (is_string($tableName))
- {
- // Option 1: $tableName is a string
-
- // Append parentheses
- $tableName .= '()';
-
- // Build query
- $query = array();
-
- // Filter?
- if ($filter !== '')
- {
- $query[] = '$filter=' . rawurlencode($filter);
- }
-
- // Build queryString
- if (count($query) > 0)
- {
- $queryString = '?' . implode('&', $query);
- }
- }
- else if (get_class($tableName) == 'Microsoft_WindowsAzure_Storage_TableEntityQuery')
- {
- // Option 2: $tableName is a Microsoft_WindowsAzure_Storage_TableEntityQuery instance
-
- // Build queryString
- $queryString = $tableName->assembleQueryString(true);
-
- // Change $tableName
- $tableName = $tableName->assembleFrom(true);
- }
- else
- {
- throw new Microsoft_WindowsAzure_Exception('Invalid argument: $tableName');
- }
-
- // Add continuation querystring parameters?
- if (!is_null($nextPartitionKey) && !is_null($nextRowKey))
- {
- if ($queryString !== '')
- $queryString .= '&';
-
- $queryString .= '&NextPartitionKey=' . rawurlencode($nextPartitionKey) . '&NextRowKey=' . rawurlencode($nextRowKey);
- }
-
- // Perform request
- $response = null;
- if ($this->isInBatch() && $this->getCurrentBatch()->getOperationCount() == 0)
- {
- $this->getCurrentBatch()->enlistOperation($tableName, $queryString, Microsoft_Http_Transport::VERB_GET, array(), true, null);
- $response = $this->getCurrentBatch()->commit();
-
- // Get inner response (multipart)
- $innerResponse = $response->getBody();
- $innerResponse = substr($innerResponse, strpos($innerResponse, 'HTTP/1.1 200 OK'));
- $innerResponse = substr($innerResponse, 0, strpos($innerResponse, '--batchresponse'));
- $response = Microsoft_Http_Response::fromString($innerResponse);
- }
- else
- {
- $response = $this->performRequest($tableName, $queryString, Microsoft_Http_Transport::VERB_GET, array(), true, null);
- }
- if ($response->isSuccessful())
- {
- // Parse result
- $result = $this->parseResponse($response);
- if (!$result)
- return array();
-
- $entries = null;
- if ($result->entry)
- {
- if (count($result->entry) > 1)
- {
- $entries = $result->entry;
- }
- else
- {
- $entries = array($result->entry);
- }
- }
- else
- {
- // This one is tricky... If we have properties defined, we have an entity.
- $properties = $result->xpath('//m:properties');
- if ($properties)
- {
- $entries = array($result);
- }
- else
- {
- return array();
- }
- }
-
- // Create return value
- $returnValue = array();
- foreach ($entries as $entry)
- {
- // Parse properties
- $properties = $entry->xpath('.//m:properties');
- $properties = $properties[0]->children('http://schemas.microsoft.com/ado/2007/08/dataservices');
-
- // Create entity
- $entity = new $entityClass('', '');
- $entity->setAzureValues((array)$properties, true);
-
- // If we have a Microsoft_WindowsAzure_Storage_DynamicTableEntity, make sure all property types are OK
- if ($entity instanceof Microsoft_WindowsAzure_Storage_DynamicTableEntity)
- {
- foreach ($properties as $key => $value)
- {
- $attributes = $value->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
- $type = (string)$attributes['type'];
- if ($type !== '')
- {
- $entity->setAzurePropertyType($key, $type);
- }
- }
- }
-
- // Update etag
- $etag = $entry->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
- $etag = (string)$etag['etag'];
- $entity->setEtag($etag);
-
- // Add to result
- $returnValue[] = $entity;
- }
-
- // More entities?
- 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')));
- }
-
- // Return
- return $returnValue;
- }
- else
- {
- throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
- }
- }
-
- /**
- * 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)
- {
- return $this->changeEntity(Microsoft_Http_Transport::VERB_PUT, $tableName, $entity, $verifyEtag);
- }
-
- /**
- * Update entity by adding 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)
- * @throws Microsoft_WindowsAzure_Exception
- */
- public function mergeEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
- {
- return $this->changeEntity(Microsoft_Http_Transport::VERB_MERGE, $tableName, $entity, $verifyEtag);
- }
-
- /**
- * Get error message from Microsoft_Http_Response
- *
- * @param Microsoft_Http_Response $response Repsonse
- * @param string $alternativeError Alternative error message
- * @return string
- */
- protected function getErrorMessage(Microsoft_Http_Response $response, $alternativeError = 'Unknwon error.')
- {
- $response = $this->parseResponse($response);
- if ($response && $response->message)
- return (string)$response->message;
- else
- 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_Transport::VERB_PUT, $tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
- {
- if ($tableName === '')
- throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
- if (is_null($entity))
- throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
-
- // Add header information
- $headers = array();
- $headers['Content-Type'] = 'application/atom+xml';
- $headers['Content-Length'] = 0;
- if (!$verifyEtag)
- {
- $headers['If-Match'] = '*';
- }
- else
- {
- $headers['If-Match'] = $entity->getEtag();
- }
-
- // Generate request body
- $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">
- <title />
- <updated>{tpl:Updated}</updated>
- <author>
- <name />
- </author>
- <id />
- <content type="application/xml">
- <m:properties>
- {tpl:Properties}
- </m:properties>
- </content>
- </entry>';
-
- $requestBody = $this->fillTemplate($requestBody, array(
- 'Updated' => $this->isoDate(),
- 'Properties' => $this->generateAzureRepresentation($entity)
- ));
-
- // Add header information
- $headers = array();
- $headers['Content-Type'] = 'application/atom+xml';
- if (!$verifyEtag)
- {
- $headers['If-Match'] = '*';
- }
- else
- {
- $headers['If-Match'] = $entity->getEtag();
- }
-
- // Perform request
- $response = null;
- if ($this->isInBatch())
- {
- $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
- return null;
- }
- else
- {
- $response = $this->performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
- }
- if ($response->isSuccessful())
- {
- // Update properties
- $entity->setEtag($response->getHeader('Etag'));
- $entity->setTimestamp($response->getHeader('Last-modified'));
-
- return $entity;
- }
- else
- {
- throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
- }
- }
-
- /**
- * Generate RFC 1123 compliant date string
- *
- * @return string
- */
- protected function rfcDate()
- {
- 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
- * @return string
- */
- protected function fillTemplate($templateText, $variables = array())
- {
- foreach ($variables as $key => $value)
- {
- $templateText = str_replace('{tpl:' . $key . '}', $value, $templateText);
- }
- return $templateText;
- }
-
- /**
- * Generate Azure representation from entity (creates atompub markup from properties)
- *
- * @param Microsoft_WindowsAzure_Storage_TableEntity $entity
- * @return string
- */
- protected function generateAzureRepresentation(Microsoft_WindowsAzure_Storage_TableEntity $entity = null)
- {
- // Generate Azure representation from entity
- $azureRepresentation = array();
- $azureValues = $entity->getAzureValues();
- foreach ($azureValues as $azureValue)
- {
- $value = array();
- $value[] = '<d:' . $azureValue->Name;
- if ($azureValue->Type != '')
- $value[] = ' m:type="' . $azureValue->Type . '"';
- if (is_null($azureValue->Value))
- $value[] = ' m:null="true"';
- $value[] = '>';
-
- if (!is_null($azureValue->Value))
- {
- if (strtolower($azureValue->Type) == 'edm.boolean')
- {
- $value[] = ($azureValue->Value == true ? '1' : '0');
- }
- else
- {
- $value[] = $azureValue->Value;
- }
- }
-
- $value[] = '</d:' . $azureValue->Name . '>';
- $azureRepresentation[] = implode('', $value);
- }
-
- return implode('', $azureRepresentation);
- }
- }