File indexing completed on 2025-01-26 05:25:30
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Service_WindowsAzure 0017 * @subpackage Storage 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** 0024 * @see Zend_Service_WindowsAzure_Storage_BatchStorageAbstract 0025 */ 0026 // require_once 'Zend/Service/WindowsAzure/Storage/BatchStorageAbstract.php'; 0027 0028 /** 0029 * @see Zend_Service_WindowsAzure_Storage_TableInstance 0030 */ 0031 // require_once 'Zend/Service/WindowsAzure/Storage/TableInstance.php'; 0032 0033 /** 0034 * @see Zend_Service_WindowsAzure_Storage_TableEntityQuery 0035 */ 0036 // require_once 'Zend/Service/WindowsAzure/Storage/TableEntityQuery.php'; 0037 0038 /** 0039 * @see Zend_Service_WindowsAzure_Storage_DynamicTableEntity 0040 */ 0041 // require_once 'Zend/Service/WindowsAzure/Storage/DynamicTableEntity.php'; 0042 0043 /** 0044 * @see Zend_Service_WindowsAzure_Credentials_SharedKeyLite 0045 */ 0046 // require_once 'Zend/Service/WindowsAzure/Credentials/SharedKeyLite.php'; 0047 0048 /** 0049 * @category Zend 0050 * @package Zend_Service_WindowsAzure 0051 * @subpackage Storage 0052 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0053 * @license http://framework.zend.com/license/new-bsd New BSD License 0054 */ 0055 class Zend_Service_WindowsAzure_Storage_Table 0056 extends Zend_Service_WindowsAzure_Storage_BatchStorageAbstract 0057 { 0058 /** 0059 * Throw Zend_Service_WindowsAzure_Exception when a property is not specified in Windows Azure? 0060 * Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET. 0061 * 0062 * @var boolean 0063 */ 0064 protected $_throwExceptionOnMissingData = true; 0065 0066 /** 0067 * Throw Zend_Service_WindowsAzure_Exception when a property is not specified in Windows Azure? 0068 * Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET. 0069 * 0070 * @param boolean $value 0071 */ 0072 public function setThrowExceptionOnMissingData($value = true) 0073 { 0074 $this->_throwExceptionOnMissingData = $value; 0075 } 0076 0077 /** 0078 * Throw Zend_Service_WindowsAzure_Exception when a property is not specified in Windows Azure? 0079 */ 0080 public function getThrowExceptionOnMissingData() 0081 { 0082 return $this->_throwExceptionOnMissingData; 0083 } 0084 0085 /** 0086 * Creates a new Zend_Service_WindowsAzure_Storage_Table instance 0087 * 0088 * @param string $host Storage host name 0089 * @param string $accountName Account name for Windows Azure 0090 * @param string $accountKey Account key for Windows Azure 0091 * @param boolean $usePathStyleUri Use path-style URI's 0092 * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests 0093 */ 0094 public function __construct($host = Zend_Service_WindowsAzure_Storage::URL_DEV_TABLE, $accountName = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_ACCOUNT, $accountKey = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_KEY, $usePathStyleUri = false, Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null) 0095 { 0096 parent::__construct($host, $accountName, $accountKey, $usePathStyleUri, $retryPolicy); 0097 0098 // Always use SharedKeyLite authentication 0099 $this->_credentials = new Zend_Service_WindowsAzure_Credentials_SharedKeyLite($accountName, $accountKey, $this->_usePathStyleUri); 0100 0101 // API version 0102 $this->_apiVersion = '2009-09-19'; 0103 } 0104 0105 /** 0106 * Check if a table exists 0107 * 0108 * @param string $tableName Table name 0109 * @return boolean 0110 */ 0111 public function tableExists($tableName = '') 0112 { 0113 if ($tableName === '') { 0114 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0115 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0116 } 0117 0118 // List tables 0119 $tables = $this->listTables(); // 2009-09-19 does not support $this->listTables($tableName); all of a sudden... 0120 foreach ($tables as $table) { 0121 if ($table->Name == $tableName) { 0122 return true; 0123 } 0124 } 0125 0126 return false; 0127 } 0128 0129 /** 0130 * List tables 0131 * 0132 * @param string $nextTableName Next table name, used for listing tables when total amount of tables is > 1000. 0133 * @return array 0134 * @throws Zend_Service_WindowsAzure_Exception 0135 */ 0136 public function listTables($nextTableName = '') 0137 { 0138 // Build query string 0139 $queryString = array(); 0140 if ($nextTableName != '') { 0141 $queryString[] = 'NextTableName=' . $nextTableName; 0142 } 0143 $queryString = self::createQueryStringFromArray($queryString); 0144 0145 // Perform request 0146 $response = $this->_performRequest('Tables', $queryString, Zend_Http_Client::GET, null, true); 0147 if ($response->isSuccessful()) { 0148 // Parse result 0149 $result = $this->_parseResponse($response); 0150 0151 if (!$result || !$result->entry) { 0152 return array(); 0153 } 0154 0155 $entries = null; 0156 if (count($result->entry) > 1) { 0157 $entries = $result->entry; 0158 } else { 0159 $entries = array($result->entry); 0160 } 0161 0162 // Create return value 0163 $returnValue = array(); 0164 foreach ($entries as $entry) { 0165 $tableName = $entry->xpath('.//m:properties/d:TableName'); 0166 $tableName = (string)$tableName[0]; 0167 0168 $returnValue[] = new Zend_Service_WindowsAzure_Storage_TableInstance( 0169 (string)$entry->id, 0170 $tableName, 0171 (string)$entry->link['href'], 0172 (string)$entry->updated 0173 ); 0174 } 0175 0176 // More tables? 0177 if (!is_null($response->getHeader('x-ms-continuation-NextTableName'))) { 0178 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0179 $returnValue = array_merge($returnValue, $this->listTables($response->getHeader('x-ms-continuation-NextTableName'))); 0180 } 0181 0182 return $returnValue; 0183 } else { 0184 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); 0185 } 0186 } 0187 0188 /** 0189 * Create table 0190 * 0191 * @param string $tableName Table name 0192 * @return Zend_Service_WindowsAzure_Storage_TableInstance 0193 * @throws Zend_Service_WindowsAzure_Exception 0194 */ 0195 public function createTable($tableName = '') 0196 { 0197 if ($tableName === '') { 0198 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0199 } 0200 0201 // Generate request body 0202 $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?> 0203 <entry 0204 xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 0205 xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 0206 xmlns="http://www.w3.org/2005/Atom"> 0207 <title /> 0208 <updated>{tpl:Updated}</updated> 0209 <author> 0210 <name /> 0211 </author> 0212 <id /> 0213 <content type="application/xml"> 0214 <m:properties> 0215 <d:TableName>{tpl:TableName}</d:TableName> 0216 </m:properties> 0217 </content> 0218 </entry>'; 0219 0220 $requestBody = $this->_fillTemplate($requestBody, array( 0221 'BaseUrl' => $this->getBaseUrl(), 0222 'TableName' => htmlspecialchars($tableName), 0223 'Updated' => $this->isoDate(), 0224 'AccountName' => $this->_accountName 0225 )); 0226 0227 // Add header information 0228 $headers = array(); 0229 $headers['Content-Type'] = 'application/atom+xml'; 0230 $headers['DataServiceVersion'] = '1.0;NetFx'; 0231 $headers['MaxDataServiceVersion'] = '1.0;NetFx'; 0232 0233 // Perform request 0234 $response = $this->_performRequest('Tables', '', Zend_Http_Client::POST, $headers, true, $requestBody); 0235 if ($response->isSuccessful()) { 0236 // Parse response 0237 $entry = $this->_parseResponse($response); 0238 0239 $tableName = $entry->xpath('.//m:properties/d:TableName'); 0240 $tableName = (string)$tableName[0]; 0241 0242 0243 return new Zend_Service_WindowsAzure_Storage_TableInstance( 0244 (string)$entry->id, 0245 $tableName, 0246 (string)$entry->link['href'], 0247 (string)$entry->updated 0248 ); 0249 } else { 0250 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0251 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); 0252 } 0253 } 0254 0255 /** 0256 * Create table if it does not exist 0257 * 0258 * @param string $tableName Table name 0259 * @throws Zend_Service_WindowsAzure_Exception 0260 */ 0261 public function createTableIfNotExists($tableName = '') 0262 { 0263 if (!$this->tableExists($tableName)) { 0264 $this->createTable($tableName); 0265 } 0266 } 0267 0268 /** 0269 * Delete table 0270 * 0271 * @param string $tableName Table name 0272 * @throws Zend_Service_WindowsAzure_Exception 0273 */ 0274 public function deleteTable($tableName = '') 0275 { 0276 if ($tableName === '') { 0277 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0278 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0279 } 0280 0281 // Add header information 0282 $headers = array(); 0283 $headers['Content-Type'] = 'application/atom+xml'; 0284 0285 // Perform request 0286 $response = $this->_performRequest('Tables(\'' . $tableName . '\')', '', Zend_Http_Client::DELETE, $headers, true, null); 0287 if (!$response->isSuccessful()) { 0288 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0289 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); 0290 } 0291 } 0292 0293 /** 0294 * Insert entity into table 0295 * 0296 * @param string $tableName Table name 0297 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to insert 0298 * @return Zend_Service_WindowsAzure_Storage_TableEntity 0299 * @throws Zend_Service_WindowsAzure_Exception 0300 */ 0301 public function insertEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null) 0302 { 0303 if ($tableName === '') { 0304 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0305 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0306 } 0307 if (is_null($entity)) { 0308 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0309 throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.'); 0310 } 0311 0312 // Generate request body 0313 $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?> 0314 <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"> 0315 <title /> 0316 <updated>{tpl:Updated}</updated> 0317 <author> 0318 <name /> 0319 </author> 0320 <id /> 0321 <content type="application/xml"> 0322 <m:properties> 0323 {tpl:Properties} 0324 </m:properties> 0325 </content> 0326 </entry>'; 0327 0328 $requestBody = $this->_fillTemplate($requestBody, array( 0329 'Updated' => $this->isoDate(), 0330 'Properties' => $this->_generateAzureRepresentation($entity) 0331 )); 0332 0333 // Add header information 0334 $headers = array(); 0335 $headers['Content-Type'] = 'application/atom+xml'; 0336 0337 // Perform request 0338 $response = null; 0339 if ($this->isInBatch()) { 0340 $this->getCurrentBatch()->enlistOperation($tableName, '', Zend_Http_Client::POST, $headers, true, $requestBody); 0341 return null; 0342 } else { 0343 $response = $this->_performRequest($tableName, '', Zend_Http_Client::POST, $headers, true, $requestBody); 0344 } 0345 if ($response->isSuccessful()) { 0346 // Parse result 0347 $result = $this->_parseResponse($response); 0348 0349 $timestamp = $result->xpath('//m:properties/d:Timestamp'); 0350 $timestamp = $this->_convertToDateTime( (string)$timestamp[0] ); 0351 0352 $etag = $result->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'); 0353 $etag = (string)$etag['etag']; 0354 0355 // Update properties 0356 $entity->setTimestamp($timestamp); 0357 $entity->setEtag($etag); 0358 0359 return $entity; 0360 } else { 0361 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0362 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); 0363 } 0364 } 0365 0366 /** 0367 * Delete entity from table 0368 * 0369 * @param string $tableName Table name 0370 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to delete 0371 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) 0372 * @throws Zend_Service_WindowsAzure_Exception 0373 */ 0374 public function deleteEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false) 0375 { 0376 if ($tableName === '') { 0377 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0378 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0379 } 0380 if (is_null($entity)) { 0381 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0382 throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.'); 0383 } 0384 0385 // Add header information 0386 $headers = array(); 0387 if (!$this->isInBatch()) { 0388 // http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/9e255447-4dc7-458a-99d3-bdc04bdc5474/ 0389 $headers['Content-Type'] = 'application/atom+xml'; 0390 } 0391 $headers['Content-Length'] = 0; 0392 if (!$verifyEtag) { 0393 $headers['If-Match'] = '*'; 0394 } else { 0395 $headers['If-Match'] = $entity->getEtag(); 0396 } 0397 0398 // Perform request 0399 $response = null; 0400 if ($this->isInBatch()) { 0401 $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Zend_Http_Client::DELETE, $headers, true, null); 0402 return null; 0403 } else { 0404 $response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Zend_Http_Client::DELETE, $headers, true, null); 0405 } 0406 if (!$response->isSuccessful()) { 0407 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0408 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); 0409 } 0410 } 0411 0412 /** 0413 * Retrieve entity from table, by id 0414 * 0415 * @param string $tableName Table name 0416 * @param string $partitionKey Partition key 0417 * @param string $rowKey Row key 0418 * @param string $entityClass Entity class name* 0419 * @return Zend_Service_WindowsAzure_Storage_TableEntity 0420 * @throws Zend_Service_WindowsAzure_Exception 0421 */ 0422 public function retrieveEntityById($tableName, $partitionKey, $rowKey, $entityClass = 'Zend_Service_WindowsAzure_Storage_DynamicTableEntity') 0423 { 0424 if (is_null($tableName) || $tableName === '') { 0425 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0426 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0427 } 0428 if (is_null($partitionKey) || $partitionKey === '') { 0429 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0430 throw new Zend_Service_WindowsAzure_Exception('Partition key is not specified.'); 0431 } 0432 if (is_null($rowKey) || $rowKey === '') { 0433 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0434 throw new Zend_Service_WindowsAzure_Exception('Row key is not specified.'); 0435 } 0436 if (is_null($entityClass) || $entityClass === '') { 0437 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0438 throw new Zend_Service_WindowsAzure_Exception('Entity class is not specified.'); 0439 } 0440 0441 0442 // Check for combined size of partition key and row key 0443 // http://msdn.microsoft.com/en-us/library/dd179421.aspx 0444 if (strlen($partitionKey . $rowKey) >= 256) { 0445 // Start a batch if possible 0446 if ($this->isInBatch()) { 0447 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0448 throw new Zend_Service_WindowsAzure_Exception('Entity cannot be retrieved. A transaction is required to retrieve the entity, but another transaction is already active.'); 0449 } 0450 0451 $this->startBatch(); 0452 } 0453 0454 // Fetch entities from Azure 0455 $result = $this->retrieveEntities( 0456 $this->select() 0457 ->from($tableName) 0458 ->wherePartitionKey($partitionKey) 0459 ->whereRowKey($rowKey), 0460 '', 0461 $entityClass 0462 ); 0463 0464 // Return 0465 if (count($result) == 1) { 0466 return $result[0]; 0467 } 0468 0469 return null; 0470 } 0471 0472 /** 0473 * Create a new Zend_Service_WindowsAzure_Storage_TableEntityQuery 0474 * 0475 * @return Zend_Service_WindowsAzure_Storage_TableEntityQuery 0476 */ 0477 public function select() 0478 { 0479 0480 return new Zend_Service_WindowsAzure_Storage_TableEntityQuery(); 0481 } 0482 0483 /** 0484 * Retrieve entities from table 0485 * 0486 * @param string $tableName|Zend_Service_WindowsAzure_Storage_TableEntityQuery Table name -or- Zend_Service_WindowsAzure_Storage_TableEntityQuery instance 0487 * @param string $filter Filter condition (not applied when $tableName is a Zend_Service_WindowsAzure_Storage_TableEntityQuery instance) 0488 * @param string $entityClass Entity class name 0489 * @param string $nextPartitionKey Next partition key, used for listing entities when total amount of entities is > 1000. 0490 * @param string $nextRowKey Next row key, used for listing entities when total amount of entities is > 1000. 0491 * @return array Array of Zend_Service_WindowsAzure_Storage_TableEntity 0492 * @throws Zend_Service_WindowsAzure_Exception 0493 */ 0494 public function retrieveEntities($tableName = '', $filter = '', $entityClass = 'Zend_Service_WindowsAzure_Storage_DynamicTableEntity', $nextPartitionKey = null, $nextRowKey = null) 0495 { 0496 if ($tableName === '') { 0497 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0498 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0499 } 0500 if ($entityClass === '') { 0501 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0502 throw new Zend_Service_WindowsAzure_Exception('Entity class is not specified.'); 0503 } 0504 0505 // Convenience... 0506 if (class_exists($filter)) { 0507 $entityClass = $filter; 0508 $filter = ''; 0509 } 0510 0511 // Query string 0512 $queryString = ''; 0513 0514 // Determine query 0515 if (is_string($tableName)) { 0516 // Option 1: $tableName is a string 0517 0518 // Append parentheses 0519 if (strpos($tableName, '()') === false) { 0520 $tableName .= '()'; 0521 } 0522 0523 // Build query 0524 $query = array(); 0525 0526 // Filter? 0527 if ($filter !== '') { 0528 $query[] = '$filter=' . Zend_Service_WindowsAzure_Storage_TableEntityQuery::encodeQuery($filter); 0529 } 0530 0531 // Build queryString 0532 if (count($query) > 0) { 0533 $queryString = '?' . implode('&', $query); 0534 } 0535 } else if (get_class($tableName) == 'Zend_Service_WindowsAzure_Storage_TableEntityQuery') { 0536 // Option 2: $tableName is a Zend_Service_WindowsAzure_Storage_TableEntityQuery instance 0537 0538 // Build queryString 0539 $queryString = $tableName->assembleQueryString(true); 0540 0541 // Change $tableName 0542 $tableName = $tableName->assembleFrom(true); 0543 } else { 0544 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0545 throw new Zend_Service_WindowsAzure_Exception('Invalid argument: $tableName'); 0546 } 0547 0548 // Add continuation querystring parameters? 0549 if (!is_null($nextPartitionKey) && !is_null($nextRowKey)) { 0550 if ($queryString !== '') { 0551 $queryString .= '&'; 0552 } else { 0553 $queryString .= '?'; 0554 } 0555 0556 $queryString .= 'NextPartitionKey=' . rawurlencode($nextPartitionKey) . '&NextRowKey=' . rawurlencode($nextRowKey); 0557 } 0558 0559 // Perform request 0560 $response = null; 0561 if ($this->isInBatch() && $this->getCurrentBatch()->getOperationCount() == 0) { 0562 $this->getCurrentBatch()->enlistOperation($tableName, $queryString, Zend_Http_Client::GET, array(), true, null); 0563 $response = $this->getCurrentBatch()->commit(); 0564 0565 // Get inner response (multipart) 0566 $innerResponse = $response->getBody(); 0567 $innerResponse = substr($innerResponse, strpos($innerResponse, 'HTTP/1.1 200 OK')); 0568 $innerResponse = substr($innerResponse, 0, strpos($innerResponse, '--batchresponse')); 0569 $response = Zend_Http_Response::fromString($innerResponse); 0570 } else { 0571 $response = $this->_performRequest($tableName, $queryString, Zend_Http_Client::GET, array(), true, null); 0572 } 0573 0574 if ($response->isSuccessful()) { 0575 // Parse result 0576 $result = $this->_parseResponse($response); 0577 if (!$result) { 0578 return array(); 0579 } 0580 0581 $entries = null; 0582 if ($result->entry) { 0583 if (count($result->entry) > 1) { 0584 $entries = $result->entry; 0585 } else { 0586 $entries = array($result->entry); 0587 } 0588 } else { 0589 // This one is tricky... If we have properties defined, we have an entity. 0590 $properties = $result->xpath('//m:properties'); 0591 if ($properties) { 0592 $entries = array($result); 0593 } else { 0594 return array(); 0595 } 0596 } 0597 0598 // Create return value 0599 $returnValue = array(); 0600 foreach ($entries as $entry) { 0601 // Parse properties 0602 $properties = $entry->xpath('.//m:properties'); 0603 $properties = $properties[0]->children('http://schemas.microsoft.com/ado/2007/08/dataservices'); 0604 0605 // Create entity 0606 $entity = new $entityClass('', ''); 0607 $entity->setAzureValues((array)$properties, $this->_throwExceptionOnMissingData); 0608 0609 // If we have a Zend_Service_WindowsAzure_Storage_DynamicTableEntity, make sure all property types are set 0610 if ($entity instanceof Zend_Service_WindowsAzure_Storage_DynamicTableEntity) { 0611 foreach ($properties as $key => $value) { 0612 $attributes = $value->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'); 0613 $type = (string)$attributes['type']; 0614 if ($type !== '') { 0615 $entity->setAzureProperty($key, (string)$value, $type); 0616 } 0617 } 0618 } 0619 0620 // Update etag 0621 $etag = $entry->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'); 0622 $etag = (string)$etag['etag']; 0623 $entity->setEtag($etag); 0624 0625 // Add to result 0626 $returnValue[] = $entity; 0627 } 0628 0629 // More entities? 0630 if (!is_null($response->getHeader('x-ms-continuation-NextPartitionKey')) && !is_null($response->getHeader('x-ms-continuation-NextRowKey'))) { 0631 if (strpos($queryString, '$top') === false) { 0632 $returnValue = array_merge($returnValue, $this->retrieveEntities($tableName, $filter, $entityClass, $response->getHeader('x-ms-continuation-NextPartitionKey'), $response->getHeader('x-ms-continuation-NextRowKey'))); 0633 } 0634 } 0635 0636 // Return 0637 return $returnValue; 0638 } else { 0639 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0640 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); 0641 } 0642 } 0643 0644 /** 0645 * Update entity by replacing it 0646 * 0647 * @param string $tableName Table name 0648 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update 0649 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) 0650 * @throws Zend_Service_WindowsAzure_Exception 0651 */ 0652 public function updateEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false) 0653 { 0654 return $this->_changeEntity(Zend_Http_Client::PUT, $tableName, $entity, $verifyEtag); 0655 } 0656 0657 /** 0658 * Update entity by adding or updating properties 0659 * 0660 * @param string $tableName Table name 0661 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update 0662 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) 0663 * @param array $properties Properties to merge. All properties will be used when omitted. 0664 * @throws Zend_Service_WindowsAzure_Exception 0665 */ 0666 public function mergeEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false, $properties = array()) 0667 { 0668 $mergeEntity = null; 0669 if (is_array($properties) && count($properties) > 0) { 0670 0671 // Build a new object 0672 $mergeEntity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($entity->getPartitionKey(), $entity->getRowKey()); 0673 0674 // Keep only values mentioned in $properties 0675 $azureValues = $entity->getAzureValues(); 0676 foreach ($azureValues as $key => $value) { 0677 if (in_array($value->Name, $properties)) { 0678 $mergeEntity->setAzureProperty($value->Name, $value->Value, $value->Type); 0679 } 0680 } 0681 } else { 0682 $mergeEntity = $entity; 0683 } 0684 0685 // Ensure entity timestamp matches updated timestamp 0686 $entity->setTimestamp(new DateTime()); 0687 0688 return $this->_changeEntity(Zend_Http_Client::MERGE, $tableName, $mergeEntity, $verifyEtag); 0689 } 0690 0691 /** 0692 * Get error message from Zend_Http_Response 0693 * 0694 * @param Zend_Http_Response $response Repsonse 0695 * @param string $alternativeError Alternative error message 0696 * @return string 0697 */ 0698 protected function _getErrorMessage(Zend_Http_Response $response, $alternativeError = 'Unknown error.') 0699 { 0700 $response = $this->_parseResponse($response); 0701 if ($response && $response->message) { 0702 return (string)$response->message; 0703 } else { 0704 return $alternativeError; 0705 } 0706 } 0707 0708 /** 0709 * Update entity / merge entity 0710 * 0711 * @param string $httpVerb HTTP verb to use (PUT = update, MERGE = merge) 0712 * @param string $tableName Table name 0713 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update 0714 * @param boolean $verifyEtag Verify etag of the entity (used for concurrency) 0715 * @throws Zend_Service_WindowsAzure_Exception 0716 */ 0717 protected function _changeEntity($httpVerb = Zend_Http_Client::PUT, $tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false) 0718 { 0719 if ($tableName === '') { 0720 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0721 throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.'); 0722 } 0723 if (is_null($entity)) { 0724 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0725 throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.'); 0726 } 0727 0728 // Add header information 0729 $headers = array(); 0730 $headers['Content-Type'] = 'application/atom+xml'; 0731 $headers['Content-Length'] = 0; 0732 if (!$verifyEtag) { 0733 $headers['If-Match'] = '*'; 0734 } else { 0735 $headers['If-Match'] = $entity->getEtag(); 0736 } 0737 0738 // Generate request body 0739 $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?> 0740 <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"> 0741 <title /> 0742 <updated>{tpl:Updated}</updated> 0743 <author> 0744 <name /> 0745 </author> 0746 <id /> 0747 <content type="application/xml"> 0748 <m:properties> 0749 {tpl:Properties} 0750 </m:properties> 0751 </content> 0752 </entry>'; 0753 0754 // Attempt to get timestamp from entity 0755 $timestamp = $entity->getTimestamp(); 0756 0757 $requestBody = $this->_fillTemplate($requestBody, array( 0758 'Updated' => $this->_convertToEdmDateTime($timestamp), 0759 'Properties' => $this->_generateAzureRepresentation($entity) 0760 )); 0761 0762 // Add header information 0763 $headers = array(); 0764 $headers['Content-Type'] = 'application/atom+xml'; 0765 if (!$verifyEtag) { 0766 $headers['If-Match'] = '*'; 0767 } else { 0768 $headers['If-Match'] = $entity->getEtag(); 0769 } 0770 0771 // Perform request 0772 $response = null; 0773 if ($this->isInBatch()) { 0774 $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody); 0775 return null; 0776 } else { 0777 $response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody); 0778 } 0779 if ($response->isSuccessful()) { 0780 // Update properties 0781 $entity->setEtag($response->getHeader('Etag')); 0782 $entity->setTimestamp( $this->_convertToDateTime($response->getHeader('Last-modified')) ); 0783 0784 return $entity; 0785 } else { 0786 // require_once 'Zend/Service/WindowsAzure/Exception.php'; 0787 throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.')); 0788 } 0789 } 0790 0791 /** 0792 * Generate RFC 1123 compliant date string 0793 * 0794 * @return string 0795 */ 0796 protected function _rfcDate() 0797 { 0798 return gmdate('D, d M Y H:i:s', time()) . ' GMT'; // RFC 1123 0799 } 0800 0801 /** 0802 * Fill text template with variables from key/value array 0803 * 0804 * @param string $templateText Template text 0805 * @param array $variables Array containing key/value pairs 0806 * @return string 0807 */ 0808 protected function _fillTemplate($templateText, $variables = array()) 0809 { 0810 foreach ($variables as $key => $value) { 0811 $templateText = str_replace('{tpl:' . $key . '}', $value, $templateText); 0812 } 0813 return $templateText; 0814 } 0815 0816 /** 0817 * Generate Azure representation from entity (creates atompub markup from properties) 0818 * 0819 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity 0820 * @return string 0821 */ 0822 protected function _generateAzureRepresentation(Zend_Service_WindowsAzure_Storage_TableEntity $entity = null) 0823 { 0824 // Generate Azure representation from entity 0825 $azureRepresentation = array(); 0826 $azureValues = $entity->getAzureValues(); 0827 foreach ($azureValues as $azureValue) { 0828 $value = array(); 0829 $value[] = '<d:' . $azureValue->Name; 0830 if ($azureValue->Type != '') { 0831 $value[] = ' m:type="' . $azureValue->Type . '"'; 0832 } 0833 if (is_null($azureValue->Value)) { 0834 $value[] = ' m:null="true"'; 0835 } 0836 $value[] = '>'; 0837 0838 if (!is_null($azureValue->Value)) { 0839 if (strtolower($azureValue->Type) == 'edm.boolean') { 0840 $value[] = ($azureValue->Value == true ? '1' : '0'); 0841 } else if (strtolower($azureValue->Type) == 'edm.datetime') { 0842 $value[] = $this->_convertToEdmDateTime($azureValue->Value); 0843 } else { 0844 $value[] = htmlspecialchars($azureValue->Value); 0845 } 0846 } 0847 0848 $value[] = '</d:' . $azureValue->Name . '>'; 0849 $azureRepresentation[] = implode('', $value); 0850 } 0851 0852 return implode('', $azureRepresentation); 0853 } 0854 0855 /** 0856 * Perform request using Zend_Http_Client channel 0857 * 0858 * @param string $path Path 0859 * @param string $queryString Query string 0860 * @param string $httpVerb HTTP verb the request will use 0861 * @param array $headers x-ms headers to add 0862 * @param boolean $forTableStorage Is the request for table storage? 0863 * @param mixed $rawData Optional RAW HTTP data to be sent over the wire 0864 * @param string $resourceType Resource type 0865 * @param string $requiredPermission Required permission 0866 * @return Zend_Http_Response 0867 */ 0868 protected function _performRequest( 0869 $path = '/', 0870 $queryString = '', 0871 $httpVerb = Zend_Http_Client::GET, 0872 $headers = array(), 0873 $forTableStorage = false, 0874 $rawData = null, 0875 $resourceType = Zend_Service_WindowsAzure_Storage::RESOURCE_UNKNOWN, 0876 $requiredPermission = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::PERMISSION_READ 0877 ) { 0878 // Add headers 0879 $headers['DataServiceVersion'] = '1.0;NetFx'; 0880 $headers['MaxDataServiceVersion'] = '1.0;NetFx'; 0881 0882 // Perform request 0883 return parent::_performRequest( 0884 $path, 0885 $queryString, 0886 $httpVerb, 0887 $headers, 0888 $forTableStorage, 0889 $rawData, 0890 $resourceType, 0891 $requiredPermission 0892 ); 0893 } 0894 0895 /** 0896 * Converts a string to a DateTime object. Returns false on failure. 0897 * 0898 * @param string $value The string value to parse 0899 * @return DateTime|boolean 0900 */ 0901 protected function _convertToDateTime($value = '') 0902 { 0903 if ($value instanceof DateTime) { 0904 return $value; 0905 } 0906 0907 try { 0908 if (substr($value, -1) == 'Z') { 0909 $value = substr($value, 0, strlen($value) - 1); 0910 } 0911 return new DateTime($value, new DateTimeZone('UTC')); 0912 } 0913 catch (Exception $ex) { 0914 return false; 0915 } 0916 } 0917 0918 /** 0919 * Converts a DateTime object into an Edm.DaeTime value in UTC timezone, 0920 * represented as a string. 0921 * 0922 * @param DateTime $value 0923 * @return string 0924 */ 0925 protected function _convertToEdmDateTime(DateTime $value) 0926 { 0927 $cloned = clone $value; 0928 $cloned->setTimezone(new DateTimeZone('UTC')); 0929 return str_replace('+0000', 'Z', $cloned->format(DateTime::ISO8601)); 0930 } 0931 }