web/lib/Zend/Service/Amazon/SimpleDb.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Service_Amazon
       
    17  * @subpackage SimpleDb
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  */
       
    21 
       
    22 /**
       
    23  * @see Zend_Service_Amazon_Abstract
       
    24  */
       
    25 require_once 'Zend/Service/Amazon/Abstract.php';
       
    26 
       
    27 /**
       
    28  * @see Zend_Service_Amazon_SimpleDb_Response
       
    29  */
       
    30 require_once 'Zend/Service/Amazon/SimpleDb/Response.php';
       
    31 
       
    32 /**
       
    33  * @see Zend_Service_Amazon_SimpleDb_Page
       
    34  */
       
    35 require_once 'Zend/Service/Amazon/SimpleDb/Page.php';
       
    36 
       
    37 /**
       
    38  * @see Zend_Service_Amazon_SimpleDb_Attribute
       
    39  */
       
    40 require_once 'Zend/Service/Amazon/SimpleDb/Attribute.php';
       
    41 
       
    42 /**
       
    43  * @see Zend_Service_Amazon_SimpleDb_Exception
       
    44  */
       
    45 require_once 'Zend/Service/Amazon/SimpleDb/Exception.php';
       
    46 
       
    47 /**
       
    48  * @see Zend_Crypt_Hmac
       
    49  */
       
    50 require_once 'Zend/Crypt/Hmac.php';
       
    51 
       
    52 /**
       
    53  * @category   Zend
       
    54  * @package    Zend_Service_Amazon
       
    55  * @subpackage SimpleDb
       
    56  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    57  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    58  */
       
    59 class Zend_Service_Amazon_SimpleDb extends Zend_Service_Amazon_Abstract
       
    60 {
       
    61     /* Notes */
       
    62     // TODO SSL is required
       
    63 
       
    64     /**
       
    65      * The HTTP query server
       
    66      */
       
    67     protected $_sdbEndpoint = 'sdb.amazonaws.com/';
       
    68 
       
    69     /**
       
    70      * Period after which HTTP request will timeout in seconds
       
    71      */
       
    72     protected $_httpTimeout = 10;
       
    73 
       
    74     /**
       
    75      * The API version to use
       
    76      */
       
    77     protected $_sdbApiVersion = '2009-04-15';
       
    78 
       
    79     /**
       
    80      * Signature Version
       
    81      */
       
    82     protected $_signatureVersion = '2';
       
    83 
       
    84     /**
       
    85      * Signature Encoding Method
       
    86      */
       
    87     protected $_signatureMethod = 'HmacSHA256';
       
    88 
       
    89     /**
       
    90      * Create Amazon SimpleDB client.
       
    91      *
       
    92      * @param  string $access_key       Override the default Access Key
       
    93      * @param  string $secret_key       Override the default Secret Key
       
    94      * @param  string $region           Sets the AWS Region
       
    95      * @return void
       
    96      */
       
    97     public function __construct($accessKey, $secretKey)
       
    98     {
       
    99         parent::__construct($accessKey, $secretKey);
       
   100         $this->setEndpoint("https://" . $this->_sdbEndpoint);
       
   101     }
       
   102 
       
   103 	/**
       
   104      * Set SimpleDB endpoint to use
       
   105      *
       
   106      * @param string|Zend_Uri_Http $endpoint
       
   107      * @return Zend_Service_Amazon_SimpleDb
       
   108      */
       
   109     public function setEndpoint($endpoint)
       
   110     {
       
   111     	if(!($endpoint instanceof Zend_Uri_Http)) {
       
   112     		$endpoint = Zend_Uri::factory($endpoint);
       
   113     	}
       
   114     	if(!$endpoint->valid()) {
       
   115     		require_once 'Zend/Service/Amazon/SimpleDb/Exception.php';
       
   116     		throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid endpoint supplied");
       
   117     	}
       
   118     	$this->_endpoint = $endpoint;
       
   119     	return $this;
       
   120     }
       
   121 
       
   122     /**
       
   123      * Get SimpleDB endpoint
       
   124      *
       
   125      * @return Zend_Uri_Http
       
   126      */
       
   127     public function getEndpoint() 
       
   128     {
       
   129     	return $this->_endpoint;
       
   130     }
       
   131 
       
   132     /**
       
   133      * Get attributes API method
       
   134      *
       
   135      * @param string $domainName Domain name within database
       
   136      * @param string 
       
   137      */
       
   138     public function getAttributes(
       
   139         $domainName, $itemName, $attributeName = null
       
   140     ) {
       
   141         $params               = array();
       
   142 	    $params['Action']     = 'GetAttributes';
       
   143 	    $params['DomainName'] = $domainName;
       
   144 	    $params['ItemName']   = $itemName;
       
   145 
       
   146 	    if (isset($attributeName)) {
       
   147 	        $params['AttributeName'] = $attributeName;
       
   148 	    }
       
   149 
       
   150 	    $response = $this->_sendRequest($params);
       
   151         $document = $response->getSimpleXMLDocument();
       
   152 
       
   153         $attributeNodes = $document->GetAttributesResult->Attribute;
       
   154 
       
   155         // Return an array of arrays
       
   156         $attributes = array();
       
   157         foreach($attributeNodes as $attributeNode) {
       
   158             $name       = (string)$attributeNode->Name;
       
   159             $valueNodes = $attributeNode->Value;
       
   160             $data       = null;
       
   161             if (is_array($valueNodes) && !empty($valueNodes)) {
       
   162                 $data = array();
       
   163                 foreach($valueNodes as $valueNode) {
       
   164                     $data[] = (string)$valueNode;
       
   165                 }
       
   166             } elseif (isset($valueNodes)) {
       
   167                 $data = (string)$valueNodes;
       
   168             }
       
   169             if (isset($attributes[$name])) {
       
   170                 $attributes[$name]->addValue($data);    
       
   171             } else {
       
   172                 $attributes[$name] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $name, $data);
       
   173             }
       
   174         }
       
   175         return $attributes;
       
   176     }
       
   177 
       
   178     /**
       
   179      * Push attributes
       
   180      *
       
   181      * @param  string $domainName
       
   182      * @param  string $itemName
       
   183      * @param  array|Traverable $attributes
       
   184      * @param  array $replace
       
   185      * @return void
       
   186      */
       
   187     public function putAttributes(
       
   188         $domainName, $itemName, $attributes, $replace = array()
       
   189     ) {
       
   190         $params               = array();
       
   191 	    $params['Action']     = 'PutAttributes';
       
   192 	    $params['DomainName'] = $domainName;
       
   193 	    $params['ItemName']   = $itemName;
       
   194 
       
   195 	    $index = 0;
       
   196 	    foreach ($attributes as $attribute) {
       
   197 	        $attributeName = $attribute->getName();
       
   198             foreach ($attribute->getValues() as $value) {
       
   199 	            $params['Attribute.' . $index . '.Name']  = $attributeName;
       
   200                 $params['Attribute.' . $index . '.Value'] = $value;
       
   201 
       
   202 	            // Check if it should be replaced
       
   203                 if(array_key_exists($attributeName, $replace) && $replace[$attributeName]) {
       
   204                     $params['Attribute.' . $index . '.Replace'] = 'true';
       
   205                 }
       
   206                 $index++;
       
   207             }
       
   208 	    }
       
   209 
       
   210 	    // Exception should get thrown if there's an error
       
   211         $response = $this->_sendRequest($params);
       
   212     }
       
   213 
       
   214     /**
       
   215      * Add many attributes at once
       
   216      * 
       
   217      * @param  array $items 
       
   218      * @param  string $domainName 
       
   219      * @param  array $replace 
       
   220      * @return void
       
   221      */
       
   222     public function batchPutAttributes($items, $domainName, array $replace = array()) 
       
   223     {
       
   224 
       
   225         $params               = array();
       
   226         $params['Action']     = 'BatchPutAttributes';
       
   227         $params['DomainName'] = $domainName;
       
   228 
       
   229         $itemIndex = 0;
       
   230         foreach ($items as $name => $attributes) {
       
   231             $params['Item.' . $itemIndex . '.ItemName'] = $name;
       
   232             $attributeIndex = 0;
       
   233             foreach ($attributes as $attribute) {
       
   234                 // attribute value cannot be array, so when several items are passed
       
   235                 // they are treated as separate values with the same attribute name
       
   236                 foreach($attribute->getValues() as $value) {
       
   237                     $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Name'] = $attribute->getName();
       
   238                     $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Value'] = $value;
       
   239                     if (isset($replace[$name]) 
       
   240                         && isset($replace[$name][$attribute->getName()]) 
       
   241                         && $replace[$name][$attribute->getName()]
       
   242                     ) {
       
   243                         $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Replace'] = 'true';
       
   244                     }
       
   245                     $attributeIndex++;
       
   246                 }
       
   247             }
       
   248             $itemIndex++;
       
   249         }
       
   250 
       
   251         $response = $this->_sendRequest($params);
       
   252     }
       
   253 
       
   254     /**
       
   255      * Delete attributes
       
   256      * 
       
   257      * @param  string $domainName 
       
   258      * @param  string $itemName 
       
   259      * @param  array $attributes 
       
   260      * @return void
       
   261      */
       
   262     public function deleteAttributes($domainName, $itemName, array $attributes = array()) 
       
   263     {
       
   264         $params               = array();
       
   265 	    $params['Action']     = 'DeleteAttributes';
       
   266 	    $params['DomainName'] = $domainName;
       
   267 	    $params['ItemName']   = $itemName;
       
   268 
       
   269 	    $attributeIndex = 0;
       
   270 	    foreach ($attributes as $attribute) {
       
   271 	        foreach ($attribute->getValues() as $value) {
       
   272 	            $params['Attribute.' . $attributeIndex . '.Name'] = $attribute->getName();
       
   273 	            $params['Attribute.' . $attributeIndex . '.Value'] = $value;
       
   274                 $attributeIndex++;
       
   275 	        }
       
   276 	    }
       
   277 
       
   278         $response = $this->_sendRequest($params);
       
   279 
       
   280         return true;
       
   281     }
       
   282 
       
   283     /**
       
   284      * List domains
       
   285      *
       
   286      * @param $maxNumberOfDomains int
       
   287      * @param $nextToken          int
       
   288      * @return array              0 or more domain names
       
   289      */
       
   290     public function listDomains($maxNumberOfDomains = 100, $nextToken = null) 
       
   291     {
       
   292         $params                       = array();
       
   293 	    $params['Action']             = 'ListDomains';
       
   294 	    $params['MaxNumberOfDomains'] = $maxNumberOfDomains;
       
   295 
       
   296 	    if (null !== $nextToken) {
       
   297 	        $params['NextToken'] = $nextToken;
       
   298 	    }
       
   299         $response = $this->_sendRequest($params);
       
   300 
       
   301         $domainNodes = $response->getSimpleXMLDocument()->ListDomainsResult->DomainName;
       
   302 
       
   303         $data = array();
       
   304         foreach ($domainNodes as $domain) {
       
   305             $data[] = (string)$domain;
       
   306         }
       
   307 
       
   308         $nextTokenNode = $response->getSimpleXMLDocument()->ListDomainsResult->NextToken;
       
   309         $nextToken     = (string)$nextTokenNode;
       
   310         $nextToken     = (trim($nextToken) === '') ? null : $nextToken;
       
   311 
       
   312         return new Zend_Service_Amazon_SimpleDb_Page($data, $nextToken);
       
   313     }
       
   314 
       
   315     /**
       
   316      * Retrieve domain metadata
       
   317      *
       
   318      * @param $domainName string Name of the domain for which metadata will be requested
       
   319      * @return array Key/value array of metadatum names and values.
       
   320      */
       
   321     public function domainMetadata($domainName) 
       
   322     {
       
   323         $params               = array();
       
   324 	    $params['Action']     = 'DomainMetadata';
       
   325 	    $params['DomainName'] = $domainName;
       
   326         $response             = $this->_sendRequest($params);
       
   327 
       
   328         $document = $response->getSimpleXMLDocument();
       
   329 
       
   330         $metadataNodes = $document->DomainMetadataResult->children();
       
   331         $metadata      = array();
       
   332         foreach ($metadataNodes as $metadataNode) {
       
   333             $name            = $metadataNode->getName();
       
   334             $metadata[$name] = (string)$metadataNode;
       
   335         }
       
   336 
       
   337         return $metadata;
       
   338     }
       
   339 
       
   340     /**
       
   341      * Create a new domain
       
   342      *
       
   343      * @param $domainName	string	Valid domain name of the domain to create
       
   344      * @return 				boolean True if successful, false if not
       
   345      */
       
   346 	public function createDomain($domainName) 
       
   347 	{
       
   348         $params               = array();
       
   349 	    $params['Action']     = 'CreateDomain';
       
   350 	    $params['DomainName'] = $domainName;
       
   351         $response             = $this->_sendRequest($params);
       
   352         return $response->getHttpResponse()->isSuccessful();
       
   353     }
       
   354 
       
   355     /**
       
   356      * Delete a domain
       
   357      *
       
   358      * @param 	$domainName string  Valid domain name of the domain to delete
       
   359      * @return 				boolean True if successful, false if not
       
   360      */
       
   361 	public function deleteDomain($domainName) 
       
   362 	{
       
   363 	    $params               = array();
       
   364 	    $params['Action']     = 'DeleteDomain';
       
   365 	    $params['DomainName'] = $domainName;
       
   366         $response             = $this->_sendRequest($params);
       
   367         return $response->getHttpResponse()->isSuccessful();
       
   368     }
       
   369 
       
   370     /**
       
   371      * Select items from the database
       
   372      *
       
   373      * @param  string $selectExpression
       
   374      * @param  null|string $nextToken
       
   375      * @return Zend_Service_Amazon_SimpleDb_Page
       
   376      */
       
   377 	public function select($selectExpression, $nextToken = null) 
       
   378 	{
       
   379         $params                     = array();
       
   380 	    $params['Action']           = 'Select';
       
   381 	    $params['SelectExpression'] = $selectExpression;
       
   382 
       
   383 	    if (null !== $nextToken) {
       
   384 	        $params['NextToken'] = $nextToken;
       
   385 	    }
       
   386 
       
   387         $response = $this->_sendRequest($params);
       
   388         $xml      = $response->getSimpleXMLDocument();
       
   389 
       
   390         $attributes = array();
       
   391         foreach ($xml->SelectResult->Item as $item) {
       
   392             $itemName = (string)$item->Name;
       
   393 
       
   394             foreach ($item->Attribute as $attribute) {
       
   395                 $attributeName = (string)$attribute->Name;
       
   396 
       
   397                 $values = array();
       
   398                 foreach ($attribute->Value as $value) {
       
   399                     $values[] = (string)$value;
       
   400                 }
       
   401                 $attributes[$itemName][$attributeName] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $attributeName, $values);
       
   402             }
       
   403         }
       
   404 
       
   405         $nextToken = (string)$xml->NextToken;
       
   406 
       
   407         return new Zend_Service_Amazon_SimpleDb_Page($attributes, $nextToken);
       
   408     }
       
   409     
       
   410 	/**
       
   411 	 * Quote SDB value
       
   412 	 * 
       
   413 	 * Wraps it in ''
       
   414 	 * 
       
   415 	 * @param string $value
       
   416 	 * @return string
       
   417 	 */
       
   418     public function quote($value)
       
   419     {
       
   420     	// wrap in single quotes and convert each ' inside to ''
       
   421     	return "'" . str_replace("'", "''", $value) . "'";
       
   422     }
       
   423     
       
   424 	/**
       
   425 	 * Quote SDB column or table name
       
   426 	 * 
       
   427 	 * Wraps it in ``
       
   428 	 * @param string $name
       
   429 	 * @return string
       
   430 	 */
       
   431     public function quoteName($name)
       
   432     {
       
   433     	if (preg_match('/^[a-z_$][a-z0-9_$-]*$/i', $name) == false) {
       
   434     		throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid name: can contain only alphanumeric characters, \$ and _");
       
   435     	}
       
   436     	return "`$name`";
       
   437     }
       
   438     
       
   439    /**
       
   440      * Sends a HTTP request to the SimpleDB service using Zend_Http_Client
       
   441      *
       
   442      * @param array $params         List of parameters to send with the request
       
   443      * @return Zend_Service_Amazon_SimpleDb_Response
       
   444      * @throws Zend_Service_Amazon_SimpleDb_Exception
       
   445      */
       
   446     protected function _sendRequest(array $params = array())
       
   447     {
       
   448         // UTF-8 encode all parameters and replace '+' characters
       
   449         foreach ($params as $name => $value) {
       
   450             unset($params[$name]);
       
   451             $params[utf8_encode($name)] = $value;
       
   452         }
       
   453 
       
   454         $params = $this->_addRequiredParameters($params);
       
   455 
       
   456         try {
       
   457             /* @var $request Zend_Http_Client */
       
   458             $request = self::getHttpClient();
       
   459             $request->resetParameters();
       
   460 
       
   461             $request->setConfig(array(
       
   462                 'timeout' => $this->_httpTimeout
       
   463             ));
       
   464 
       
   465 
       
   466             $request->setUri($this->getEndpoint());
       
   467             $request->setMethod(Zend_Http_Client::POST);
       
   468             foreach ($params as $key => $value) {
       
   469                 $params_out[] = rawurlencode($key)."=".rawurlencode($value);
       
   470             }
       
   471             $request->setRawData(join('&', $params_out), Zend_Http_Client::ENC_URLENCODED);
       
   472             $httpResponse = $request->request();
       
   473         } catch (Zend_Http_Client_Exception $zhce) {
       
   474             $message = 'Error in request to AWS service: ' . $zhce->getMessage();
       
   475             throw new Zend_Service_Amazon_SimpleDb_Exception($message, $zhce->getCode());
       
   476         }
       
   477         $response = new Zend_Service_Amazon_SimpleDb_Response($httpResponse);
       
   478         $this->_checkForErrors($response);
       
   479         return $response;
       
   480     }
       
   481 
       
   482     /**
       
   483      * Adds required authentication and version parameters to an array of
       
   484      * parameters
       
   485      *
       
   486      * The required parameters are:
       
   487      * - AWSAccessKey
       
   488      * - SignatureVersion
       
   489      * - Timestamp
       
   490      * - Version and
       
   491      * - Signature
       
   492      *
       
   493      * If a required parameter is already set in the <tt>$parameters</tt> array,
       
   494      * it is overwritten.
       
   495      *
       
   496      * @param array $parameters the array to which to add the required
       
   497      *                          parameters.
       
   498      *
       
   499      * @return array
       
   500      */
       
   501     protected function _addRequiredParameters(array $parameters)
       
   502     {
       
   503         $parameters['AWSAccessKeyId']   = $this->_getAccessKey();
       
   504         $parameters['SignatureVersion'] = $this->_signatureVersion;
       
   505         $parameters['Timestamp']        = gmdate('c');
       
   506         $parameters['Version']          = $this->_sdbApiVersion;
       
   507         $parameters['SignatureMethod']  = $this->_signatureMethod;
       
   508         $parameters['Signature']        = $this->_signParameters($parameters);
       
   509 
       
   510         return $parameters;
       
   511     }
       
   512 
       
   513     /**
       
   514      * Computes the RFC 2104-compliant HMAC signature for request parameters
       
   515      *
       
   516      * This implements the Amazon Web Services signature, as per the following
       
   517      * specification:
       
   518      *
       
   519      * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and
       
   520      *    excluding <tt>Signature</tt>, the value of which is being created),
       
   521      *    ignoring case.
       
   522      *
       
   523      * 2. Iterate over the sorted list and append the parameter name (in its
       
   524      *    original case) and then its value. Do not URL-encode the parameter
       
   525      *    values before constructing this string. Do not use any separator
       
   526      *    characters when appending strings.
       
   527      *
       
   528      * @param array  $parameters the parameters for which to get the signature.
       
   529      * @param string $secretKey  the secret key to use to sign the parameters.
       
   530      *
       
   531      * @return string the signed data.
       
   532      */
       
   533     protected function _signParameters(array $paramaters)
       
   534     {
       
   535         $data  = "POST\n";
       
   536         $data .= $this->getEndpoint()->getHost() . "\n";
       
   537         $data .= "/\n";
       
   538 
       
   539         uksort($paramaters, 'strcmp');
       
   540         unset($paramaters['Signature']);
       
   541 
       
   542         $arrData = array();
       
   543         foreach ($paramaters as $key => $value) {
       
   544             $value = urlencode($value);
       
   545             $value = str_replace("%7E", "~", $value);
       
   546             $value = str_replace("+", "%20", $value);
       
   547             $arrData[] = urlencode($key) . '=' . $value;
       
   548         }
       
   549 
       
   550         $data .= implode('&', $arrData);
       
   551 
       
   552         require_once 'Zend/Crypt/Hmac.php';
       
   553         $hmac = Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'SHA256', $data, Zend_Crypt_Hmac::BINARY);
       
   554 
       
   555         return base64_encode($hmac);
       
   556     }
       
   557 
       
   558     /**
       
   559      * Checks for errors responses from Amazon
       
   560      *
       
   561      * @param Zend_Service_Amazon_SimpleDb_Response $response the response object to
       
   562      *                                                   check.
       
   563      *
       
   564      * @return void
       
   565      *
       
   566      * @throws Zend_Service_Amazon_SimpleDb_Exception if one or more errors are
       
   567      *         returned from Amazon.
       
   568      */
       
   569     private function _checkForErrors(Zend_Service_Amazon_SimpleDb_Response $response)
       
   570     {
       
   571         $xpath = new DOMXPath($response->getDocument());
       
   572         $list  = $xpath->query('//Error');
       
   573         if ($list->length > 0) {
       
   574             $node    = $list->item(0);
       
   575             $code    = $xpath->evaluate('string(Code/text())', $node);
       
   576             $message = $xpath->evaluate('string(Message/text())', $node);
       
   577             throw new Zend_Service_Amazon_SimpleDb_Exception($message, 0, $code);
       
   578         }
       
   579     }
       
   580 }