Microsoft_WindowsAzure
[ class tree: Microsoft_WindowsAzure ] [ index: Microsoft_WindowsAzure ] [ all elements ]

Source for file Table.php

Documentation is available at Table.php

  1. <?php
  2. /**
  3.  * Copyright (c) 2009 - 2011, 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 - 2011, 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. /**
  37.  * @see Microsoft_AutoLoader
  38.  */
  39. require_once dirname(__FILE__'/../../AutoLoader.php';
  40.  
  41.  
  42. /**
  43.  * @category   Microsoft
  44.  * @package    Microsoft_WindowsAzure
  45.  * @subpackage Storage
  46.  * @copyright  Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com)
  47.  * @license    http://phpazure.codeplex.com/license
  48.  */
  49. {
  50.     /**
  51.      * Throw Microsoft_WindowsAzure_Exception when a property is not specified in Windows Azure?
  52.      * Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET.
  53.      * 
  54.      * @var boolean 
  55.      */
  56.     protected $_throwExceptionOnMissingData = true;
  57.     
  58.     /**
  59.      * Throw Microsoft_WindowsAzure_Exception when a property is not specified in Windows Azure?
  60.      * Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET.
  61.      * 
  62.      * @param boolean $value 
  63.      */
  64.     public function setThrowExceptionOnMissingData($value true)
  65.     {
  66.         $this->_throwExceptionOnMissingData = $value;
  67.     }
  68.     
  69.     /**
  70.      * Throw Microsoft_WindowsAzure_Exception when a property is not specified in Windows Azure?
  71.      */
  72.     public function getThrowExceptionOnMissingData()
  73.     {
  74.         return $this->_throwExceptionOnMissingData;
  75.     }
  76.     
  77.     /**
  78.      * Creates a new Microsoft_WindowsAzure_Storage_Table instance
  79.      *
  80.      * @param string $host Storage host name
  81.      * @param string $accountName Account name for Windows Azure
  82.      * @param string $accountKey Account key for Windows Azure
  83.      * @param boolean $usePathStyleUri Use path-style URI's
  84.      * @param Microsoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
  85.      */
  86.     public function __construct($host Microsoft_WindowsAzure_Storage::URL_DEV_TABLE$accountName Microsoft_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_ACCOUNT$accountKey Microsoft_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_KEY$usePathStyleUri falseMicrosoft_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy null)
  87.     {
  88.         parent::__construct($host$accountName$accountKey$usePathStyleUri$retryPolicy);
  89.  
  90.         // Always use SharedKeyLite authentication
  91.         $this->_credentials = new Microsoft_WindowsAzure_Credentials_SharedKeyLite($accountName$accountKey$this->_usePathStyleUri);
  92.         
  93.         // API version
  94.         $this->_apiVersion = '2009-09-19';
  95.     }
  96.     
  97.     /**
  98.      * Check if a table exists
  99.      * 
  100.      * @param string $tableName Table name
  101.      * @return boolean 
  102.      */
  103.     public function tableExists($tableName '')
  104.     {
  105.         if ($tableName === ''{
  106.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  107.         }
  108.             
  109.         // List tables
  110.         $tables $this->listTables()// 2009-09-19 does not support $this->listTables($tableName); all of a sudden...
  111.         foreach ($tables as $table{
  112.             if ($table->Name == $tableName{
  113.                 return true;
  114.             }
  115.         }
  116.         
  117.         return false;
  118.     }
  119.     
  120.     /**
  121.      * List tables
  122.      *
  123.      * @param  string $nextTableName Next table name, used for listing tables when total amount of tables is > 1000.
  124.      * @return array 
  125.      * @throws Microsoft_WindowsAzure_Exception
  126.      */
  127.     public function listTables($nextTableName '')
  128.     {
  129.         // Build query string
  130.         $queryString array();
  131.         if ($nextTableName != ''{
  132.             $queryString['NextTableName=' $nextTableName;
  133.         }
  134.         $queryString self::createQueryStringFromArray($queryString);
  135.         
  136.         // Perform request
  137.         $response $this->_performRequest('Tables'$queryStringMicrosoft_Http_Client::GETnulltrue);
  138.         if ($response->isSuccessful()) {        
  139.             // Parse result
  140.             $result $this->_parseResponse($response);    
  141.             
  142.             if (!$result || !$result->entry{
  143.                 return array();
  144.             }
  145.             
  146.             $entries null;
  147.             if (count($result->entry1{
  148.                 $entries $result->entry;
  149.             else {
  150.                 $entries array($result->entry);
  151.             }
  152.  
  153.             // Create return value
  154.             $returnValue array();            
  155.             foreach ($entries as $entry{
  156.                 $tableName $entry->xpath('.//m:properties/d:TableName');
  157.                 $tableName = (string)$tableName[0];
  158.                 
  159.                 $returnValue[new Microsoft_WindowsAzure_Storage_TableInstance(
  160.                     (string)$entry->id,
  161.                     $tableName,
  162.                     (string)$entry->link['href'],
  163.                     (string)$entry->updated
  164.                 );
  165.             }
  166.             
  167.             // More tables?
  168.             if (!is_null($response->getHeader('x-ms-continuation-NextTableName'))) {
  169.                 $returnValue array_merge($returnValue$this->listTables($response->getHeader('x-ms-continuation-NextTableName')));
  170.             }
  171.  
  172.             return $returnValue;
  173.         else {
  174.             throw new Microsoft_WindowsAzure_Exception($this->_getErrorMessage($response'Resource could not be accessed.'));
  175.         }
  176.     }
  177.     
  178.     /**
  179.      * Create table
  180.      *
  181.      * @param string $tableName Table name
  182.      * @return Microsoft_WindowsAzure_Storage_TableInstance 
  183.      * @throws Microsoft_WindowsAzure_Exception
  184.      */
  185.     public function createTable($tableName '')
  186.     {
  187.         if ($tableName === ''{
  188.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  189.         }
  190.             
  191.         // Generate request body
  192.         $requestBody '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  193.                         <entry
  194.                             xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
  195.                             xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
  196.                             xmlns="http://www.w3.org/2005/Atom">
  197.                           <title />
  198.                           <updated>{tpl:Updated}</updated>
  199.                           <author>
  200.                             <name />
  201.                           </author>
  202.                           <id />
  203.                           <content type="application/xml">
  204.                             <m:properties>
  205.                               <d:TableName>{tpl:TableName}</d:TableName>
  206.                             </m:properties>
  207.                           </content>
  208.                         </entry>';
  209.         
  210.         $requestBody $this->_fillTemplate($requestBodyarray(
  211.             'BaseUrl' => $this->getBaseUrl(),
  212.             'TableName' => htmlspecialchars($tableName),
  213.             'Updated' => $this->isoDate(),
  214.             'AccountName' => $this->_accountName
  215.         ));
  216.         
  217.         // Add header information
  218.         $headers array();
  219.         $headers['Content-Type''application/atom+xml';
  220.         $headers['DataServiceVersion''1.0;NetFx';
  221.         $headers['MaxDataServiceVersion''1.0;NetFx';        
  222.  
  223.         // Perform request
  224.         $response $this->_performRequest('Tables'''Microsoft_Http_Client::POST$headerstrue$requestBody);
  225.         if ($response->isSuccessful()) {
  226.             // Parse response
  227.             $entry $this->_parseResponse($response);
  228.             
  229.             $tableName $entry->xpath('.//m:properties/d:TableName');
  230.             $tableName = (string)$tableName[0];
  231.                 
  232.             return new Microsoft_WindowsAzure_Storage_TableInstance(
  233.                 (string)$entry->id,
  234.                 $tableName,
  235.                 (string)$entry->link['href'],
  236.                 (string)$entry->updated
  237.             );
  238.         else {
  239.             throw new Microsoft_WindowsAzure_Exception($this->_getErrorMessage($response'Resource could not be accessed.'));
  240.         }
  241.     }
  242.     
  243.     /**
  244.      * Create table if it does not exist
  245.      *
  246.      * @param string $tableName Table name
  247.      * @throws Microsoft_WindowsAzure_Exception
  248.      */
  249.     public function createTableIfNotExists($tableName '')
  250.     {
  251.         if (!$this->tableExists($tableName)) {
  252.             $this->createTable($tableName);
  253.         }
  254.     }
  255.     
  256.     /**
  257.      * Delete table
  258.      *
  259.      * @param string $tableName Table name
  260.      * @throws Microsoft_WindowsAzure_Exception
  261.      */
  262.     public function deleteTable($tableName '')
  263.     {
  264.         if ($tableName === ''{
  265.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  266.         }
  267.  
  268.         // Add header information
  269.         $headers array();
  270.         $headers['Content-Type''application/atom+xml';
  271.  
  272.         // Perform request
  273.         $response $this->_performRequest('Tables(\'' $tableName '\')'''Microsoft_Http_Client::DELETE$headerstruenull);
  274.         if (!$response->isSuccessful()) {
  275.             throw new Microsoft_WindowsAzure_Exception($this->_getErrorMessage($response'Resource could not be accessed.'));
  276.         }
  277.     }
  278.     
  279.     /**
  280.      * Insert entity into table
  281.      * 
  282.      * @param string                              $tableName   Table name
  283.      * @param Microsoft_WindowsAzure_Storage_TableEntity $entity      Entity to insert
  284.      * @return Microsoft_WindowsAzure_Storage_TableEntity 
  285.      * @throws Microsoft_WindowsAzure_Exception
  286.      */
  287.     public function insertEntity($tableName ''Microsoft_WindowsAzure_Storage_TableEntity $entity null)
  288.     {
  289.         if ($tableName === ''{
  290.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  291.         }
  292.         if (is_null($entity)) {
  293.             throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
  294.         }
  295.                              
  296.         // Generate request body
  297.         $requestBody '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  298.                         <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">
  299.                           <title />
  300.                           <updated>{tpl:Updated}</updated>
  301.                           <author>
  302.                             <name />
  303.                           </author>
  304.                           <id />
  305.                           <content type="application/xml">
  306.                             <m:properties>
  307.                               {tpl:Properties}
  308.                             </m:properties>
  309.                           </content>
  310.                         </entry>';
  311.         
  312.         $requestBody $this->_fillTemplate($requestBodyarray(
  313.             'Updated'    => $this->isoDate(),
  314.             'Properties' => $this->_generateAzureRepresentation($entity)
  315.         ));
  316.  
  317.         // Add header information
  318.         $headers array();
  319.         $headers['Content-Type''application/atom+xml';
  320.  
  321.         // Perform request
  322.         $response null;
  323.         if ($this->isInBatch()) {
  324.             $this->getCurrentBatch()->enlistOperation($tableName''Microsoft_Http_Client::POST$headerstrue$requestBody);
  325.             return null;
  326.         else {
  327.             $response $this->_performRequest($tableName''Microsoft_Http_Client::POST$headerstrue$requestBody);
  328.         }
  329.         if ($response->isSuccessful()) {
  330.             // Parse result
  331.             $result $this->_parseResponse($response);
  332.             
  333.             $timestamp $result->xpath('//m:properties/d:Timestamp');
  334.             $timestamp $this->_convertToDateTime(string)$timestamp[0);
  335.  
  336.             $etag      $result->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  337.             $etag      = (string)$etag['etag'];
  338.             
  339.             // Update properties
  340.             $entity->setTimestamp($timestamp);
  341.             $entity->setEtag($etag);
  342.  
  343.             return $entity;
  344.         else {
  345.             throw new Microsoft_WindowsAzure_Exception($this->_getErrorMessage($response'Resource could not be accessed.'));
  346.         }
  347.     }
  348.     
  349.     /**
  350.      * Delete entity from table
  351.      * 
  352.      * @param string                              $tableName   Table name
  353.      * @param Microsoft_WindowsAzure_Storage_TableEntity $entity      Entity to delete
  354.      * @param boolean                             $verifyEtag  Verify etag of the entity (used for concurrency)
  355.      * @throws Microsoft_WindowsAzure_Exception
  356.      */
  357.     public function deleteEntity($tableName ''Microsoft_WindowsAzure_Storage_TableEntity $entity null$verifyEtag false)
  358.     {
  359.         if ($tableName === ''{
  360.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  361.         }
  362.         if (is_null($entity)) {
  363.             throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
  364.         }
  365.                              
  366.         // Add header information
  367.         $headers array();
  368.         if (!$this->isInBatch()) {
  369.             // http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/9e255447-4dc7-458a-99d3-bdc04bdc5474/
  370.             $headers['Content-Type']   'application/atom+xml';
  371.         }
  372.         $headers['Content-Length'0;
  373.         if (!$verifyEtag{
  374.             $headers['If-Match']       '*';
  375.         else {
  376.             $headers['If-Match']       $entity->getEtag();
  377.         }
  378.  
  379.         // Perform request
  380.         $response null;
  381.         if ($this->isInBatch()) {
  382.             $this->getCurrentBatch()->enlistOperation($tableName '(PartitionKey=\'' $entity->getPartitionKey('\', RowKey=\'' $entity->getRowKey('\')'''Microsoft_Http_Client::DELETE$headerstruenull);
  383.             return null;
  384.         else {
  385.             $response $this->_performRequest($tableName '(PartitionKey=\'' $entity->getPartitionKey('\', RowKey=\'' $entity->getRowKey('\')'''Microsoft_Http_Client::DELETE$headerstruenull);
  386.         }
  387.         if (!$response->isSuccessful()) {
  388.             throw new Microsoft_WindowsAzure_Exception($this->_getErrorMessage($response'Resource could not be accessed.'));
  389.         }
  390.     }
  391.     
  392.     /**
  393.      * Retrieve entity from table, by id
  394.      * 
  395.      * @param string $tableName    Table name
  396.      * @param string $partitionKey Partition key
  397.      * @param string $rowKey       Row key
  398.      * @param string $entityClass  Entity class name*
  399.      * @return Microsoft_WindowsAzure_Storage_TableEntity 
  400.      * @throws Microsoft_WindowsAzure_Exception
  401.      */
  402.     public function retrieveEntityById($tableName$partitionKey$rowKey$entityClass 'Microsoft_WindowsAzure_Storage_DynamicTableEntity')
  403.     {
  404.         if (is_null($tableName|| $tableName === ''{
  405.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  406.         }
  407.         if (is_null($partitionKey|| $partitionKey === ''{
  408.             throw new Microsoft_WindowsAzure_Exception('Partition key is not specified.');
  409.         }
  410.         if (is_null($rowKey|| $rowKey === ''{
  411.             throw new Microsoft_WindowsAzure_Exception('Row key is not specified.');
  412.         }
  413.         if (is_null($entityClass|| $entityClass === ''{
  414.             throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
  415.         }
  416.  
  417.             
  418.         // Check for combined size of partition key and row key
  419.         // http://msdn.microsoft.com/en-us/library/dd179421.aspx
  420.         if (strlen($partitionKey $rowKey>= 256{
  421.             // Start a batch if possible
  422.             if ($this->isInBatch()) {
  423.                 throw new Microsoft_WindowsAzure_Exception('Entity cannot be retrieved. A transaction is required to retrieve the entity, but another transaction is already active.');
  424.             }
  425.                 
  426.             $this->startBatch();
  427.         }
  428.         
  429.         // Fetch entities from Azure
  430.         $result $this->retrieveEntities(
  431.             $this->select()
  432.                  ->from($tableName)
  433.                  ->wherePartitionKey($partitionKey)
  434.                  ->whereRowKey($rowKey),
  435.             '',
  436.             $entityClass
  437.         );
  438.         
  439.         // Return
  440.         if (count($result== 1{
  441.             return $result[0];
  442.         }
  443.         
  444.         return null;
  445.     }
  446.     
  447.     /**
  448.      * Create a new Microsoft_WindowsAzure_Storage_TableEntityQuery
  449.      * 
  450.      * @return Microsoft_WindowsAzure_Storage_TableEntityQuery 
  451.      */
  452.     public function select()
  453.     {
  454.         return new Microsoft_WindowsAzure_Storage_TableEntityQuery();
  455.     }
  456.     
  457.     /**
  458.      * Retrieve entities from table
  459.      * 
  460.      * @param string $tableName|Microsoft_WindowsAzure_Storage_TableEntityQuery    Table name -or- Microsoft_WindowsAzure_Storage_TableEntityQuery instance
  461.      * @param string $filter                                                Filter condition (not applied when $tableName is a Microsoft_WindowsAzure_Storage_TableEntityQuery instance)
  462.      * @param string $entityClass                                           Entity class name
  463.      * @param string $nextPartitionKey                                      Next partition key, used for listing entities when total amount of entities is > 1000.
  464.      * @param string $nextRowKey                                            Next row key, used for listing entities when total amount of entities is > 1000.
  465.      * @return array Array of Microsoft_WindowsAzure_Storage_TableEntity
  466.      * @throws Microsoft_WindowsAzure_Exception
  467.      */
  468.     public function retrieveEntities($tableName ''$filter ''$entityClass 'Microsoft_WindowsAzure_Storage_DynamicTableEntity'$nextPartitionKey null$nextRowKey null)
  469.     {
  470.         if ($tableName === ''{
  471.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  472.         }
  473.         if ($entityClass === ''{
  474.             throw new Microsoft_WindowsAzure_Exception('Entity class is not specified.');
  475.         }
  476.  
  477.         // Convenience...
  478.         if (class_exists($filter)) {
  479.             $entityClass $filter;
  480.             $filter '';
  481.         }
  482.             
  483.         // Query string
  484.         $queryString '';
  485.  
  486.         // Determine query
  487.         if (is_string($tableName)) {
  488.             // Option 1: $tableName is a string
  489.             
  490.             // Append parentheses
  491.             if (strpos($tableName'()'=== false{
  492.                 $tableName .= '()';
  493.             }
  494.             
  495.             // Build query
  496.             $query array();
  497.             
  498.             // Filter?
  499.             if ($filter !== ''{
  500.                 $query['$filter=' Microsoft_WindowsAzure_Storage_TableEntityQuery::encodeQuery($filter);
  501.             }
  502.                 
  503.             // Build queryString
  504.             if (count($query0)  {
  505.                 $queryString '?' implode('&'$query);
  506.             }
  507.         else if (get_class($tableName== 'Microsoft_WindowsAzure_Storage_TableEntityQuery'{
  508.             // Option 2: $tableName is a Microsoft_WindowsAzure_Storage_TableEntityQuery instance
  509.  
  510.             // Build queryString
  511.             $queryString $tableName->assembleQueryString(true);
  512.  
  513.             // Change $tableName
  514.             $tableName $tableName->assembleFrom(true);
  515.         else {
  516.             throw new Microsoft_WindowsAzure_Exception('Invalid argument: $tableName');
  517.         }
  518.         
  519.         // Add continuation querystring parameters?
  520.         if (!is_null($nextPartitionKey&& !is_null($nextRowKey)) {
  521.             if ($queryString !== ''{
  522.                 $queryString .= '&';
  523.             else {
  524.                 $queryString .= '?';
  525.             }
  526.                 
  527.             $queryString .= 'NextPartitionKey=' rawurlencode($nextPartitionKey'&NextRowKey=' rawurlencode($nextRowKey);
  528.         }
  529.  
  530.         // Perform request
  531.         $response null;
  532.         if ($this->isInBatch(&& $this->getCurrentBatch()->getOperationCount(== 0{
  533.             $this->getCurrentBatch()->enlistOperation($tableName$queryStringMicrosoft_Http_Client::GETarray()truenull);
  534.             $response $this->getCurrentBatch()->commit();
  535.             
  536.             // Get inner response (multipart)
  537.             $innerResponse $response->getBody();
  538.             $innerResponse substr($innerResponsestrpos($innerResponse'HTTP/1.1 200 OK'));
  539.             $innerResponse substr($innerResponse0strpos($innerResponse'--batchresponse'));
  540.             $response Microsoft_Http_Response::fromString($innerResponse);
  541.         else {
  542.             $response $this->_performRequest($tableName$queryStringMicrosoft_Http_Client::GETarray()truenull);
  543.         }
  544.  
  545.         if ($response->isSuccessful()) {
  546.             // Parse result
  547.             $result $this->_parseResponse($response);
  548.             if (!$result{
  549.                 return array();
  550.             }
  551.  
  552.             $entries null;
  553.             if ($result->entry{
  554.                 if (count($result->entry1{
  555.                     $entries $result->entry;
  556.                 else {
  557.                     $entries array($result->entry);
  558.                 }
  559.             else {
  560.                 // This one is tricky... If we have properties defined, we have an entity.
  561.                 $properties $result->xpath('//m:properties');
  562.                 if ($properties{
  563.                     $entries array($result);
  564.                 else {
  565.                     return array();
  566.                 }
  567.             }
  568.  
  569.             // Create return value
  570.             $returnValue array();            
  571.             foreach ($entries as $entry{
  572.                 // Parse properties
  573.                 $properties $entry->xpath('.//m:properties');
  574.                 $properties $properties[0]->children('http://schemas.microsoft.com/ado/2007/08/dataservices');
  575.                 
  576.                 // Create entity
  577.                 $entity new $entityClass('''');
  578.                 $entity->setAzureValues((array)$properties$this->_throwExceptionOnMissingData);
  579.                 
  580.                 // If we have a Microsoft_WindowsAzure_Storage_DynamicTableEntity, make sure all property types are set
  581.                 if ($entity instanceof Microsoft_WindowsAzure_Storage_DynamicTableEntity{
  582.                     foreach ($properties as $key => $value{  
  583.                         $attributes $value->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  584.                         $type = (string)$attributes['type'];
  585.                         if ($type !== ''{
  586.                             $entity->setAzureProperty($key(string)$value$type);
  587.                         }
  588.                     }
  589.                 }
  590.     
  591.                 // Update etag
  592.                 $etag      $entry->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  593.                 $etag      = (string)$etag['etag'];
  594.                 $entity->setEtag($etag);
  595.                 
  596.                 // Add to result
  597.                 $returnValue[$entity;
  598.             }
  599.  
  600.             // More entities?
  601.             if (!is_null($response->getHeader('x-ms-continuation-NextPartitionKey')) && !is_null($response->getHeader('x-ms-continuation-NextRowKey'))) {
  602.                 if (strpos($queryString'$top'=== false{
  603.                     $returnValue array_merge($returnValue$this->retrieveEntities($tableName$filter$entityClass$response->getHeader('x-ms-continuation-NextPartitionKey')$response->getHeader('x-ms-continuation-NextRowKey')));
  604.                 }
  605.             }
  606.             
  607.             // Return
  608.             return $returnValue;
  609.         else {
  610.             throw new Microsoft_WindowsAzure_Exception($this->_getErrorMessage($response'Resource could not be accessed.'));
  611.         }
  612.     }
  613.     
  614.     /**
  615.      * Update entity by replacing it
  616.      * 
  617.      * @param string                              $tableName   Table name
  618.      * @param Microsoft_WindowsAzure_Storage_TableEntity $entity      Entity to update
  619.      * @param boolean                             $verifyEtag  Verify etag of the entity (used for concurrency)
  620.      * @throws Microsoft_WindowsAzure_Exception
  621.      */
  622.     public function updateEntity($tableName ''Microsoft_WindowsAzure_Storage_TableEntity $entity null$verifyEtag false)
  623.     {
  624.         return $this->_changeEntity(Microsoft_Http_Client::PUT$tableName$entity$verifyEtag);
  625.     }
  626.     
  627.     /**
  628.      * Update entity by adding or updating properties
  629.      * 
  630.      * @param string                              $tableName   Table name
  631.      * @param Microsoft_WindowsAzure_Storage_TableEntity $entity      Entity to update
  632.      * @param boolean                             $verifyEtag  Verify etag of the entity (used for concurrency)
  633.      * @param array                               $properties  Properties to merge. All properties will be used when omitted.
  634.      * @throws Microsoft_WindowsAzure_Exception
  635.      */
  636.     public function mergeEntity($tableName ''Microsoft_WindowsAzure_Storage_TableEntity $entity null$verifyEtag false$properties array())
  637.     {
  638.         $mergeEntity null;
  639.         if (is_array($properties&& count($properties0{
  640.             // Build a new object
  641.             $mergeEntity new Microsoft_WindowsAzure_Storage_DynamicTableEntity($entity->getPartitionKey()$entity->getRowKey());
  642.             
  643.             // Keep only values mentioned in $properties
  644.             $azureValues $entity->getAzureValues();
  645.             foreach ($azureValues as $key => $value{
  646.                 if (in_array($value->Name$properties)) {
  647.                     $mergeEntity->setAzureProperty($value->Name$value->Value$value->Type);
  648.                 }
  649.             }
  650.         else {
  651.             $mergeEntity $entity;
  652.         }
  653.         
  654.         // Ensure entity timestamp matches updated timestamp 
  655.         $entity->setTimestamp(new DateTime());
  656.         
  657.         return $this->_changeEntity(Microsoft_Http_Client::MERGE$tableName$mergeEntity$verifyEtag);
  658.     }
  659.     
  660.     /**
  661.      * Get error message from Microsoft_Http_Response
  662.      * 
  663.      * @param Microsoft_Http_Response $response Repsonse
  664.      * @param string $alternativeError Alternative error message
  665.      * @return string 
  666.      */
  667.     protected function _getErrorMessage(Microsoft_Http_Response $response$alternativeError 'Unknown error.')
  668.     {
  669.         $response $this->_parseResponse($response);
  670.         if ($response && $response->message{
  671.             return (string)$response->message;
  672.         else {
  673.             return $alternativeError;
  674.         }
  675.     }
  676.     
  677.     /**
  678.      * Update entity / merge entity
  679.      * 
  680.      * @param string                              $httpVerb    HTTP verb to use (PUT = update, MERGE = merge)
  681.      * @param string                              $tableName   Table name
  682.      * @param Microsoft_WindowsAzure_Storage_TableEntity $entity      Entity to update
  683.      * @param boolean                             $verifyEtag  Verify etag of the entity (used for concurrency)
  684.      * @throws Microsoft_WindowsAzure_Exception
  685.      */
  686.     protected function _changeEntity($httpVerb Microsoft_Http_Client::PUT$tableName ''Microsoft_WindowsAzure_Storage_TableEntity $entity null$verifyEtag false)
  687.     {
  688.         if ($tableName === ''{
  689.             throw new Microsoft_WindowsAzure_Exception('Table name is not specified.');
  690.         }
  691.         if (is_null($entity)) {
  692.             throw new Microsoft_WindowsAzure_Exception('Entity is not specified.');
  693.         }
  694.                              
  695.         // Add header information
  696.         $headers array();
  697.         $headers['Content-Type']   'application/atom+xml';
  698.         $headers['Content-Length'0;
  699.         if (!$verifyEtag{
  700.             $headers['If-Match']       '*';
  701.         else {
  702.             $headers['If-Match']       $entity->getEtag();
  703.         }
  704.  
  705.         // Generate request body
  706.         $requestBody '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  707.                         <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">
  708.                           <title />
  709.                           <updated>{tpl:Updated}</updated>
  710.                           <author>
  711.                             <name />
  712.                           </author>
  713.                           <id />
  714.                           <content type="application/xml">
  715.                             <m:properties>
  716.                               {tpl:Properties}
  717.                             </m:properties>
  718.                           </content>
  719.                         </entry>';
  720.         
  721.         // Attempt to get timestamp from entity
  722.         $timestamp $entity->getTimestamp();
  723.         
  724.         $requestBody $this->_fillTemplate($requestBodyarray(
  725.             'Updated'    => $this->_convertToEdmDateTime($timestamp),
  726.             'Properties' => $this->_generateAzureRepresentation($entity)
  727.         ));
  728.  
  729.         // Add header information
  730.         $headers array();
  731.         $headers['Content-Type''application/atom+xml';
  732.         if (!$verifyEtag{
  733.             $headers['If-Match']       '*';
  734.         else {
  735.             $headers['If-Match']       $entity->getEtag();
  736.         }
  737.         
  738.         // Perform request
  739.         $response null;
  740.         if ($this->isInBatch()) {
  741.             $this->getCurrentBatch()->enlistOperation($tableName '(PartitionKey=\'' $entity->getPartitionKey('\', RowKey=\'' $entity->getRowKey('\')'''$httpVerb$headerstrue$requestBody);
  742.             return null;
  743.         else {
  744.             $response $this->_performRequest($tableName '(PartitionKey=\'' $entity->getPartitionKey('\', RowKey=\'' $entity->getRowKey('\')'''$httpVerb$headerstrue$requestBody);
  745.         }
  746.         if ($response->isSuccessful()) {
  747.             // Update properties
  748.             $entity->setEtag($response->getHeader('Etag'));
  749.             $entity->setTimestamp$this->_convertToDateTime($response->getHeader('Last-modified')) );
  750.  
  751.             return $entity;
  752.         else {
  753.             throw new Microsoft_WindowsAzure_Exception($this->_getErrorMessage($response'Resource could not be accessed.'));
  754.         }
  755.     }
  756.     
  757.     /**
  758.      * Generate RFC 1123 compliant date string
  759.      * 
  760.      * @return string 
  761.      */
  762.     protected function _rfcDate()
  763.     {
  764.         return gmdate('D, d M Y H:i:s'time()) ' GMT'// RFC 1123
  765.     }
  766.     
  767.     /**
  768.      * Fill text template with variables from key/value array
  769.      * 
  770.      * @param string $templateText Template text
  771.      * @param array $variables Array containing key/value pairs
  772.      * @return string 
  773.      */
  774.     protected function _fillTemplate($templateText$variables array())
  775.     {
  776.         foreach ($variables as $key => $value{
  777.             $templateText str_replace('{tpl:' $key '}'$value$templateText);
  778.         }
  779.         return $templateText;
  780.     }
  781.     
  782.     /**
  783.      * Generate Azure representation from entity (creates atompub markup from properties)
  784.      * 
  785.      * @param Microsoft_WindowsAzure_Storage_TableEntity $entity 
  786.      * @return string 
  787.      */
  788.     protected function _generateAzureRepresentation(Microsoft_WindowsAzure_Storage_TableEntity $entity null)
  789.     {
  790.         // Generate Azure representation from entity
  791.         $azureRepresentation array();
  792.         $azureValues         $entity->getAzureValues();
  793.         foreach ($azureValues as $azureValue{
  794.             $value array();
  795.             $value['<d:' $azureValue->Name;
  796.             if ($azureValue->Type != ''{
  797.                 $value[' m:type="' $azureValue->Type '"';
  798.             }
  799.             if (is_null($azureValue->Value)) {
  800.                 $value[' m:null="true"'
  801.             }
  802.             $value['>';
  803.             
  804.             if (!is_null($azureValue->Value)) {
  805.                 if (strtolower($azureValue->Type== 'edm.boolean'{
  806.                     $value[($azureValue->Value == true '1' '0');
  807.                 else if (strtolower($azureValue->Type== 'edm.datetime'{
  808.                     $value[$this->_convertToEdmDateTime($azureValue->Value);
  809.                 else {
  810.                     $value[htmlspecialchars($azureValue->Value);
  811.                 }
  812.             }
  813.             
  814.             $value['</d:' $azureValue->Name '>';
  815.             $azureRepresentation[implode(''$value);
  816.         }
  817.  
  818.         return implode(''$azureRepresentation);
  819.     }
  820.     
  821.         /**
  822.      * Perform request using Microsoft_Http_Client channel
  823.      *
  824.      * @param string $path Path
  825.      * @param string $queryString Query string
  826.      * @param string $httpVerb HTTP verb the request will use
  827.      * @param array $headers x-ms headers to add
  828.      * @param boolean $forTableStorage Is the request for table storage?
  829.      * @param mixed $rawData Optional RAW HTTP data to be sent over the wire
  830.      * @param string $resourceType Resource type
  831.      * @param string $requiredPermission Required permission
  832.      * @return Microsoft_Http_Response 
  833.      */
  834.     protected function _performRequest(
  835.         $path '/',
  836.         $queryString '',
  837.         $httpVerb Microsoft_Http_Client::GET,
  838.         $headers array(),
  839.         $forTableStorage false,
  840.         $rawData null,
  841.         $resourceType Microsoft_WindowsAzure_Storage::RESOURCE_UNKNOWN,
  842.         $requiredPermission Microsoft_WindowsAzure_Credentials_CredentialsAbstract::PERMISSION_READ
  843.     {
  844.         // Add headers
  845.         $headers['DataServiceVersion''1.0;NetFx';
  846.         $headers['MaxDataServiceVersion''1.0;NetFx';
  847.  
  848.         // Perform request
  849.         return parent::_performRequest(
  850.             $path,
  851.             $queryString,
  852.             $httpVerb,
  853.             $headers,
  854.             $forTableStorage,
  855.             $rawData,
  856.             $resourceType,
  857.             $requiredPermission
  858.         );
  859.     }  
  860.       
  861.     /**
  862.      * Converts a string to a DateTime object. Returns false on failure.
  863.      * 
  864.      * @param string $value The string value to parse
  865.      * @return DateTime|boolean
  866.      */
  867.     protected function _convertToDateTime($value ''
  868.     {
  869.         if ($value instanceof DateTime{
  870.             return $value;
  871.         }
  872.         
  873.         try {
  874.             if (substr($value-1== 'Z'{
  875.                 $value substr($value0strlen($value1);
  876.             }
  877.             return new DateTime($valuenew DateTimeZone('UTC'));
  878.         }
  879.         catch (Exception $ex{
  880.             return false;
  881.         }
  882.     }
  883.     
  884.     /**
  885.      * Converts a DateTime object into an Edm.DaeTime value in UTC timezone,
  886.      * represented as a string.
  887.      * 
  888.      * @param DateTime $value 
  889.      * @return string 
  890.      */
  891.     protected function _convertToEdmDateTime(DateTime $value
  892.     {
  893.         $cloned clone $value;
  894.         $cloned->setTimezone(new DateTimeZone('UTC'));
  895.         return str_replace('+0000''Z'$cloned->format(DateTime::ISO8601));
  896.     }
  897. }

Documentation generated on Wed, 18 May 2011 12:06:58 +0200 by phpDocumentor 1.4.3