PageRenderTime 63ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/PhpSiteGenerator/PhpSiteGenerator/Microsoft/WindowsAzure/Storage/Table.php

http://SqlCrudPHPWizard.codeplex.com
PHP | 973 lines | 562 code | 109 blank | 302 comment | 87 complexity | 0bf24b625c998e3ce1556b62bfd91be5 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright (c) 2009, RealDolmen
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are met:
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * * Neither the name of RealDolmen nor the
  14. * names of its contributors may be used to endorse or promote products
  15. * derived from this software without specific prior written permission.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY RealDolmen ''AS IS'' AND ANY
  18. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. * DISCLAIMED. IN NO EVENT SHALL RealDolmen BE LIABLE FOR ANY
  21. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  26. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. *
  28. * @category Microsoft
  29. * @package Microsoft_WindowsAzure
  30. * @subpackage Storage
  31. * @copyright Copyright (c) 2009, RealDolmen (http://www.realdolmen.com)
  32. * @license http://phpazure.codeplex.com/license
  33. * @version $Id: Blob.php 14561 2009-05-07 08:05:12Z unknown $
  34. */
  35. /**
  36. * @see Microsoft_WindowsAzure_Credentials
  37. */
  38. require_once 'Microsoft/WindowsAzure/Credentials.php';
  39. /**
  40. * @see Microsoft_WindowsAzure_SharedKeyCredentials
  41. */
  42. require_once 'Microsoft/WindowsAzure/SharedKeyCredentials.php';
  43. /**
  44. * @see Microsoft_WindowsAzure_SharedKeyLiteCredentials
  45. */
  46. require_once 'Microsoft/WindowsAzure/SharedKeyLiteCredentials.php';
  47. /**
  48. * @see Microsoft_WindowsAzure_RetryPolicy
  49. */
  50. require_once 'Microsoft/WindowsAzure/RetryPolicy.php';
  51. /**
  52. * @see Microsoft_Http_Transport
  53. */
  54. require_once 'Microsoft/Http/Transport.php';
  55. /**
  56. * @see Microsoft_Http_Response
  57. */
  58. require_once 'Microsoft/Http/Response.php';
  59. /**
  60. * @see Microsoft_WindowsAzure_Storage
  61. */
  62. require_once 'Microsoft/WindowsAzure/Storage.php';
  63. /**
  64. * @see Microsoft_WindowsAzure_Storage_BatchStorage
  65. */
  66. require_once 'Microsoft/WindowsAzure/Storage/BatchStorage.php';
  67. /**
  68. * @see Microsoft_WindowsAzure_Storage_TableInstance
  69. */
  70. require_once 'Microsoft/WindowsAzure/Storage/TableInstance.php';
  71. /**
  72. * @see Microsoft_WindowsAzure_Storage_TableEntity
  73. */
  74. require_once 'Microsoft/WindowsAzure/Storage/TableEntity.php';
  75. /**
  76. * @see Microsoft_WindowsAzure_Storage_DynamicTableEntity
  77. */
  78. require_once 'Microsoft/WindowsAzure/Storage/DynamicTableEntity.php';
  79. /**
  80. * @see Microsoft_WindowsAzure_Storage_TableEntityQuery
  81. */
  82. require_once 'Microsoft/WindowsAzure/Storage/TableEntityQuery.php';
  83. /**
  84. * @see Microsoft_WindowsAzure_Exception
  85. */
  86. require_once 'Microsoft/WindowsAzure/Exception.php';
  87. /**
  88. * @category Microsoft
  89. * @package Microsoft_WindowsAzure
  90. * @subpackage Storage
  91. * @copyright Copyright (c) 2009, RealDolmen (http://www.realdolmen.com)
  92. * @license http://phpazure.codeplex.com/license
  93. */
  94. class Microsoft_WindowsAzure_Storage_Table extends Microsoft_WindowsAzure_Storage_BatchStorage
  95. {
  96. /**
  97. * ODBC connection string
  98. *
  99. * @var string
  100. */
  101. protected $_odbcConnectionString;
  102. /**
  103. * ODBC user name
  104. *
  105. * @var string
  106. */
  107. protected $_odbcUsername;
  108. /**
  109. * ODBC password
  110. *
  111. * @var string
  112. */
  113. protected $_odbcPassword;
  114. /**
  115. * Creates a new Microsoft_WindowsAzure_Storage_Table instance
  116. *
  117. * @param string $host Storage host name
  118. * @param string $accountName Account name for Windows Azure
  119. * @param string $accountKey Account key for Windows Azure
  120. * @param boolean $usePathStyleUri Use path-style URI's
  121. * @param Microsoft_WindowsAzure_RetryPolicy $retryPolicy Retry policy to use when making requests
  122. */
  123. 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)
  124. {
  125. parent::__construct($host, $accountName, $accountKey, $usePathStyleUri, $retryPolicy);
  126. // Always use SharedKeyLite authentication
  127. $this->_credentials = new Microsoft_WindowsAzure_SharedKeyLiteCredentials($accountName, $accountKey, $this->_usePathStyleUri);
  128. // API version
  129. $this->_apiVersion = '2009-04-14';
  130. }
  131. /**
  132. * Set ODBC connection settings - used for creating tables on development storage
  133. *
  134. * @param string $connectionString ODBC connection string
  135. * @param string $username ODBC user name
  136. * @param string $password ODBC password
  137. */
  138. public function setOdbcSettings($connectionString, $username, $password)
  139. {
  140. $this->_odbcConnectionString = $connectionString;
  141. $this->_odbcUserame = $username;
  142. $this->_odbcPassword = $password;
  143. }
  144. /**
  145. * Generate table on development storage
  146. *
  147. * Note 1: ODBC settings must be specified first using setOdbcSettings()
  148. * Note 2: Development table storage MST BE RESTARTED after generating tables. Otherwise newly create tables will NOT be accessible!
  149. *
  150. * @param string $entityClass Entity class name
  151. * @param string $tableName Table name
  152. */
  153. public function generateDevelopmentTable($entityClass, $tableName)
  154. {
  155. // Check if we can do this...
  156. if (!function_exists('odbc_connect'))
  157. throw new Microsoft_WindowsAzure_Exception('Function odbc_connect does not exist. Please enable the php_odbc.dll module in your php.ini.');
  158. if ($this->_odbcConnectionString == '')
  159. throw new Microsoft_WindowsAzure_Exception('Please specify ODBC settings first using setOdbcSettings().');
  160. if ($entityClass === '')
  161. throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
  162. // Get accessors
  163. $accessors = Microsoft_WindowsAzure_Storage_TableEntity::getAzureAccessors($entityClass);
  164. // Generate properties
  165. $properties = array();
  166. foreach ($accessors as $accessor)
  167. {
  168. if ($accessor->AzurePropertyName == 'Timestamp'
  169. || $accessor->AzurePropertyName == 'PartitionKey'
  170. || $accessor->AzurePropertyName == 'RowKey')
  171. {
  172. continue;
  173. }
  174. switch (strtolower($accessor->AzurePropertyType))
  175. {
  176. case 'edm.int32':
  177. case 'edm.int64':
  178. $sqlType = '[int] NULL'; break;
  179. case 'edm.guid':
  180. $sqlType = '[uniqueidentifier] NULL'; break;
  181. case 'edm.datetime':
  182. $sqlType = '[datetime] NULL'; break;
  183. case 'edm.boolean':
  184. $sqlType = '[bit] NULL'; break;
  185. case 'edm.double':
  186. $sqlType = '[decimal] NULL'; break;
  187. default:
  188. $sqlType = '[nvarchar](1000) NULL'; break;
  189. }
  190. $properties[] = '[' . $accessor->AzurePropertyName . '] ' . $sqlType;
  191. }
  192. // Generate SQL
  193. $sql = 'CREATE TABLE [dbo].[{tpl:TableName}](
  194. {tpl:Properties} {tpl:PropertiesComma}
  195. [Timestamp] [datetime] NULL,
  196. [PartitionKey] [nvarchar](1000) NOT NULL,
  197. [RowKey] [nvarchar](1000) NOT NULL
  198. );
  199. ALTER TABLE [dbo].[{tpl:TableName}] ADD PRIMARY KEY (PartitionKey, RowKey);';
  200. $sql = $this->fillTemplate($sql, array(
  201. 'TableName' => $tableName,
  202. 'Properties' => implode(',', $properties),
  203. 'PropertiesComma' => count($properties) > 0 ? ',' : ''
  204. ));
  205. // Connect to database
  206. $db = @odbc_connect($this->_odbcConnectionString, $this->_odbcUserame, $this->_odbcPassword);
  207. if (!$db)
  208. {
  209. throw new Microsoft_WindowsAzure_Exception('Could not connect to database via ODBC.');
  210. }
  211. // Create table
  212. odbc_exec($db, $sql);
  213. // Close connection
  214. odbc_close($db);
  215. }
  216. /**
  217. * Check if a table exists
  218. *
  219. * @param string $tableName Table name
  220. * @return boolean
  221. */
  222. public function tableExists($tableName = '')
  223. {
  224. if ($tableName === '')
  225. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  226. // List tables
  227. $tables = $this->listTables($tableName);
  228. foreach ($tables as $table)
  229. {
  230. if ($table->Name == $tableName)
  231. return true;
  232. }
  233. return false;
  234. }
  235. /**
  236. * List tables
  237. *
  238. * @param string $nextTableName Next table name, used for listing tables when total amount of tables is > 1000.
  239. * @return array
  240. * @throws Microsoft_WindowsAzure_Exception
  241. */
  242. public function listTables($nextTableName = '')
  243. {
  244. // Build query string
  245. $queryString = '';
  246. if ($nextTableName != '')
  247. {
  248. $queryString = '?NextTableName=' . $nextTableName;
  249. }
  250. // Perform request
  251. $response = $this->performRequest('Tables', $queryString, Microsoft_Http_Transport::VERB_GET, null, true);
  252. if ($response->isSuccessful())
  253. {
  254. // Parse result
  255. $result = $this->parseResponse($response);
  256. if (!$result || !$result->entry)
  257. return array();
  258. $entries = null;
  259. if (count($result->entry) > 1)
  260. {
  261. $entries = $result->entry;
  262. }
  263. else
  264. {
  265. $entries = array($result->entry);
  266. }
  267. // Create return value
  268. $returnValue = array();
  269. foreach ($entries as $entry)
  270. {
  271. $tableName = $entry->xpath('.//m:properties/d:TableName');
  272. $tableName = (string)$tableName[0];
  273. $returnValue[] = new Microsoft_WindowsAzure_Storage_TableInstance(
  274. (string)$entry->id,
  275. $tableName,
  276. (string)$entry->link['href'],
  277. (string)$entry->updated
  278. );
  279. }
  280. // More tables?
  281. if (!is_null($response->getHeader('x-ms-continuation-NextTableName')))
  282. {
  283. $returnValue = array_merge($returnValue, $this->listTables($response->getHeader('x-ms-continuation-NextTableName')));
  284. }
  285. return $returnValue;
  286. }
  287. else
  288. {
  289. throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
  290. }
  291. }
  292. /**
  293. * Create table
  294. *
  295. * @param string $tableName Table name
  296. * @return Microsoft_WindowsAzure_Storage_TableInstance
  297. * @throws Microsoft_WindowsAzure_Exception
  298. */
  299. public function createTable($tableName = '')
  300. {
  301. if ($tableName === '')
  302. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  303. // Generate request body
  304. $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  305. <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">
  306. <id>{tpl:BaseUrl}/Tables(\'{tpl:TableName}\')</id>
  307. <title type="text"></title>
  308. <updated>{tpl:Updated}</updated>
  309. <author>
  310. <name />
  311. </author>
  312. <link rel="edit" title="Tables" href="Tables(\'{tpl:TableName}\')" />
  313. <category term="{tpl:AccountName}.Tables" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  314. <content type="application/xml">
  315. <m:properties>
  316. <d:TableName>{tpl:TableName}</d:TableName>
  317. </m:properties>
  318. </content>
  319. </entry>';
  320. $requestBody = $this->fillTemplate($requestBody, array(
  321. 'BaseUrl' => $this->getBaseUrl(),
  322. 'TableName' => $tableName,
  323. 'Updated' => $this->isoDate(),
  324. 'AccountName' => $this->_accountName
  325. ));
  326. // Add header information
  327. $headers = array();
  328. $headers['Content-Type'] = 'application/atom+xml';
  329. // Perform request
  330. $response = $this->performRequest('Tables', '', Microsoft_Http_Transport::VERB_POST, $headers, true, $requestBody);
  331. if ($response->isSuccessful())
  332. {
  333. // Parse response
  334. $entry = $this->parseResponse($response);
  335. $tableName = $entry->xpath('.//m:properties/d:TableName');
  336. $tableName = (string)$tableName[0];
  337. return new Microsoft_WindowsAzure_Storage_TableInstance(
  338. (string)$entry->id,
  339. $tableName,
  340. (string)$entry->link['href'],
  341. (string)$entry->updated
  342. );
  343. }
  344. else
  345. {
  346. throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
  347. }
  348. }
  349. /**
  350. * Delete table
  351. *
  352. * @param string $tableName Table name
  353. * @throws Microsoft_WindowsAzure_Exception
  354. */
  355. public function deleteTable($tableName = '')
  356. {
  357. if ($tableName === '')
  358. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  359. // Add header information
  360. $headers = array();
  361. $headers['Content-Type'] = 'application/atom+xml';
  362. // Perform request
  363. $response = $this->performRequest('Tables(\'' . $tableName . '\')', '', Microsoft_Http_Transport::VERB_DELETE, $headers, true, null);
  364. if (!$response->isSuccessful())
  365. {
  366. throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
  367. }
  368. }
  369. /**
  370. * Insert entity into table
  371. *
  372. * @param string $tableName Table name
  373. * @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to insert
  374. * @return Microsoft_WindowsAzure_Storage_TableEntity
  375. * @throws Microsoft_WindowsAzure_Exception
  376. */
  377. public function insertEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null)
  378. {
  379. if ($tableName === '')
  380. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  381. if (is_null($entity))
  382. throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
  383. // Generate request body
  384. $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  385. <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">
  386. <title />
  387. <updated>{tpl:Updated}</updated>
  388. <author>
  389. <name />
  390. </author>
  391. <id />
  392. <content type="application/xml">
  393. <m:properties>
  394. {tpl:Properties}
  395. </m:properties>
  396. </content>
  397. </entry>';
  398. $requestBody = $this->fillTemplate($requestBody, array(
  399. 'Updated' => $this->isoDate(),
  400. 'Properties' => $this->generateAzureRepresentation($entity)
  401. ));
  402. // Add header information
  403. $headers = array();
  404. $headers['Content-Type'] = 'application/atom+xml';
  405. // Perform request
  406. $response = null;
  407. if ($this->isInBatch())
  408. {
  409. $this->getCurrentBatch()->enlistOperation($tableName, '', Microsoft_Http_Transport::VERB_POST, $headers, true, $requestBody);
  410. return null;
  411. }
  412. else
  413. {
  414. $response = $this->performRequest($tableName, '', Microsoft_Http_Transport::VERB_POST, $headers, true, $requestBody);
  415. }
  416. if ($response->isSuccessful())
  417. {
  418. // Parse result
  419. $result = $this->parseResponse($response);
  420. $timestamp = $result->xpath('//m:properties/d:Timestamp');
  421. $timestamp = (string)$timestamp[0];
  422. $etag = $result->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  423. $etag = (string)$etag['etag'];
  424. // Update properties
  425. $entity->setTimestamp($timestamp);
  426. $entity->setEtag($etag);
  427. return $entity;
  428. }
  429. else
  430. {
  431. throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
  432. }
  433. }
  434. /**
  435. * Delete entity from table
  436. *
  437. * @param string $tableName Table name
  438. * @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to delete
  439. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  440. * @throws Microsoft_WindowsAzure_Exception
  441. */
  442. public function deleteEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
  443. {
  444. if ($tableName === '')
  445. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  446. if (is_null($entity))
  447. throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
  448. // Add header information
  449. $headers = array();
  450. if (!$this->isInBatch()) // http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/9e255447-4dc7-458a-99d3-bdc04bdc5474/
  451. $headers['Content-Type'] = 'application/atom+xml';
  452. $headers['Content-Length'] = 0;
  453. if (!$verifyEtag)
  454. {
  455. $headers['If-Match'] = '*';
  456. }
  457. else
  458. {
  459. $headers['If-Match'] = $entity->getEtag();
  460. }
  461. // Perform request
  462. $response = null;
  463. if ($this->isInBatch())
  464. {
  465. $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Microsoft_Http_Transport::VERB_DELETE, $headers, true, null);
  466. return null;
  467. }
  468. else
  469. {
  470. $response = $this->performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Microsoft_Http_Transport::VERB_DELETE, $headers, true, null);
  471. }
  472. if (!$response->isSuccessful())
  473. {
  474. throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
  475. }
  476. }
  477. /**
  478. * Retrieve entity from table, by id
  479. *
  480. * @param string $tableName Table name
  481. * @param string $partitionKey Partition key
  482. * @param string $rowKey Row key
  483. * @param string $entityClass Entity class name*
  484. * @return Microsoft_WindowsAzure_Storage_TableEntity
  485. * @throws Microsoft_WindowsAzure_Exception
  486. */
  487. public function retrieveEntityById($tableName = '', $partitionKey = '', $rowKey = '', $entityClass = 'Microsoft_WindowsAzure_Storage_DynamicTableEntity')
  488. {
  489. if ($tableName === '')
  490. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  491. if ($partitionKey === '')
  492. throw new Microsoft_WindowsAzure_Exception('Partition key is not specified.');
  493. //if ($rowKey === '')
  494. // throw new Microsoft_WindowsAzure_Exception('Row key is not specified.');
  495. if ($entityClass === '')
  496. throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
  497. // Check for combined size of partition key and row key
  498. // http://msdn.microsoft.com/en-us/library/dd179421.aspx
  499. if (strlen($partitionKey . $rowKey) >= 256)
  500. {
  501. // Start a batch if possible
  502. if ($this->isInBatch())
  503. throw new Microsoft_WindowsAzure_Exception('Entity cannot be retrieved. A transaction is required to retrieve the entity, but another transaction is already active.');
  504. $this->startBatch();
  505. }
  506. // Fetch entities from Azure
  507. $result = $this->retrieveEntities(
  508. $this->select()
  509. ->from($tableName)
  510. ->wherePartitionKey($partitionKey)
  511. ->whereRowKey($rowKey),
  512. '',
  513. $entityClass
  514. );
  515. // Return
  516. if (count($result) == 1)
  517. {
  518. return $result[0];
  519. }
  520. return null;
  521. }
  522. /**
  523. * Create a new Microsoft_WindowsAzure_Storage_TableEntityQuery
  524. *
  525. * @return Microsoft_WindowsAzure_Storage_TableEntityQuery
  526. */
  527. public function select()
  528. {
  529. return new Microsoft_WindowsAzure_Storage_TableEntityQuery();
  530. }
  531. /**
  532. * Retrieve entities from table
  533. *
  534. * @param string $tableName|Microsoft_WindowsAzure_Storage_TableEntityQuery Table name -or- Microsoft_WindowsAzure_Storage_TableEntityQuery instance
  535. * @param string $filter Filter condition (not applied when $tableName is a Microsoft_WindowsAzure_Storage_TableEntityQuery instance)
  536. * @param string $entityClass Entity class name
  537. * @param string $nextPartitionKey Next partition key, used for listing entities when total amount of entities is > 1000.
  538. * @param string $nextRowKey Next row key, used for listing entities when total amount of entities is > 1000.
  539. * @return array Array of Microsoft_WindowsAzure_Storage_TableEntity
  540. * @throws Microsoft_WindowsAzure_Exception
  541. */
  542. public function retrieveEntities($tableName = '', $filter = '', $entityClass = 'Microsoft_WindowsAzure_Storage_DynamicTableEntity', $nextPartitionKey = null, $nextRowKey = null)
  543. {
  544. if ($tableName === '')
  545. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  546. if ($entityClass === '')
  547. throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
  548. // Convenience...
  549. if (class_exists($filter))
  550. {
  551. $entityClass = $filter;
  552. $filter = '';
  553. }
  554. // Query string
  555. $queryString = '';
  556. // Determine query
  557. if (is_string($tableName))
  558. {
  559. // Option 1: $tableName is a string
  560. // Append parentheses
  561. $tableName .= '()';
  562. // Build query
  563. $query = array();
  564. // Filter?
  565. if ($filter !== '')
  566. {
  567. $query[] = '$filter=' . rawurlencode($filter);
  568. }
  569. // Build queryString
  570. if (count($query) > 0)
  571. {
  572. $queryString = '?' . implode('&', $query);
  573. }
  574. }
  575. else if (get_class($tableName) == 'Microsoft_WindowsAzure_Storage_TableEntityQuery')
  576. {
  577. // Option 2: $tableName is a Microsoft_WindowsAzure_Storage_TableEntityQuery instance
  578. // Build queryString
  579. $queryString = $tableName->assembleQueryString(true);
  580. // Change $tableName
  581. $tableName = $tableName->assembleFrom(true);
  582. }
  583. else
  584. {
  585. throw new Microsoft_WindowsAzure_Exception('Invalid argument: $tableName');
  586. }
  587. // Add continuation querystring parameters?
  588. if (!is_null($nextPartitionKey) && !is_null($nextRowKey))
  589. {
  590. if ($queryString !== '')
  591. $queryString .= '&';
  592. $queryString .= '&NextPartitionKey=' . rawurlencode($nextPartitionKey) . '&NextRowKey=' . rawurlencode($nextRowKey);
  593. }
  594. // Perform request
  595. $response = null;
  596. if ($this->isInBatch() && $this->getCurrentBatch()->getOperationCount() == 0)
  597. {
  598. $this->getCurrentBatch()->enlistOperation($tableName, $queryString, Microsoft_Http_Transport::VERB_GET, array(), true, null);
  599. $response = $this->getCurrentBatch()->commit();
  600. // Get inner response (multipart)
  601. $innerResponse = $response->getBody();
  602. $innerResponse = substr($innerResponse, strpos($innerResponse, 'HTTP/1.1 200 OK'));
  603. $innerResponse = substr($innerResponse, 0, strpos($innerResponse, '--batchresponse'));
  604. $response = Microsoft_Http_Response::fromString($innerResponse);
  605. }
  606. else
  607. {
  608. $response = $this->performRequest($tableName, $queryString, Microsoft_Http_Transport::VERB_GET, array(), true, null);
  609. }
  610. if ($response->isSuccessful())
  611. {
  612. // Parse result
  613. $result = $this->parseResponse($response);
  614. if (!$result)
  615. return array();
  616. $entries = null;
  617. if ($result->entry)
  618. {
  619. if (count($result->entry) > 1)
  620. {
  621. $entries = $result->entry;
  622. }
  623. else
  624. {
  625. $entries = array($result->entry);
  626. }
  627. }
  628. else
  629. {
  630. // This one is tricky... If we have properties defined, we have an entity.
  631. $properties = $result->xpath('//m:properties');
  632. if ($properties)
  633. {
  634. $entries = array($result);
  635. }
  636. else
  637. {
  638. return array();
  639. }
  640. }
  641. // Create return value
  642. $returnValue = array();
  643. foreach ($entries as $entry)
  644. {
  645. // Parse properties
  646. $properties = $entry->xpath('.//m:properties');
  647. $properties = $properties[0]->children('http://schemas.microsoft.com/ado/2007/08/dataservices');
  648. // Create entity
  649. $entity = new $entityClass('', '');
  650. $entity->setAzureValues((array)$properties, true);
  651. // If we have a Microsoft_WindowsAzure_Storage_DynamicTableEntity, make sure all property types are OK
  652. if ($entity instanceof Microsoft_WindowsAzure_Storage_DynamicTableEntity)
  653. {
  654. foreach ($properties as $key => $value)
  655. {
  656. $attributes = $value->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  657. $type = (string)$attributes['type'];
  658. if ($type !== '')
  659. {
  660. $entity->setAzurePropertyType($key, $type);
  661. }
  662. }
  663. }
  664. // Update etag
  665. $etag = $entry->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  666. $etag = (string)$etag['etag'];
  667. $entity->setEtag($etag);
  668. // Add to result
  669. $returnValue[] = $entity;
  670. }
  671. // More entities?
  672. if (!is_null($response->getHeader('x-ms-continuation-NextPartitionKey')) && !is_null($response->getHeader('x-ms-continuation-NextRowKey')))
  673. {
  674. if (strpos($queryString, '$top') === false)
  675. $returnValue = array_merge($returnValue, $this->retrieveEntities($tableName, $filter, $entityClass, $response->getHeader('x-ms-continuation-NextPartitionKey'), $response->getHeader('x-ms-continuation-NextRowKey')));
  676. }
  677. // Return
  678. return $returnValue;
  679. }
  680. else
  681. {
  682. throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
  683. }
  684. }
  685. /**
  686. * Update entity by replacing it
  687. *
  688. * @param string $tableName Table name
  689. * @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to update
  690. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  691. * @throws Microsoft_WindowsAzure_Exception
  692. */
  693. public function updateEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
  694. {
  695. return $this->changeEntity(Microsoft_Http_Transport::VERB_PUT, $tableName, $entity, $verifyEtag);
  696. }
  697. /**
  698. * Update entity by adding properties
  699. *
  700. * @param string $tableName Table name
  701. * @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to update
  702. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  703. * @throws Microsoft_WindowsAzure_Exception
  704. */
  705. public function mergeEntity($tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
  706. {
  707. return $this->changeEntity(Microsoft_Http_Transport::VERB_MERGE, $tableName, $entity, $verifyEtag);
  708. }
  709. /**
  710. * Get error message from Microsoft_Http_Response
  711. *
  712. * @param Microsoft_Http_Response $response Repsonse
  713. * @param string $alternativeError Alternative error message
  714. * @return string
  715. */
  716. protected function getErrorMessage(Microsoft_Http_Response $response, $alternativeError = 'Unknwon error.')
  717. {
  718. $response = $this->parseResponse($response);
  719. if ($response && $response->message)
  720. return (string)$response->message;
  721. else
  722. return $alternativeError;
  723. }
  724. /**
  725. * Update entity / merge entity
  726. *
  727. * @param string $httpVerb HTTP verb to use (PUT = update, MERGE = merge)
  728. * @param string $tableName Table name
  729. * @param Microsoft_WindowsAzure_Storage_TableEntity $entity Entity to update
  730. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  731. * @throws Microsoft_WindowsAzure_Exception
  732. */
  733. protected function changeEntity($httpVerb = Microsoft_Http_Transport::VERB_PUT, $tableName = '', Microsoft_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
  734. {
  735. if ($tableName === '')
  736. throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  737. if (is_null($entity))
  738. throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
  739. // Add header information
  740. $headers = array();
  741. $headers['Content-Type'] = 'application/atom+xml';
  742. $headers['Content-Length'] = 0;
  743. if (!$verifyEtag)
  744. {
  745. $headers['If-Match'] = '*';
  746. }
  747. else
  748. {
  749. $headers['If-Match'] = $entity->getEtag();
  750. }
  751. // Generate request body
  752. $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  753. <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">
  754. <title />
  755. <updated>{tpl:Updated}</updated>
  756. <author>
  757. <name />
  758. </author>
  759. <id />
  760. <content type="application/xml">
  761. <m:properties>
  762. {tpl:Properties}
  763. </m:properties>
  764. </content>
  765. </entry>';
  766. $requestBody = $this->fillTemplate($requestBody, array(
  767. 'Updated' => $this->isoDate(),
  768. 'Properties' => $this->generateAzureRepresentation($entity)
  769. ));
  770. // Add header information
  771. $headers = array();
  772. $headers['Content-Type'] = 'application/atom+xml';
  773. if (!$verifyEtag)
  774. {
  775. $headers['If-Match'] = '*';
  776. }
  777. else
  778. {
  779. $headers['If-Match'] = $entity->getEtag();
  780. }
  781. // Perform request
  782. $response = null;
  783. if ($this->isInBatch())
  784. {
  785. $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
  786. return null;
  787. }
  788. else
  789. {
  790. $response = $this->performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
  791. }
  792. if ($response->isSuccessful())
  793. {
  794. // Update properties
  795. $entity->setEtag($response->getHeader('Etag'));
  796. $entity->setTimestamp($response->getHeader('Last-modified'));
  797. return $entity;
  798. }
  799. else
  800. {
  801. throw new Microsoft_WindowsAzure_Exception($this->getErrorMessage($response, 'Resource could not be accessed.'));
  802. }
  803. }
  804. /**
  805. * Generate RFC 1123 compliant date string
  806. *
  807. * @return string
  808. */
  809. protected function rfcDate()
  810. {
  811. return gmdate('D, d M Y H:i:s', time()) . ' GMT'; // RFC 1123
  812. }
  813. /**
  814. * Fill text template with variables from key/value array
  815. *
  816. * @param string $templateText Template text
  817. * @param array $variables Array containing key/value pairs
  818. * @return string
  819. */
  820. protected function fillTemplate($templateText, $variables = array())
  821. {
  822. foreach ($variables as $key => $value)
  823. {
  824. $templateText = str_replace('{tpl:' . $key . '}', $value, $templateText);
  825. }
  826. return $templateText;
  827. }
  828. /**
  829. * Generate Azure representation from entity (creates atompub markup from properties)
  830. *
  831. * @param Microsoft_WindowsAzure_Storage_TableEntity $entity
  832. * @return string
  833. */
  834. protected function generateAzureRepresentation(Microsoft_WindowsAzure_Storage_TableEntity $entity = null)
  835. {
  836. // Generate Azure representation from entity
  837. $azureRepresentation = array();
  838. $azureValues = $entity->getAzureValues();
  839. foreach ($azureValues as $azureValue)
  840. {
  841. $value = array();
  842. $value[] = '<d:' . $azureValue->Name;
  843. if ($azureValue->Type != '')
  844. $value[] = ' m:type="' . $azureValue->Type . '"';
  845. if (is_null($azureValue->Value))
  846. $value[] = ' m:null="true"';
  847. $value[] = '>';
  848. if (!is_null($azureValue->Value))
  849. {
  850. if (strtolower($azureValue->Type) == 'edm.boolean')
  851. {
  852. $value[] = ($azureValue->Value == true ? '1' : '0');
  853. }
  854. else
  855. {
  856. $value[] = $azureValue->Value;
  857. }
  858. }
  859. $value[] = '</d:' . $azureValue->Name . '>';
  860. $azureRepresentation[] = implode('', $value);
  861. }
  862. return implode('', $azureRepresentation);
  863. }
  864. }