| changeset 886 | 1e110b03ae96 |
| parent 807 | 877f952ae2bd |
| child 1230 | 68c69c656a2c |
| 885:2251fb41dbc7 | 886:1e110b03ae96 |
|---|---|
11 * to license@zend.com so we can send you a copy immediately. |
11 * to license@zend.com so we can send you a copy immediately. |
12 * |
12 * |
13 * @category Zend |
13 * @category Zend |
14 * @package Zend_Cloud |
14 * @package Zend_Cloud |
15 * @subpackage DocumentService |
15 * @subpackage DocumentService |
16 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
16 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
17 * @license http://framework.zend.com/license/new-bsd New BSD License |
17 * @license http://framework.zend.com/license/new-bsd New BSD License |
18 */ |
18 */ |
19 |
19 |
20 require_once 'Zend/Cloud/DocumentService/Adapter/AbstractAdapter.php'; |
20 require_once 'Zend/Cloud/DocumentService/Adapter/AbstractAdapter.php'; |
21 require_once 'Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php'; |
21 require_once 'Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php'; |
27 * SimpleDB adapter for document service. |
27 * SimpleDB adapter for document service. |
28 * |
28 * |
29 * @category Zend |
29 * @category Zend |
30 * @package Zend_Cloud |
30 * @package Zend_Cloud |
31 * @subpackage DocumentService |
31 * @subpackage DocumentService |
32 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
32 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
33 * @license http://framework.zend.com/license/new-bsd New BSD License |
33 * @license http://framework.zend.com/license/new-bsd New BSD License |
34 */ |
34 */ |
35 class Zend_Cloud_DocumentService_Adapter_WindowsAzure |
35 class Zend_Cloud_DocumentService_Adapter_WindowsAzure |
36 extends Zend_Cloud_DocumentService_Adapter_AbstractAdapter |
36 extends Zend_Cloud_DocumentService_Adapter_AbstractAdapter |
37 { |
37 { |
38 /* |
38 /* |
39 * Options array keys for the Azure adapter. |
39 * Options array keys for the Azure adapter. |
40 */ |
40 */ |
48 |
48 |
49 const PARTITION_KEY = 'PartitionKey'; |
49 const PARTITION_KEY = 'PartitionKey'; |
50 const ROW_KEY = 'RowKey'; |
50 const ROW_KEY = 'RowKey'; |
51 const VERIFY_ETAG = "verify_etag"; |
51 const VERIFY_ETAG = "verify_etag"; |
52 const TIMESTAMP_KEY = "Timestamp"; |
52 const TIMESTAMP_KEY = "Timestamp"; |
53 |
53 |
54 const DEFAULT_HOST = Zend_Service_WindowsAzure_Storage::URL_CLOUD_TABLE; |
54 const DEFAULT_HOST = Zend_Service_WindowsAzure_Storage::URL_CLOUD_TABLE; |
55 const DEFAULT_QUERY_CLASS = 'Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query'; |
55 const DEFAULT_QUERY_CLASS = 'Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query'; |
56 |
56 |
57 /** |
57 /** |
58 * Azure service instance. |
58 * Azure service instance. |
59 * |
59 * |
60 * @var Zend_Service_WindowsAzure_Storage_Table |
60 * @var Zend_Service_WindowsAzure_Storage_Table |
61 */ |
61 */ |
62 protected $_storageClient; |
62 protected $_storageClient; |
63 |
63 |
64 /** |
64 /** |
65 * Class to utilize for new query objects |
65 * Class to utilize for new query objects |
66 * |
66 * |
67 * @var string |
67 * @var string |
68 */ |
68 */ |
69 protected $_queryClass = 'Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query'; |
69 protected $_queryClass = 'Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query'; |
70 |
70 |
71 /** |
71 /** |
74 */ |
74 */ |
75 protected $_defaultPartitionKey; |
75 protected $_defaultPartitionKey; |
76 |
76 |
77 /** |
77 /** |
78 * Constructor |
78 * Constructor |
79 * |
79 * |
80 * @param array $options |
80 * @param array $options |
81 * @return void |
81 * @return void |
82 */ |
82 */ |
83 public function __construct($options = array()) |
83 public function __construct($options = array()) |
84 { |
84 { |
85 if ($options instanceof Zend_Config) { |
85 if ($options instanceof Zend_Config) { |
86 $options = $options->toArray(); |
86 $options = $options->toArray(); |
87 } |
87 } |
88 |
88 |
140 } |
140 } |
141 } |
141 } |
142 |
142 |
143 /** |
143 /** |
144 * Set the default partition key |
144 * Set the default partition key |
145 * |
145 * |
146 * @param string $key |
146 * @param string $key |
147 * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure |
147 * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure |
148 */ |
148 */ |
149 public function setDefaultPartitionKey($key) |
149 public function setDefaultPartitionKey($key) |
150 { |
150 { |
151 $this->_validateKey($key); |
151 $this->_validateKey($key); |
153 return $this; |
153 return $this; |
154 } |
154 } |
155 |
155 |
156 /** |
156 /** |
157 * Retrieve default partition key |
157 * Retrieve default partition key |
158 * |
158 * |
159 * @return null|string |
159 * @return null|string |
160 */ |
160 */ |
161 public function getDefaultPartitionKey() |
161 public function getDefaultPartitionKey() |
162 { |
162 { |
163 return $this->_defaultPartitionKey; |
163 return $this->_defaultPartitionKey; |
168 * |
168 * |
169 * @param string $name |
169 * @param string $name |
170 * @param array $options |
170 * @param array $options |
171 * @return boolean |
171 * @return boolean |
172 */ |
172 */ |
173 public function createCollection($name, $options = null) |
173 public function createCollection($name, $options = null) |
174 { |
174 { |
175 if (!preg_match('/^[A-Za-z][A-Za-z0-9]{2,}$/', $name)) { |
175 if (!preg_match('/^[A-Za-z][A-Za-z0-9]{2,}$/', $name)) { |
176 throw new Zend_Cloud_DocumentService_Exception('Invalid collection name; Windows Azure collection names must consist of alphanumeric characters only, and be at least 3 characters long'); |
176 throw new Zend_Cloud_DocumentService_Exception('Invalid collection name; Windows Azure collection names must consist of alphanumeric characters only, and be at least 3 characters long'); |
177 } |
177 } |
178 try { |
178 try { |
190 * |
190 * |
191 * @param string $name |
191 * @param string $name |
192 * @param array $options |
192 * @param array $options |
193 * @return boolean |
193 * @return boolean |
194 */ |
194 */ |
195 public function deleteCollection($name, $options = null) |
195 public function deleteCollection($name, $options = null) |
196 { |
196 { |
197 try { |
197 try { |
198 $this->_storageClient->deleteTable($name); |
198 $this->_storageClient->deleteTable($name); |
199 } catch(Zend_Service_WindowsAzure_Exception $e) { |
199 } catch(Zend_Service_WindowsAzure_Exception $e) { |
200 if (strpos($e->getMessage(), "does not exist") === false) { |
200 if (strpos($e->getMessage(), "does not exist") === false) { |
208 * List collections. |
208 * List collections. |
209 * |
209 * |
210 * @param array $options |
210 * @param array $options |
211 * @return array |
211 * @return array |
212 */ |
212 */ |
213 public function listCollections($options = null) |
213 public function listCollections($options = null) |
214 { |
214 { |
215 try { |
215 try { |
216 $tables = $this->_storageClient->listTables(); |
216 $tables = $this->_storageClient->listTables(); |
217 $restables = array(); |
217 $restables = array(); |
218 foreach ($tables as $table) { |
218 foreach ($tables as $table) { |
226 return $tables; |
226 return $tables; |
227 } |
227 } |
228 |
228 |
229 /** |
229 /** |
230 * Create suitable document from array of fields |
230 * Create suitable document from array of fields |
231 * |
231 * |
232 * @param array $document |
232 * @param array $document |
233 * @param null|string $collectionName Collection to which this document belongs |
233 * @param null|string $collectionName Collection to which this document belongs |
234 * @return Zend_Cloud_DocumentService_Document |
234 * @return Zend_Cloud_DocumentService_Document |
235 */ |
235 */ |
236 protected function _getDocumentFromArray($document, $collectionName = null) |
236 protected function _getDocumentFromArray($document, $collectionName = null) |
255 } |
255 } |
256 |
256 |
257 $documentClass = $this->getDocumentClass(); |
257 $documentClass = $this->getDocumentClass(); |
258 return new $documentClass($document, $key); |
258 return new $documentClass($document, $key); |
259 } |
259 } |
260 |
260 |
261 /** |
261 /** |
262 * List all documents in a collection |
262 * List all documents in a collection |
263 * |
263 * |
264 * @param string $collectionName |
264 * @param string $collectionName |
265 * @param null|array $options |
265 * @param null|array $options |
266 * @return Zend_Cloud_DocumentService_DocumentSet |
266 * @return Zend_Cloud_DocumentService_DocumentSet |
267 */ |
267 */ |
268 public function listDocuments($collectionName, array $options = null) |
268 public function listDocuments($collectionName, array $options = null) |
269 { |
269 { |
270 $select = $this->select()->from($collectionName); |
270 $select = $this->select()->from($collectionName); |
280 */ |
280 */ |
281 public function insertDocument($collectionName, $document, $options = null) |
281 public function insertDocument($collectionName, $document, $options = null) |
282 { |
282 { |
283 if (is_array($document)) { |
283 if (is_array($document)) { |
284 $document = $this->_getDocumentFromArray($document, $collectionName); |
284 $document = $this->_getDocumentFromArray($document, $collectionName); |
285 } |
285 } |
286 |
286 |
287 if (!$document instanceof Zend_Cloud_DocumentService_Document) { |
287 if (!$document instanceof Zend_Cloud_DocumentService_Document) { |
288 throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); |
288 throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); |
289 } |
289 } |
290 |
290 |
291 $key = $this->_validateDocumentId($document->getId(), $collectionName); |
291 $key = $this->_validateDocumentId($document->getId(), $collectionName); |
292 $document->setId($key); |
292 $document->setId($key); |
293 |
293 |
294 $this->_validateCompositeKey($key); |
294 $this->_validateCompositeKey($key); |
295 $this->_validateFields($document); |
295 $this->_validateFields($document); |
296 try { |
296 try { |
297 |
297 |
298 $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]); |
298 $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]); |
299 $entity->setAzureValues($document->getFields(), true); |
299 $entity->setAzureValues($document->getFields(), true); |
300 $this->_storageClient->insertEntity($collectionName, $entity); |
300 $this->_storageClient->insertEntity($collectionName, $entity); |
301 } catch(Zend_Service_WindowsAzure_Exception $e) { |
301 } catch(Zend_Service_WindowsAzure_Exception $e) { |
302 throw new Zend_Cloud_DocumentService_Exception('Error on document insertion: '.$e->getMessage(), $e->getCode(), $e); |
302 throw new Zend_Cloud_DocumentService_Exception('Error on document insertion: '.$e->getMessage(), $e->getCode(), $e); |
303 } |
303 } |
304 } |
304 } |
305 |
305 |
306 /** |
306 /** |
307 * Replace document. |
307 * Replace document. |
308 * |
308 * |
309 * The new document replaces the existing document. |
309 * The new document replaces the existing document. |
310 * |
310 * |
311 * @param Zend_Cloud_DocumentService_Document $document |
311 * @param Zend_Cloud_DocumentService_Document $document |
312 * @param array $options |
312 * @param array $options |
313 * @return boolean |
313 * @return boolean |
314 */ |
314 */ |
315 public function replaceDocument($collectionName, $document, $options = null) |
315 public function replaceDocument($collectionName, $document, $options = null) |
316 { |
316 { |
317 if (is_array($document)) { |
317 if (is_array($document)) { |
318 $document = $this->_getDocumentFromArray($document, $collectionName); |
318 $document = $this->_getDocumentFromArray($document, $collectionName); |
319 } |
319 } |
320 |
320 |
321 if (!$document instanceof Zend_Cloud_DocumentService_Document) { |
321 if (!$document instanceof Zend_Cloud_DocumentService_Document) { |
322 throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); |
322 throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); |
323 } |
323 } |
324 |
324 |
325 $key = $this->_validateDocumentId($document->getId(), $collectionName); |
325 $key = $this->_validateDocumentId($document->getId(), $collectionName); |
326 $this->_validateFields($document); |
326 $this->_validateFields($document); |
327 try { |
327 try { |
328 $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]); |
328 $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]); |
329 $entity->setAzureValues($document->getFields(), true); |
329 $entity->setAzureValues($document->getFields(), true); |
330 if (isset($options[self::VERIFY_ETAG])) { |
330 if (isset($options[self::VERIFY_ETAG])) { |
331 $entity->setEtag($options[self::VERIFY_ETAG]); |
331 $entity->setEtag($options[self::VERIFY_ETAG]); |
332 } |
332 } |
333 |
333 |
334 $this->_storageClient->updateEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG])); |
334 $this->_storageClient->updateEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG])); |
335 } catch(Zend_Service_WindowsAzure_Exception $e) { |
335 } catch(Zend_Service_WindowsAzure_Exception $e) { |
336 throw new Zend_Cloud_DocumentService_Exception('Error on document replace: '.$e->getMessage(), $e->getCode(), $e); |
336 throw new Zend_Cloud_DocumentService_Exception('Error on document replace: '.$e->getMessage(), $e->getCode(), $e); |
337 } |
337 } |
338 } |
338 } |
339 |
339 |
340 /** |
340 /** |
341 * Update document. |
341 * Update document. |
342 * |
342 * |
343 * The new document is merged the existing document. |
343 * The new document is merged the existing document. |
344 * |
344 * |
345 * @param string $collectionName |
345 * @param string $collectionName |
346 * @param mixed|Zend_Cloud_DocumentService_Document $documentId Document identifier or document contaiing updates |
346 * @param mixed|Zend_Cloud_DocumentService_Document $documentId Document identifier or document contaiing updates |
347 * @param null|array|Zend_Cloud_DocumentService_Document Fields to update (or new fields)) |
347 * @param null|array|Zend_Cloud_DocumentService_Document Fields to update (or new fields)) |
373 |
373 |
374 $entity->setAzureValues($fieldset, true); |
374 $entity->setAzureValues($fieldset, true); |
375 if (isset($options[self::VERIFY_ETAG])) { |
375 if (isset($options[self::VERIFY_ETAG])) { |
376 $entity->setEtag($options[self::VERIFY_ETAG]); |
376 $entity->setEtag($options[self::VERIFY_ETAG]); |
377 } |
377 } |
378 |
378 |
379 $this->_storageClient->mergeEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG])); |
379 $this->_storageClient->mergeEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG])); |
380 } catch(Zend_Service_WindowsAzure_Exception $e) { |
380 } catch(Zend_Service_WindowsAzure_Exception $e) { |
381 throw new Zend_Cloud_DocumentService_Exception('Error on document update: '.$e->getMessage(), $e->getCode(), $e); |
381 throw new Zend_Cloud_DocumentService_Exception('Error on document update: '.$e->getMessage(), $e->getCode(), $e); |
382 } |
382 } |
383 } |
383 } |
384 |
384 |
385 /** |
385 /** |
386 * Delete document. |
386 * Delete document. |
387 * |
387 * |
388 * @param mixed $document Document ID or Document object. |
388 * @param mixed $document Document ID or Document object. |
389 * @param array $options |
389 * @param array $options |
410 } |
410 } |
411 } |
411 } |
412 |
412 |
413 /** |
413 /** |
414 * Fetch single document by ID |
414 * Fetch single document by ID |
415 * |
415 * |
416 * @param string $collectionName Collection name |
416 * @param string $collectionName Collection name |
417 * @param mixed $documentId Document ID, adapter-dependent |
417 * @param mixed $documentId Document ID, adapter-dependent |
418 * @param array $options |
418 * @param array $options |
419 * @return Zend_Cloud_DocumentService_Document |
419 * @return Zend_Cloud_DocumentService_Document |
420 */ |
420 */ |
430 return false; |
430 return false; |
431 } |
431 } |
432 throw new Zend_Cloud_DocumentService_Exception('Error on document fetch: '.$e->getMessage(), $e->getCode(), $e); |
432 throw new Zend_Cloud_DocumentService_Exception('Error on document fetch: '.$e->getMessage(), $e->getCode(), $e); |
433 } |
433 } |
434 } |
434 } |
435 |
435 |
436 /** |
436 /** |
437 * Query for documents stored in the document service. If a string is passed in |
437 * Query for documents stored in the document service. If a string is passed in |
438 * $query, the query string will be passed directly to the service. |
438 * $query, the query string will be passed directly to the service. |
439 * |
439 * |
440 * @param string $collectionName Collection name |
440 * @param string $collectionName Collection name |
464 } |
464 } |
465 |
465 |
466 $setClass = $this->getDocumentSetClass(); |
466 $setClass = $this->getDocumentSetClass(); |
467 return new $setClass($resultSet); |
467 return new $setClass($resultSet); |
468 } |
468 } |
469 |
469 |
470 /** |
470 /** |
471 * Create query statement |
471 * Create query statement |
472 * |
472 * |
473 * @return Zend_Cloud_DocumentService_Query |
473 * @return Zend_Cloud_DocumentService_Query |
474 */ |
474 */ |
485 if (!$query instanceof $defaultClass) { |
485 if (!$query instanceof $defaultClass) { |
486 throw new Zend_Cloud_DocumentService_Exception('Query class must extend ' . self::DEFAULT_QUERY_CLASS); |
486 throw new Zend_Cloud_DocumentService_Exception('Query class must extend ' . self::DEFAULT_QUERY_CLASS); |
487 } |
487 } |
488 |
488 |
489 $query->select($fields); |
489 $query->select($fields); |
490 return $query; |
490 return $query; |
491 } |
491 } |
492 |
492 |
493 /** |
493 /** |
494 * Get the concrete service client |
494 * Get the concrete service client |
495 * |
495 * |
496 * @return Zend_Service_WindowsAzure_Storage_Table |
496 * @return Zend_Service_WindowsAzure_Storage_Table |
497 */ |
497 */ |
498 public function getClient() |
498 public function getClient() |
499 { |
499 { |
500 return $this->_storageClient; |
500 return $this->_storageClient; |
501 } |
501 } |
502 |
502 |
503 /** |
503 /** |
504 * Resolve table values to attributes |
504 * Resolve table values to attributes |
505 * |
505 * |
506 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity |
506 * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity |
507 * @return array |
507 * @return array |
508 */ |
508 */ |
509 protected function _resolveAttributes(Zend_Service_WindowsAzure_Storage_TableEntity $entity) |
509 protected function _resolveAttributes(Zend_Service_WindowsAzure_Storage_TableEntity $entity) |
510 { |
510 { |
511 $result = array(); |
511 $result = array(); |
512 foreach ($entity->getAzureValues() as $attr) { |
512 foreach ($entity->getAzureValues() as $attr) { |
513 $result[$attr->Name] = $attr->Value; |
513 $result[$attr->Name] = $attr->Value; |
514 } |
514 } |
515 return $result; |
515 return $result; |
516 } |
516 } |
517 |
517 |
518 |
518 |
519 /** |
519 /** |
520 * Validate a partition or row key |
520 * Validate a partition or row key |
521 * |
521 * |
522 * @param string $key |
522 * @param string $key |
523 * @return void |
523 * @return void |
524 * @throws Zend_Cloud_DocumentService_Exception |
524 * @throws Zend_Cloud_DocumentService_Exception |
525 */ |
525 */ |
526 protected function _validateKey($key) |
526 protected function _validateKey($key) |
527 { |
527 { |
530 } |
530 } |
531 } |
531 } |
532 |
532 |
533 /** |
533 /** |
534 * Validate a composite key |
534 * Validate a composite key |
535 * |
535 * |
536 * @param array $key |
536 * @param array $key |
537 * @return throws Zend_Cloud_DocumentService_Exception |
537 * @return throws Zend_Cloud_DocumentService_Exception |
538 */ |
538 */ |
539 protected function _validateCompositeKey(array $key) |
539 protected function _validateCompositeKey(array $key) |
540 { |
540 { |
541 if (2 != count($key)) { |
541 if (2 != count($key)) { |
547 } |
547 } |
548 |
548 |
549 /** |
549 /** |
550 * Validate a document identifier |
550 * Validate a document identifier |
551 * |
551 * |
552 * If the identifier is an array containing a valid partition and row key, |
552 * If the identifier is an array containing a valid partition and row key, |
553 * returns it. If the identifier is a string: |
553 * returns it. If the identifier is a string: |
554 * - if a default partition key is present, it creates an identifier using |
554 * - if a default partition key is present, it creates an identifier using |
555 * that and the provided document ID |
555 * that and the provided document ID |
556 * - if a collection name is provided, it will use that for the partition key |
556 * - if a collection name is provided, it will use that for the partition key |
557 * - otherwise, it's invalid |
557 * - otherwise, it's invalid |
558 * |
558 * |
559 * @param array|string $documentId |
559 * @param array|string $documentId |
560 * @param null|string $collectionName |
560 * @param null|string $collectionName |
561 * @return array |
561 * @return array |
562 * @throws Zend_Cloud_DocumentService_Exception |
562 * @throws Zend_Cloud_DocumentService_Exception |
563 */ |
563 */ |
564 protected function _validateDocumentId($documentId, $collectionName = false) |
564 protected function _validateDocumentId($documentId, $collectionName = false) |
565 { |
565 { |
583 } |
583 } |
584 |
584 |
585 /** |
585 /** |
586 * Validate a document's fields for well-formedness |
586 * Validate a document's fields for well-formedness |
587 * |
587 * |
588 * Since Azure uses Atom, and fieldnames are included as part of XML |
588 * Since Azure uses Atom, and fieldnames are included as part of XML |
589 * element tag names, the field names must be valid XML names. |
589 * element tag names, the field names must be valid XML names. |
590 * |
590 * |
591 * @param Zend_Cloud_DocumentService_Document|array $document |
591 * @param Zend_Cloud_DocumentService_Document|array $document |
592 * @return void |
592 * @return void |
593 * @throws Zend_Cloud_DocumentService_Exception |
593 * @throws Zend_Cloud_DocumentService_Exception |
606 } |
606 } |
607 |
607 |
608 /** |
608 /** |
609 * Validate an individual field name for well-formedness |
609 * Validate an individual field name for well-formedness |
610 * |
610 * |
611 * Since Azure uses Atom, and fieldnames are included as part of XML |
611 * Since Azure uses Atom, and fieldnames are included as part of XML |
612 * element tag names, the field names must be valid XML names. |
612 * element tag names, the field names must be valid XML names. |
613 * |
613 * |
614 * While we could potentially normalize names, this could also lead to |
614 * While we could potentially normalize names, this could also lead to |
615 * conflict with other field names -- which we should avoid. As such, |
615 * conflict with other field names -- which we should avoid. As such, |
616 * invalid field names will raise an exception. |
616 * invalid field names will raise an exception. |
617 * |
617 * |
618 * @param string $key |
618 * @param string $key |
619 * @return void |
619 * @return void |