File indexing completed on 2024-12-22 05:37:02
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Service_Amazon 0017 * @subpackage SimpleDb 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 */ 0021 0022 /** 0023 * @see Zend_Service_Amazon_Abstract 0024 */ 0025 // require_once 'Zend/Service/Amazon/Abstract.php'; 0026 0027 /** 0028 * @see Zend_Service_Amazon_SimpleDb_Response 0029 */ 0030 // require_once 'Zend/Service/Amazon/SimpleDb/Response.php'; 0031 0032 /** 0033 * @see Zend_Service_Amazon_SimpleDb_Page 0034 */ 0035 // require_once 'Zend/Service/Amazon/SimpleDb/Page.php'; 0036 0037 /** 0038 * @see Zend_Service_Amazon_SimpleDb_Attribute 0039 */ 0040 // require_once 'Zend/Service/Amazon/SimpleDb/Attribute.php'; 0041 0042 /** 0043 * @see Zend_Service_Amazon_SimpleDb_Exception 0044 */ 0045 // require_once 'Zend/Service/Amazon/SimpleDb/Exception.php'; 0046 0047 /** 0048 * @see Zend_Crypt_Hmac 0049 */ 0050 // require_once 'Zend/Crypt/Hmac.php'; 0051 0052 /** 0053 * @category Zend 0054 * @package Zend_Service_Amazon 0055 * @subpackage SimpleDb 0056 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0057 * @license http://framework.zend.com/license/new-bsd New BSD License 0058 */ 0059 class Zend_Service_Amazon_SimpleDb extends Zend_Service_Amazon_Abstract 0060 { 0061 /* Notes */ 0062 // TODO SSL is required 0063 0064 /** 0065 * The HTTP query server 0066 */ 0067 protected $_sdbEndpoint = 'sdb.amazonaws.com/'; 0068 0069 /** 0070 * Period after which HTTP request will timeout in seconds 0071 */ 0072 protected $_httpTimeout = 10; 0073 0074 /** 0075 * The API version to use 0076 */ 0077 protected $_sdbApiVersion = '2009-04-15'; 0078 0079 /** 0080 * Signature Version 0081 */ 0082 protected $_signatureVersion = '2'; 0083 0084 /** 0085 * Signature Encoding Method 0086 */ 0087 protected $_signatureMethod = 'HmacSHA256'; 0088 0089 /** 0090 * Create Amazon SimpleDB client. 0091 * 0092 * @param string $accessKey Override the default Access Key 0093 * @param string $secretKey Override the default Secret Key 0094 */ 0095 public function __construct($accessKey, $secretKey) 0096 { 0097 parent::__construct($accessKey, $secretKey); 0098 $this->setEndpoint("https://" . $this->_sdbEndpoint); 0099 } 0100 0101 /** 0102 * Set SimpleDB endpoint to use 0103 * 0104 * @param string|Zend_Uri_Http $endpoint 0105 * @throws Zend_Service_Amazon_SimpleDb_Exception 0106 * @throws Zend_Uri_Exception 0107 * @return Zend_Service_Amazon_SimpleDb 0108 */ 0109 public function setEndpoint($endpoint) 0110 { 0111 if(!($endpoint instanceof Zend_Uri_Http)) { 0112 $endpoint = Zend_Uri::factory($endpoint); 0113 } 0114 if(!$endpoint->valid()) { 0115 // require_once 'Zend/Service/Amazon/SimpleDb/Exception.php'; 0116 throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid endpoint supplied"); 0117 } 0118 $this->_endpoint = $endpoint; 0119 return $this; 0120 } 0121 0122 /** 0123 * Get SimpleDB endpoint 0124 * 0125 * @return Zend_Uri_Http 0126 */ 0127 public function getEndpoint() 0128 { 0129 return $this->_endpoint; 0130 } 0131 0132 /** 0133 * Get attributes API method 0134 * 0135 * @param string $domainName Domain name within database 0136 * @param string $itemName 0137 * @param string|null $attributeName 0138 * @throws Zend_Service_Amazon_SimpleDb_Exception 0139 * @return array 0140 */ 0141 public function getAttributes( 0142 $domainName, $itemName, $attributeName = null 0143 ) { 0144 $params = array(); 0145 $params['Action'] = 'GetAttributes'; 0146 $params['DomainName'] = $domainName; 0147 $params['ItemName'] = $itemName; 0148 0149 if (isset($attributeName)) { 0150 $params['AttributeName'] = $attributeName; 0151 } 0152 0153 $response = $this->_sendRequest($params); 0154 $document = $response->getSimpleXMLDocument(); 0155 0156 $attributeNodes = $document->GetAttributesResult->Attribute; 0157 0158 // Return an array of arrays 0159 $attributes = array(); 0160 foreach($attributeNodes as $attributeNode) { 0161 $name = (string)$attributeNode->Name; 0162 $valueNodes = $attributeNode->Value; 0163 $data = null; 0164 if (is_array($valueNodes) && !empty($valueNodes)) { 0165 $data = array(); 0166 foreach($valueNodes as $valueNode) { 0167 $data[] = (string)$valueNode; 0168 } 0169 } elseif (isset($valueNodes)) { 0170 $data = (string)$valueNodes; 0171 } 0172 if (isset($attributes[$name])) { 0173 $attributes[$name]->addValue($data); 0174 } else { 0175 $attributes[$name] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $name, $data); 0176 } 0177 } 0178 return $attributes; 0179 } 0180 0181 /** 0182 * Push attributes 0183 * 0184 * @param string $domainName 0185 * @param string $itemName 0186 * @param array|Traversable $attributes 0187 * @param array $replace 0188 * @return void 0189 */ 0190 public function putAttributes( 0191 $domainName, $itemName, $attributes, $replace = array() 0192 ) { 0193 $params = array(); 0194 $params['Action'] = 'PutAttributes'; 0195 $params['DomainName'] = $domainName; 0196 $params['ItemName'] = $itemName; 0197 0198 $index = 0; 0199 foreach ($attributes as $attribute) { 0200 $attributeName = $attribute->getName(); 0201 foreach ($attribute->getValues() as $value) { 0202 $params['Attribute.' . $index . '.Name'] = $attributeName; 0203 $params['Attribute.' . $index . '.Value'] = $value; 0204 0205 // Check if it should be replaced 0206 if(array_key_exists($attributeName, $replace) && $replace[$attributeName]) { 0207 $params['Attribute.' . $index . '.Replace'] = 'true'; 0208 } 0209 $index++; 0210 } 0211 } 0212 0213 // Exception should get thrown if there's an error 0214 $response = $this->_sendRequest($params); 0215 } 0216 0217 /** 0218 * Add many attributes at once 0219 * 0220 * @param array $items 0221 * @param string $domainName 0222 * @param array $replace 0223 * @return void 0224 */ 0225 public function batchPutAttributes($items, $domainName, array $replace = array()) 0226 { 0227 0228 $params = array(); 0229 $params['Action'] = 'BatchPutAttributes'; 0230 $params['DomainName'] = $domainName; 0231 0232 $itemIndex = 0; 0233 foreach ($items as $name => $attributes) { 0234 $params['Item.' . $itemIndex . '.ItemName'] = $name; 0235 $attributeIndex = 0; 0236 foreach ($attributes as $attribute) { 0237 // attribute value cannot be array, so when several items are passed 0238 // they are treated as separate values with the same attribute name 0239 foreach($attribute->getValues() as $value) { 0240 $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Name'] = $attribute->getName(); 0241 $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Value'] = $value; 0242 if (isset($replace[$name]) 0243 && isset($replace[$name][$attribute->getName()]) 0244 && $replace[$name][$attribute->getName()] 0245 ) { 0246 $params['Item.' . $itemIndex . '.Attribute.' . $attributeIndex . '.Replace'] = 'true'; 0247 } 0248 $attributeIndex++; 0249 } 0250 } 0251 $itemIndex++; 0252 } 0253 0254 $response = $this->_sendRequest($params); 0255 } 0256 0257 /** 0258 * Delete attributes 0259 * 0260 * @param string $domainName 0261 * @param string $itemName 0262 * @param array $attributes 0263 * @return void 0264 */ 0265 public function deleteAttributes($domainName, $itemName, array $attributes = array()) 0266 { 0267 $params = array(); 0268 $params['Action'] = 'DeleteAttributes'; 0269 $params['DomainName'] = $domainName; 0270 $params['ItemName'] = $itemName; 0271 0272 $attributeIndex = 0; 0273 foreach ($attributes as $attribute) { 0274 foreach ($attribute->getValues() as $value) { 0275 $params['Attribute.' . $attributeIndex . '.Name'] = $attribute->getName(); 0276 $params['Attribute.' . $attributeIndex . '.Value'] = $value; 0277 $attributeIndex++; 0278 } 0279 } 0280 0281 $response = $this->_sendRequest($params); 0282 0283 return true; 0284 } 0285 0286 /** 0287 * List domains 0288 * 0289 * @param int $maxNumberOfDomains 0290 * @param int $nextToken 0291 * @return array 0 or more domain names 0292 */ 0293 public function listDomains($maxNumberOfDomains = 100, $nextToken = null) 0294 { 0295 $params = array(); 0296 $params['Action'] = 'ListDomains'; 0297 $params['MaxNumberOfDomains'] = $maxNumberOfDomains; 0298 0299 if (null !== $nextToken) { 0300 $params['NextToken'] = $nextToken; 0301 } 0302 $response = $this->_sendRequest($params); 0303 0304 $domainNodes = $response->getSimpleXMLDocument()->ListDomainsResult->DomainName; 0305 0306 $data = array(); 0307 foreach ($domainNodes as $domain) { 0308 $data[] = (string)$domain; 0309 } 0310 0311 $nextTokenNode = $response->getSimpleXMLDocument()->ListDomainsResult->NextToken; 0312 $nextToken = (string)$nextTokenNode; 0313 0314 return new Zend_Service_Amazon_SimpleDb_Page($data, $nextToken); 0315 } 0316 0317 /** 0318 * Retrieve domain metadata 0319 * 0320 * @param string $domainName Name of the domain for which metadata will be requested 0321 * @return array Key/value array of metadatum names and values. 0322 */ 0323 public function domainMetadata($domainName) 0324 { 0325 $params = array(); 0326 $params['Action'] = 'DomainMetadata'; 0327 $params['DomainName'] = $domainName; 0328 $response = $this->_sendRequest($params); 0329 0330 $document = $response->getSimpleXMLDocument(); 0331 0332 $metadataNodes = $document->DomainMetadataResult->children(); 0333 $metadata = array(); 0334 foreach ($metadataNodes as $metadataNode) { 0335 $name = $metadataNode->getName(); 0336 $metadata[$name] = (string)$metadataNode; 0337 } 0338 0339 return $metadata; 0340 } 0341 0342 /** 0343 * Create a new domain 0344 * 0345 * @param string $domainName Valid domain name of the domain to create 0346 * @return boolean True if successful, false if not 0347 */ 0348 public function createDomain($domainName) 0349 { 0350 $params = array(); 0351 $params['Action'] = 'CreateDomain'; 0352 $params['DomainName'] = $domainName; 0353 $response = $this->_sendRequest($params); 0354 return $response->getHttpResponse()->isSuccessful(); 0355 } 0356 0357 /** 0358 * Delete a domain 0359 * 0360 * @param string $domainName Valid domain name of the domain to delete 0361 * @return boolean True if successful, false if not 0362 */ 0363 public function deleteDomain($domainName) 0364 { 0365 $params = array(); 0366 $params['Action'] = 'DeleteDomain'; 0367 $params['DomainName'] = $domainName; 0368 $response = $this->_sendRequest($params); 0369 return $response->getHttpResponse()->isSuccessful(); 0370 } 0371 0372 /** 0373 * Select items from the database 0374 * 0375 * @param string $selectExpression 0376 * @param null|string $nextToken 0377 * @return Zend_Service_Amazon_SimpleDb_Page 0378 */ 0379 public function select($selectExpression, $nextToken = null) 0380 { 0381 $params = array(); 0382 $params['Action'] = 'Select'; 0383 $params['SelectExpression'] = $selectExpression; 0384 0385 if (null !== $nextToken) { 0386 $params['NextToken'] = $nextToken; 0387 } 0388 0389 $response = $this->_sendRequest($params); 0390 $xml = $response->getSimpleXMLDocument(); 0391 0392 $attributes = array(); 0393 foreach ($xml->SelectResult->Item as $item) { 0394 $itemName = (string)$item->Name; 0395 0396 foreach ($item->Attribute as $attribute) { 0397 $attributeName = (string)$attribute->Name; 0398 0399 $values = array(); 0400 foreach ($attribute->Value as $value) { 0401 $values[] = (string)$value; 0402 } 0403 $attributes[$itemName][$attributeName] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $attributeName, $values); 0404 } 0405 } 0406 0407 $nextToken = (string)$xml->NextToken; 0408 0409 return new Zend_Service_Amazon_SimpleDb_Page($attributes, $nextToken); 0410 } 0411 0412 /** 0413 * Quote SDB value 0414 * 0415 * Wraps it in '' 0416 * 0417 * @param string $value 0418 * @return string 0419 */ 0420 public function quote($value) 0421 { 0422 // wrap in single quotes and convert each ' inside to '' 0423 return "'" . str_replace("'", "''", $value) . "'"; 0424 } 0425 0426 /** 0427 * Quote SDB column or table name 0428 * 0429 * Wraps it in `` 0430 * 0431 * @param string $name 0432 * @throws Zend_Service_Amazon_SimpleDb_Exception 0433 * @return string 0434 */ 0435 public function quoteName($name) 0436 { 0437 if (preg_match('/^[a-z_$][a-z0-9_$-]*$/i', $name) == false) { 0438 throw new Zend_Service_Amazon_SimpleDb_Exception("Invalid name: can contain only alphanumeric characters, \$ and _"); 0439 } 0440 return "`$name`"; 0441 } 0442 0443 /** 0444 * Sends a HTTP request to the SimpleDB service using Zend_Http_Client 0445 * 0446 * @param array $params List of parameters to send with the request 0447 * @return Zend_Service_Amazon_SimpleDb_Response 0448 * @throws Zend_Service_Amazon_SimpleDb_Exception 0449 */ 0450 protected function _sendRequest(array $params = array()) 0451 { 0452 // UTF-8 encode all parameters and replace '+' characters 0453 foreach ($params as $name => $value) { 0454 unset($params[$name]); 0455 $params[utf8_encode($name)] = $value; 0456 } 0457 0458 $params = $this->_addRequiredParameters($params); 0459 0460 try { 0461 /* @var $request Zend_Http_Client */ 0462 $request = self::getHttpClient(); 0463 $request->resetParameters(); 0464 0465 $request->setConfig(array( 0466 'timeout' => $this->_httpTimeout 0467 )); 0468 0469 0470 $request->setUri($this->getEndpoint()); 0471 $request->setMethod(Zend_Http_Client::POST); 0472 foreach ($params as $key => $value) { 0473 $params_out[] = rawurlencode($key)."=".rawurlencode($value); 0474 } 0475 $request->setRawData(join('&', $params_out), Zend_Http_Client::ENC_URLENCODED); 0476 $httpResponse = $request->request(); 0477 } catch (Zend_Http_Client_Exception $zhce) { 0478 $message = 'Error in request to AWS service: ' . $zhce->getMessage(); 0479 throw new Zend_Service_Amazon_SimpleDb_Exception($message, $zhce->getCode()); 0480 } 0481 $response = new Zend_Service_Amazon_SimpleDb_Response($httpResponse); 0482 $this->_checkForErrors($response); 0483 return $response; 0484 } 0485 0486 /** 0487 * Adds required authentication and version parameters to an array of 0488 * parameters 0489 * 0490 * The required parameters are: 0491 * - AWSAccessKey 0492 * - SignatureVersion 0493 * - Timestamp 0494 * - Version and 0495 * - Signature 0496 * 0497 * If a required parameter is already set in the <tt>$parameters</tt> array, 0498 * it is overwritten. 0499 * 0500 * @param array $parameters the array to which to add the required 0501 * parameters. 0502 * 0503 * @return array 0504 */ 0505 protected function _addRequiredParameters(array $parameters) 0506 { 0507 $parameters['AWSAccessKeyId'] = $this->_getAccessKey(); 0508 $parameters['SignatureVersion'] = $this->_signatureVersion; 0509 $parameters['Timestamp'] = gmdate('c'); 0510 $parameters['Version'] = $this->_sdbApiVersion; 0511 $parameters['SignatureMethod'] = $this->_signatureMethod; 0512 $parameters['Signature'] = $this->_signParameters($parameters); 0513 0514 return $parameters; 0515 } 0516 0517 /** 0518 * Computes the RFC 2104-compliant HMAC signature for request parameters 0519 * 0520 * This implements the Amazon Web Services signature, as per the following 0521 * specification: 0522 * 0523 * 1. Sort all request parameters (including <tt>SignatureVersion</tt> and 0524 * excluding <tt>Signature</tt>, the value of which is being created), 0525 * ignoring case. 0526 * 0527 * 2. Iterate over the sorted list and append the parameter name (in its 0528 * original case) and then its value. Do not URL-encode the parameter 0529 * values before constructing this string. Do not use any separator 0530 * characters when appending strings. 0531 * 0532 * @param array $parameters the parameters for which to get the signature. 0533 * @return string the signed data. 0534 */ 0535 protected function _signParameters(array $parameters) 0536 { 0537 $data = "POST\n"; 0538 $data .= $this->getEndpoint()->getHost() . "\n"; 0539 $data .= "/\n"; 0540 0541 uksort($parameters, 'strcmp'); 0542 unset($parameters['Signature']); 0543 0544 $arrData = array(); 0545 foreach ($parameters as $key => $value) { 0546 $value = urlencode($value); 0547 $value = str_replace("%7E", "~", $value); 0548 $value = str_replace("+", "%20", $value); 0549 $arrData[] = urlencode($key) . '=' . $value; 0550 } 0551 0552 $data .= implode('&', $arrData); 0553 0554 // require_once 'Zend/Crypt/Hmac.php'; 0555 $hmac = Zend_Crypt_Hmac::compute($this->_getSecretKey(), 'SHA256', $data, Zend_Crypt_Hmac::BINARY); 0556 0557 return base64_encode($hmac); 0558 } 0559 0560 /** 0561 * Checks for errors responses from Amazon 0562 * 0563 * @param Zend_Service_Amazon_SimpleDb_Response $response the response object to 0564 * check. 0565 * @throws Zend_Service_Amazon_SimpleDb_Exception if one or more errors are 0566 * returned from Amazon. 0567 */ 0568 private function _checkForErrors(Zend_Service_Amazon_SimpleDb_Response $response) 0569 { 0570 $xpath = new DOMXPath($response->getDocument()); 0571 $list = $xpath->query('//Error'); 0572 if ($list->length > 0) { 0573 $node = $list->item(0); 0574 $code = $xpath->evaluate('string(Code/text())', $node); 0575 $message = $xpath->evaluate('string(Message/text())', $node); 0576 throw new Zend_Service_Amazon_SimpleDb_Exception($message, 0, $code); 0577 } 0578 } 0579 }