From ec52cc10f79d2270aa9bdacbe4a98ecef86a0eb1 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Sat, 23 Apr 2011 15:42:40 +0200 Subject: [PATCH 01/45] First GoGrid service prototype --- library/Zend/Service/GoGrid/Exception.php | 34 +++ library/Zend/Service/GoGrid/GoGrid.php | 221 ++++++++++++++++++++ library/Zend/Service/GoGrid/Job.php | 58 ++++++ library/Zend/Service/GoGrid/Object.php | 21 ++ library/Zend/Service/GoGrid/ObjectList.php | 230 +++++++++++++++++++++ 5 files changed, 564 insertions(+) create mode 100644 library/Zend/Service/GoGrid/Exception.php create mode 100644 library/Zend/Service/GoGrid/GoGrid.php create mode 100644 library/Zend/Service/GoGrid/Job.php create mode 100644 library/Zend/Service/GoGrid/Object.php create mode 100644 library/Zend/Service/GoGrid/ObjectList.php diff --git a/library/Zend/Service/GoGrid/Exception.php b/library/Zend/Service/GoGrid/Exception.php new file mode 100644 index 00000000000..4ba87680718 --- /dev/null +++ b/library/Zend/Service/GoGrid/Exception.php @@ -0,0 +1,34 @@ +_apiKey = (string) $key; + $this->_secret = (string) $secret; + if (!empty($apiVer)) { + $this->_apiVersion = (string) $apiVer; + } + } + + /** + * get the HttpClient static instance + * + * @return Zend\Http\Client + */ + private function _getHttpClient() + { + if (empty($this->_httpClient)) { + $this->_httpClient = new HttpClient(); + } + return $this->_httpClient; + } + + /** + * Set the API secret + * + * @param string $secret + */ + public function setSecret($secret) + { + if (!empty($secret)) { + $this->_secret = (string) $secret; + } + } + + /** + * Set the API key + * + * @param string $key + */ + public function setApiKey($key) + { + if (!empty($key)) { + $this->_apiKey = (string) $key; + } + } + + /** + * Set the API version + * + * @param string $ver + */ + public function setVersion($ver) + { + if (!empty($ver) && $ver < self::API_VER) { + $this->_apiVersion = $ver; + } + } + + /** + * Get the API version + * + * @return string + */ + public function getVersion() + { + return $this->_apiVersion; + } + + /** + * Compute the signature for the API call + * This signature is valid in a window of 10 min with the localtime of the server + * + * @return string + */ + private function _computeSignature() + { + return md5($this->_apiKey . $this->_secret . time()); + } + + /** + * + * @param string $method + * @param array $options + * @return array|boolean + */ + protected function _call($method, $options=null) + { + if (!empty($options) && !is_array($options)) { + throw new Exception\InvalidArgumentException("The options must be an array"); + } + $client = $this->_getHttpClient(); + $client->setUri(self::URL_API . $method); + $client->setParameterGet('format', self::FORMAT_API); + $client->setParameterGet('api_key', $this->_apiKey); + $client->setParameterGet('sig', $this->_computeSignature()); + $client->setParameterGet('v', $this->_apiVersion); + if (!empty($options)) { + $client->setParameterGet($options); + } + $this->_error= false; + $this->_errorType= null; + $response = $client->request(); + if ($response->isSuccessful()) { + return json_decode($response->getBody(), true); + } + $this->_error= true; + $this->_errorType= $response->getStatus(); + return false; + } + + /** + * Get the last HTTP response + * + * @return string + */ + public function getLastResponse() + { + return $this->_getHttpClient()->getLastResponse(); + } + /** + * Get the last HTTP request + * + * @return string + */ + public function getLastRequest() + { + return $this->_getHttpClient()->getLastRequest(); + } + /** + * Check if the last request was successful + * + * @return boolean + */ + public function isSuccessful() + { + return ($this->_error===false); + } + /** + * Get the last error type + * + * @return integer + */ + public function getLastError() + { + return $this->_errorType; + } +} \ No newline at end of file diff --git a/library/Zend/Service/GoGrid/Job.php b/library/Zend/Service/GoGrid/Job.php new file mode 100644 index 00000000000..b35a17f0e74 --- /dev/null +++ b/library/Zend/Service/GoGrid/Job.php @@ -0,0 +1,58 @@ +_call(self::API_GRID_JOB_GET, $options); + return new GoGridObject($result); + } +} \ No newline at end of file diff --git a/library/Zend/Service/GoGrid/Object.php b/library/Zend/Service/GoGrid/Object.php new file mode 100644 index 00000000000..40a121d6d5a --- /dev/null +++ b/library/Zend/Service/GoGrid/Object.php @@ -0,0 +1,21 @@ +_attributes= $data; + } + } + public function getAttribute($id) { + if (!empty($this->_attributes) && key_exists($id,$this->_attributes)) { + return $this->_attributes[$id]; + } + return false; + } + public function isSuccessful() { + return !empty($this->_attribute); + } +} diff --git a/library/Zend/Service/GoGrid/ObjectList.php b/library/Zend/Service/GoGrid/ObjectList.php new file mode 100644 index 00000000000..55af11e5288 --- /dev/null +++ b/library/Zend/Service/GoGrid/ObjectList.php @@ -0,0 +1,230 @@ +_constructFromArray($list['list']); + } + } + /** + * Transforms the Array to array of posts + * + * @param array $postList + * @return void + */ + private function _constructFromArray(array $list) + { + foreach ($list as $obj) { + $this->_addObject(new Object($obj)); + } + } + + /** + * Add an object + * + * @param Zend\Service\GoGrid\Object $obj + * @return Zend\Service\GoGrid\JobList + */ + protected function _addObject (Object $obj) + { + $this->_objects[] = $obj; + return $this; + } + /** + * Return number of servers + * + * Implement Countable::count() + * + * @return int + */ + public function count() + { + return count($this->_objects); + } + + /** + * Return the current element + * + * Implement Iterator::current() + * + * @return Zend\Service\GoGrid\Object + */ + public function current() + { + return $this->_objects[$this->_iteratorKey]; + } + + /** + * Return the key of the current element + * + * Implement Iterator::key() + * + * @return int + */ + public function key() + { + return $this->_iteratorKey; + } + + /** + * Move forward to next element + * + * Implement Iterator::next() + * + * @return void + */ + public function next() + { + $this->_iteratorKey += 1; + } + + /** + * Rewind the Iterator to the first element + * + * Implement Iterator::rewind() + * + * @return void + */ + public function rewind() + { + $this->_iteratorKey = 0; + } + + /** + * Check if there is a current element after calls to rewind() or next() + * + * Implement Iterator::valid() + * + * @return bool + */ + public function valid() + { + $numItems = $this->count(); + + if ($numItems > 0 && $this->_iteratorKey < $numItems) { + return true; + } else { + return false; + } + } + + /** + * Whether the offset exists + * + * Implement ArrayAccess::offsetExists() + * + * @param int $offset + * @return bool + */ + public function offsetExists($offset) + { + return ($offset < $this->count()); + } + + /** + * Return value at given offset + * + * Implement ArrayAccess::offsetGet() + * + * @param int $offset + * @throws OutOfBoundsException + * @return Zend\Service\GoGrid\Object + */ + public function offsetGet($offset) + { + if ($this->offsetExists($offset)) { + return $this->_objects[$offset]; + } else { + throw new \OutOfBoundsException('Illegal index'); + } + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetSet() + * + * @param int $offset + * @param string $value + * @throws Zend\Service\GoGrid\Exception + */ + public function offsetSet($offset, $value) + { + throw new Exception('You are trying to set read-only property'); + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetUnset() + * + * @param int $offset + * @throws Zend\Service\GoGrid\Exception + */ + public function offsetUnset($offset) + { + throw new Exception('You are trying to unset read-only property'); + } + /** + * Check if the the object list was successful + * + * @return boolen + */ + public function isSuccessful() { + return !empty($this->_objects); + } +} From d952312126742a801235b089b381e5d7e10173cf Mon Sep 17 00:00:00 2001 From: ezimuel Date: Tue, 26 Apr 2011 15:21:56 +0200 Subject: [PATCH 02/45] Fixed problem of _call with GET parameters using same keys --- library/Zend/Service/GoGrid/GoGrid.php | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/library/Zend/Service/GoGrid/GoGrid.php b/library/Zend/Service/GoGrid/GoGrid.php index a301e294580..0e1d7729b2d 100644 --- a/library/Zend/Service/GoGrid/GoGrid.php +++ b/library/Zend/Service/GoGrid/GoGrid.php @@ -13,7 +13,7 @@ * to license@zend.com so we can send you a copy immediately. * * @category Zend - * @package Zend_Service + * @package Zend\Service * @subpackage GoGrid * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License @@ -58,7 +58,6 @@ abstract class GoGrid * @var Zend\Http\Client */ private $_httpClient; - /** * __construct * @@ -80,7 +79,6 @@ public function __construct($key, $secret, $apiVer = null) $this->_apiVersion = (string) $apiVer; } } - /** * get the HttpClient static instance * @@ -93,7 +91,6 @@ private function _getHttpClient() } return $this->_httpClient; } - /** * Set the API secret * @@ -105,7 +102,6 @@ public function setSecret($secret) $this->_secret = (string) $secret; } } - /** * Set the API key * @@ -117,7 +113,6 @@ public function setApiKey($key) $this->_apiKey = (string) $key; } } - /** * Set the API version * @@ -129,7 +124,6 @@ public function setVersion($ver) $this->_apiVersion = $ver; } } - /** * Get the API version * @@ -139,7 +133,6 @@ public function getVersion() { return $this->_apiVersion; } - /** * Compute the signature for the API call * This signature is valid in a window of 10 min with the localtime of the server @@ -150,7 +143,6 @@ private function _computeSignature() { return md5($this->_apiKey . $this->_secret . time()); } - /** * * @param string $method @@ -163,13 +155,26 @@ protected function _call($method, $options=null) throw new Exception\InvalidArgumentException("The options must be an array"); } $client = $this->_getHttpClient(); - $client->setUri(self::URL_API . $method); $client->setParameterGet('format', self::FORMAT_API); $client->setParameterGet('api_key', $this->_apiKey); $client->setParameterGet('sig', $this->_computeSignature()); $client->setParameterGet('v', $this->_apiVersion); if (!empty($options)) { - $client->setParameterGet($options); + $get=''; + foreach ($options as $key=>$value) { + if (is_array($value)) { + foreach ($value as $val) { + $get.= $key.'='.urlencode($val).'&'; + } + } else { + $client->setParameterGet($key, $value); + } + } + } + if (!empty($get)) { + $client->setUri(self::URL_API . $method.'?'.$get); + } else { + $client->setUri(self::URL_API . $method); } $this->_error= false; $this->_errorType= null; @@ -181,7 +186,6 @@ protected function _call($method, $options=null) $this->_errorType= $response->getStatus(); return false; } - /** * Get the last HTTP response * From 15fb57bd6c942fe59e278920fd84569c8ff38e23 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Tue, 26 Apr 2011 15:22:34 +0200 Subject: [PATCH 03/45] Fixed the get() method --- library/Zend/Service/GoGrid/Job.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/library/Zend/Service/GoGrid/Job.php b/library/Zend/Service/GoGrid/Job.php index b35a17f0e74..5da03974f3e 100644 --- a/library/Zend/Service/GoGrid/Job.php +++ b/library/Zend/Service/GoGrid/Job.php @@ -13,7 +13,7 @@ * to license@zend.com so we can send you a copy immediately. * * @category Zend - * @package Zend_Service + * @package Zend\Service * @subpackage GoGrid * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License @@ -30,7 +30,8 @@ class Job extends GoGridAbstract const API_GRID_JOB_LIST = 'grid/job/list'; const API_GRID_JOB_GET = 'grid/job/get'; /** - * get job list API + * Get job list API + * This call will list all the jobs in the system for a specified date range. The default is the last month. * * @param array $options * @return Zend\Service\GoGrid\ObjectList @@ -40,19 +41,20 @@ public function getList($options=null) { return new GoGridObjectList($result); } /** - * get job API + * Get job API + * This call will retrieve one or many job objects from your list of jobs * - * @param array $options + * @param string|array $job * @return Zend\Service\GoGrid\ObjectList */ - public function get($id, $job, $options=null) + public function get($job) { - if (empty($options)) { - $options = array(); + if (empty($job)) { + throw new Exception\InvalidArgumentException("The job.get API needs a id/job parameter"); } - $options['id'] = $id; - $options['job'] = $job; + $options=array(); + $options['job']= $job; $result= $this->_call(self::API_GRID_JOB_GET, $options); - return new GoGridObject($result); + return new GoGridObjectList($result); } } \ No newline at end of file From 805029e60fea645d41878364035f6d50811229c1 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Tue, 26 Apr 2011 15:23:33 +0200 Subject: [PATCH 04/45] Commented the source code --- library/Zend/Service/GoGrid/Object.php | 46 +++++++++++++++-- library/Zend/Service/GoGrid/ObjectList.php | 57 ++++++++++++++++------ 2 files changed, 86 insertions(+), 17 deletions(-) diff --git a/library/Zend/Service/GoGrid/Object.php b/library/Zend/Service/GoGrid/Object.php index 40a121d6d5a..d06ff178601 100644 --- a/library/Zend/Service/GoGrid/Object.php +++ b/library/Zend/Service/GoGrid/Object.php @@ -1,20 +1,60 @@ _attributes= $data; } } - public function getAttribute($id) { - if (!empty($this->_attributes) && key_exists($id,$this->_attributes)) { - return $this->_attributes[$id]; + /** + * Get Attribute with a specific key + * + * @param array $data + * @return misc|boolean + */ + public function getAttribute($key) { + if (array_key_exists($key,$this->_attributes)) { + return $this->_attributes[$key]; } return false; } + /** + * Check if the last call was successful + * + * @return boolean + */ public function isSuccessful() { return !empty($this->_attribute); } diff --git a/library/Zend/Service/GoGrid/ObjectList.php b/library/Zend/Service/GoGrid/ObjectList.php index 55af11e5288..87a1dadf030 100644 --- a/library/Zend/Service/GoGrid/ObjectList.php +++ b/library/Zend/Service/GoGrid/ObjectList.php @@ -13,7 +13,7 @@ * to license@zend.com so we can send you a copy immediately. * * @category Zend - * @package Zend_Service + * @package Zend\Service * @subpackage GoGrid * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License @@ -33,7 +33,7 @@ * @uses Countable * @uses Iterator * @uses OutOfBoundsException - * @uses Zend_Service_GoGrid_Server + * @uses Zend_Service_GoGrid_Object * @category Zend * @package Zend_Service * @subpackage GoGrid @@ -50,6 +50,18 @@ class ObjectList implements \Countable, \Iterator, \ArrayAccess * @var int Iterator key */ protected $_iteratorKey = 0; + /** + * @var array + */ + protected $_summary; + /** + * @var string + */ + protected $_status; + /** + * @var string + */ + protected $_method; /** * @param array $list * @return void @@ -59,11 +71,20 @@ public function __construct($list = null) if (is_array($list)) { $this->_constructFromArray($list['list']); } + if (array_key_exists('summary', $list)) { + $this->_summary= $list['summary']; + } + if (array_key_exists('status', $list)) { + $this->_status= $list['status']; + } + if (array_key_exists('method', $list)) { + $this->_method= $list['method']; + } } /** * Transforms the Array to array of posts * - * @param array $postList + * @param array $list * @return void */ private function _constructFromArray(array $list) @@ -72,12 +93,11 @@ private function _constructFromArray(array $list) $this->_addObject(new Object($obj)); } } - /** * Add an object * * @param Zend\Service\GoGrid\Object $obj - * @return Zend\Service\GoGrid\JobList + * @return Zend\Service\GoGrid\ObjectList */ protected function _addObject (Object $obj) { @@ -95,7 +115,6 @@ public function count() { return count($this->_objects); } - /** * Return the current element * @@ -107,7 +126,6 @@ public function current() { return $this->_objects[$this->_iteratorKey]; } - /** * Return the key of the current element * @@ -119,7 +137,6 @@ public function key() { return $this->_iteratorKey; } - /** * Move forward to next element * @@ -131,7 +148,6 @@ public function next() { $this->_iteratorKey += 1; } - /** * Rewind the Iterator to the first element * @@ -143,7 +159,6 @@ public function rewind() { $this->_iteratorKey = 0; } - /** * Check if there is a current element after calls to rewind() or next() * @@ -154,14 +169,12 @@ public function rewind() public function valid() { $numItems = $this->count(); - if ($numItems > 0 && $this->_iteratorKey < $numItems) { return true; } else { return false; } } - /** * Whether the offset exists * @@ -174,7 +187,6 @@ public function offsetExists($offset) { return ($offset < $this->count()); } - /** * Return value at given offset * @@ -189,7 +201,7 @@ public function offsetGet($offset) if ($this->offsetExists($offset)) { return $this->_objects[$offset]; } else { - throw new \OutOfBoundsException('Illegal index'); + throw new Exception\OutOfBoundsException('Illegal index'); } } @@ -227,4 +239,21 @@ public function offsetUnset($offset) public function isSuccessful() { return !empty($this->_objects); } + + public function getSummary($key=null) { + if (!empty($key)) { + if (array_key_exists($key, $this->_summary)) { + return $this->_summary[$key]; + } else { + return false; + } + } + return $this->_summary; + } + public function getMethod() { + return $this->_method; + } + public function getStatus() { + return $this->_status; + } } From 21b744793610f5eba9269f937308923e95487916 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Tue, 26 Apr 2011 15:24:00 +0200 Subject: [PATCH 05/45] New exception --- .../Exception/InvalidArgumentException.php | 39 +++++++++++++++++++ .../GoGrid/Exception/OutOfBoundsException.php | 39 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 library/Zend/Service/GoGrid/Exception/InvalidArgumentException.php create mode 100644 library/Zend/Service/GoGrid/Exception/OutOfBoundsException.php diff --git a/library/Zend/Service/GoGrid/Exception/InvalidArgumentException.php b/library/Zend/Service/GoGrid/Exception/InvalidArgumentException.php new file mode 100644 index 00000000000..d2789b937a7 --- /dev/null +++ b/library/Zend/Service/GoGrid/Exception/InvalidArgumentException.php @@ -0,0 +1,39 @@ + Date: Wed, 27 Apr 2011 12:42:00 +0200 Subject: [PATCH 06/45] changed name of setVersion in setApiVersion --- library/Zend/Service/GoGrid/GoGrid.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Zend/Service/GoGrid/GoGrid.php b/library/Zend/Service/GoGrid/GoGrid.php index 0e1d7729b2d..13e696f3f95 100644 --- a/library/Zend/Service/GoGrid/GoGrid.php +++ b/library/Zend/Service/GoGrid/GoGrid.php @@ -118,7 +118,7 @@ public function setApiKey($key) * * @param string $ver */ - public function setVersion($ver) + public function setApiVersion($ver) { if (!empty($ver) && $ver < self::API_VER) { $this->_apiVersion = $ver; @@ -129,7 +129,7 @@ public function setVersion($ver) * * @return string */ - public function getVersion() + public function getApiVersion() { return $this->_apiVersion; } From 4ca5c2c847d04fe0dd07c6b25175a6ffb0a34551 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 12:43:13 +0200 Subject: [PATCH 07/45] Added the GoGrid unit test --- tests/TestConfiguration.php.dist | 7 + tests/Zend/Service/GoGrid/JobOfflineTest.php | 150 ++++++++++++++++ tests/Zend/Service/GoGrid/_files/job_get.json | 90 ++++++++++ .../Zend/Service/GoGrid/_files/job_list.json | 168 ++++++++++++++++++ tests/runtests.sh | 2 +- 5 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 tests/Zend/Service/GoGrid/JobOfflineTest.php create mode 100644 tests/Zend/Service/GoGrid/_files/job_get.json create mode 100644 tests/Zend/Service/GoGrid/_files/job_list.json mode change 100644 => 100755 tests/runtests.sh diff --git a/tests/TestConfiguration.php.dist b/tests/TestConfiguration.php.dist index 117d572b7bc..7b5ec083ede 100644 --- a/tests/TestConfiguration.php.dist +++ b/tests/TestConfiguration.php.dist @@ -573,6 +573,13 @@ define('TESTS_ZEND_SERVICE_DEVELOPERGARDEN_ONLINE_PASSWORD', 'ZF_Password'); define('TESTS_ZEND_SERVICE_FLICKR_ONLINE_ENABLED', false); define('TESTS_ZEND_SERVICE_FLICKR_ONLINE_APIKEY', 'Enter API key here'); +/** + * Zend_service_GoGrid offline tests + */ + +define ('TESTS_ZEND_SERVICE_GOGRID_OFFLINE_KEY','test'); +define ('TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET','test'); + /** * Zend_Service_LiveDocx configuration * diff --git a/tests/Zend/Service/GoGrid/JobOfflineTest.php b/tests/Zend/Service/GoGrid/JobOfflineTest.php new file mode 100644 index 00000000000..ef9ca8f63c1 --- /dev/null +++ b/tests/Zend/Service/GoGrid/JobOfflineTest.php @@ -0,0 +1,150 @@ +_job = new Job(TESTS_ZEND_SERVICE_GOGRID_OFFLINE_KEY,TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET); + + } + + /** + * Ensures that __construct() throws an exception when given an empty key attribute + * + * @return void + */ + public function testConstructExceptionMissingKeyAttribute() + { + $this->setExpectedException( + 'Zend\Service\GoGrid\Exception\InvalidArgumentException', + 'The key cannot be empty' + ); + $job= new Job(null,TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET); + } + /** + * Ensures that __construct() throws an exception when given an empty secret attribute + * + * @return void + */ + public function testConstructExceptionMissingSecretAttribute() + { + $this->setExpectedException( + 'Zend\Service\GoGrid\Exception\InvalidArgumentException', + 'The secret cannot be empty' + ); + $job= new Job(TESTS_ZEND_SERVICE_GOGRID_OFFLINE_KEY,null); + } + /** + * testJobList + * + * @return void + */ + public function testJobList() + { + $file= file_get_contents(__DIR__."/_files/job_list.json"); + $joblist= new Zend\Service\GoGrid\ObjectList($file); + + $this->assertEquals(count($joblist),2); + $this->assertEquals($joblist['status'],'success'); + + $job= $joblist[0]; + $this->assertEquals($job->getAttribute('id'), '60531'); + $this->assertEquals($job->getAttribute('owner'),'ankit@gogrid.com'); + $command= $job->getAttribute('command'); + $this->assertEquals($command['name'],'CreateVirtualServer'); + $history= $job->getAttribute('history'); + $this->assertEquals($history[0]['id'],'10242'); + $this->assertEquals(count($history),4); + } + + /** + * testApiVersion + * + * @return void + */ + public function testApiVersion() + { + $this->assertEquals($this->_job->getApiVersion(),Job::VERSION_API); + $this->_job->setApiVersion('1.0'); + $this->assertEquals($this->_job->getApiVersion(),'1.0'); + } + +} diff --git a/tests/Zend/Service/GoGrid/_files/job_get.json b/tests/Zend/Service/GoGrid/_files/job_get.json new file mode 100644 index 00000000000..5b54daa3dbf --- /dev/null +++ b/tests/Zend/Service/GoGrid/_files/job_get.json @@ -0,0 +1,90 @@ +{ + "list": [ + { + "attempts": 1, + "command": { + "description": "Create Virtual Server", + "id": 6, + "name": "CreateVirtualServer", + "object": "option" + }, + "createdon": 1231007644632, + "currentstate": { + "description": "Change request has succeeded.", + "id": 3, + "name": "Succeeded", + "object": "option" + }, + "datacenter": { + "description": "US East 1 Datacenter", + "id": 2, + "name": "US-East-1", + "object": "option" + }, + "detail": { + "description": "tests", + "image": "w2k3_64_iis_asp_mssql2k5xp", + "ip": "208.113.76.34", + "name": "test 2", + "type": "virtual_server" + }, + "history": [ + { + "id": 10242, + "state": { + "description": "Change request is created but not queued yet", + "id": 7, + "name": "Created", + "object": "option" + } + }, + { + "id": 10243, + "state": { + "description": "Change request is new to the system.", + "id": 1, + "name": "Queued", + "object": "option" + } + }, + { + "id": 10244, + "state": { + "description": "Change request is is transient state...Processing.", + "id": 2, + "name": "Processing", + "object": "option" + } + }, + { + "id": 10251, + "state": { + "description": "Change request has succeeded.", + "id": 3, + "name": "Succeeded", + "object": "option" + }, + "updatedon": 1231008346317 + } + ], + "id": 60531, + "lastupdatedon": 1231008346317, + "object": "job", + "objecttype": { + "description": null, + "id": 1, + "name": "VirtualServer", + "object": "option" + }, + "owner": "ankit@gogrid.com" + } + ], + "method": "/grid/job/get", + "status": "success", + "summary": { + "numpages": 1, + "returned": 1, + "start": 0, + "total": 1 + } +} \ No newline at end of file diff --git a/tests/Zend/Service/GoGrid/_files/job_list.json b/tests/Zend/Service/GoGrid/_files/job_list.json new file mode 100644 index 00000000000..b14b65135af --- /dev/null +++ b/tests/Zend/Service/GoGrid/_files/job_list.json @@ -0,0 +1,168 @@ +{ + "list": [ + { + "attempts": 1, + "command": { + "description": "Create Virtual Server", + "id": 6, + "name": "CreateVirtualServer", + "object": "option" + }, + "createdon": 1231007644632, + "currentstate": { + "description": "Change request has succeeded.", + "id": 3, + "name": "Succeeded", + "object": "option" + }, + "datacenter": { + "description": "US East 1 Datacenter", + "id": 2, + "name": "US-East-1", + "object": "option" + }, + "detail": { + "description": "tests", + "image": "w2k3_64_iis_asp_mssql2k5xp", + "ip": "208.113.76.34", + "name": "test 2", + "type": "virtual_server" + }, + "history": [ + { + "id": 10242, + "state": { + "description": "Change request is created but not queued yet", + "id": 7, + "name": "Created", + "object": "option" + } + }, + { + "id": 10243, + "state": { + "description": "Change request is new to the system.", + "id": 1, + "name": "Queued", + "object": "option" + } + }, + { + "id": 10244, + "state": { + "description": "Change request is is transient state...Processing.", + "id": 2, + "name": "Processing", + "object": "option" + } + }, + { + "id": 10251, + "state": { + "description": "Change request has succeeded.", + "id": 3, + "name": "Succeeded", + "object": "option" + }, + "updatedon": 1231008346317 + } + ], + "id": 60531, + "lastupdatedon": 1231008346317, + "object": "job", + "objecttype": { + "description": null, + "id": 1, + "name": "VirtualServer", + "object": "option" + }, + "owner": "ankit@gogrid.com" + }, + { + "attempts": 1, + "command": { + "description": "Create Virtual Server", + "id": 6, + "name": "CreateVirtualServer", + "object": "option" + }, + "createdon": 1231007606467, + "currentstate": { + "description": "Change request has succeeded.", + "id": 3, + "name": "Succeeded", + "object": "option" + }, + "datacenter": { + "description": "US West 1 Datacenter", + "id": 1, + "name": "US-West-1", + "object": "option" + }, + "detail": { + "description": "tetsss", + "image": "centos51_64_lamp", + "ip": "208.113.76.32", + "name": "test", + "type": "virtual_server" + }, + "history": [ + { + "id": 10239, + "state": { + "description": "Change request is created but not queued yet", + "id": 7, + "name": "Created", + "object": "option" + } + }, + { + "id": 10240, + "state": { + "description": "Change request is new to the system.", + "id": 1, + "name": "Queued", + "object": "option" + } + }, + { + "id": 10241, + "state": { + "description": "Change request is is transient state...Processing.", + "id": 2, + "name": "Processing", + "object": "option" + } + }, + { + "id": 10246, + "state": { + "description": "Change request has succeeded.", + "id": 3, + "name": "Succeeded", + "object": "option" + }, + "updatedon": 1231008211834 + } + ], + "id": 60530, + "lastupdatedon": 1231008211834, + "object": "job", + "objecttype": { + "description": null, + "id": 1, + "name": "VirtualServer", + "object": "option" + }, + "owner": "ankit@gogrid.com" + } + ], + "method": "/grid/job/list", + "status": "success", + "summary": { + "numpages": 1, + "returned": 7, + "start": 0, + "total": 7 + } +} \ No newline at end of file diff --git a/tests/runtests.sh b/tests/runtests.sh old mode 100644 new mode 100755 index 2c68021bda6..660bfb0a74f --- a/tests/runtests.sh +++ b/tests/runtests.sh @@ -46,7 +46,7 @@ while [ -n "$1" ] ; do PHPUNIT_GROUPS="" break ;; - Akismet|Amazon|Amazon_Ec2|Amazon_S3|Amazon_Sqs|Audioscrobbler|Delicious|Flickr|LiveDocx|Nirvanix|ReCaptcha|Simpy|SlideShare|StrikeIron|Technorati|Twitter|WindowsAzure|Yahoo) + Akismet|Amazon|Amazon_Ec2|Amazon_S3|Amazon_Sqs|Audioscrobbler|Delicious|Flickr|GoGrid|LiveDocx|Nirvanix|ReCaptcha|Simpy|SlideShare|StrikeIron|Technorati|Twitter|WindowsAzure|Yahoo) PHPUNIT_GROUPS="${PHPUNIT_GROUPS:+"$PHPUNIT_GROUPS,"}Zend_Service_$1" shift ;; Ec2|S3) From 82b65203a502db77f59d6f5ac16cf4e0acfae153 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 12:44:05 +0200 Subject: [PATCH 08/45] Changes the comment --- .../Zend/Service/GoGrid/Exception/OutOfBoundsException.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Zend/Service/GoGrid/Exception/OutOfBoundsException.php b/library/Zend/Service/GoGrid/Exception/OutOfBoundsException.php index d877dab8550..634c544051f 100644 --- a/library/Zend/Service/GoGrid/Exception/OutOfBoundsException.php +++ b/library/Zend/Service/GoGrid/Exception/OutOfBoundsException.php @@ -14,7 +14,7 @@ * * @uses \Zend\Service\GoGrid\Exception * @category Zend - * @package Zend_Service + * @package Zend\Service * @subpackage GoGrid * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License @@ -27,7 +27,7 @@ /** * @category Zend - * @package Zend_Service + * @package Zend\Service * @subpackage GoGrid * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License From 828f6111591eb5b40b79cb48a7866c01c87cf8e4 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 17:28:27 +0200 Subject: [PATCH 09/45] Added the HTTP status of the last request --- library/Zend/Service/GoGrid/GoGrid.php | 40 ++++++++------------------ 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/library/Zend/Service/GoGrid/GoGrid.php b/library/Zend/Service/GoGrid/GoGrid.php index 13e696f3f95..82547627712 100644 --- a/library/Zend/Service/GoGrid/GoGrid.php +++ b/library/Zend/Service/GoGrid/GoGrid.php @@ -53,11 +53,13 @@ abstract class GoGrid */ protected $_apiVersion = self::VERSION_API; /** - * HttpClient - * * @var Zend\Http\Client */ - private $_httpClient; + protected $_httpClient; + /** + * @var Zend\Http\Response + */ + protected $_lastResponse; /** * __construct * @@ -73,11 +75,9 @@ public function __construct($key, $secret, $apiVer = null) if (!isset($secret)) { throw new Exception\InvalidArgumentException("The secret cannot be empty"); } - $this->_apiKey = (string) $key; - $this->_secret = (string) $secret; - if (!empty($apiVer)) { - $this->_apiVersion = (string) $apiVer; - } + $this->setApiKey($key); + $this->setSecret($secret); + $this->setApiVersion($apiVer); } /** * get the HttpClient static instance @@ -176,15 +176,8 @@ protected function _call($method, $options=null) } else { $client->setUri(self::URL_API . $method); } - $this->_error= false; - $this->_errorType= null; - $response = $client->request(); - if ($response->isSuccessful()) { - return json_decode($response->getBody(), true); - } - $this->_error= true; - $this->_errorType= $response->getStatus(); - return false; + $this->_lastResponse = $client->request(); + return json_decode($this->_lastResponse->getBody(), true); } /** * Get the last HTTP response @@ -204,22 +197,13 @@ public function getLastRequest() { return $this->_getHttpClient()->getLastRequest(); } - /** - * Check if the last request was successful - * - * @return boolean - */ - public function isSuccessful() - { - return ($this->_error===false); - } /** * Get the last error type * * @return integer */ - public function getLastError() + public function getHttpStatus() { - return $this->_errorType; + return $this->_lastResponse->getStatus(); } } \ No newline at end of file From 431fe2f2456d369cee9c4375580a0a409b3e675d Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 17:29:25 +0200 Subject: [PATCH 10/45] Removed the isSuccessful method --- library/Zend/Service/GoGrid/Object.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/library/Zend/Service/GoGrid/Object.php b/library/Zend/Service/GoGrid/Object.php index d06ff178601..e39b7ff0da2 100644 --- a/library/Zend/Service/GoGrid/Object.php +++ b/library/Zend/Service/GoGrid/Object.php @@ -50,12 +50,4 @@ public function getAttribute($key) { } return false; } - /** - * Check if the last call was successful - * - * @return boolean - */ - public function isSuccessful() { - return !empty($this->_attribute); - } } From 251bd9eb1d9823926aefa2bbf6997e3181d60c5f Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 17:29:50 +0200 Subject: [PATCH 11/45] Fixed the isSuccessful method --- library/Zend/Service/GoGrid/ObjectList.php | 57 ++++++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/library/Zend/Service/GoGrid/ObjectList.php b/library/Zend/Service/GoGrid/ObjectList.php index 87a1dadf030..898004e9451 100644 --- a/library/Zend/Service/GoGrid/ObjectList.php +++ b/library/Zend/Service/GoGrid/ObjectList.php @@ -42,6 +42,7 @@ */ class ObjectList implements \Countable, \Iterator, \ArrayAccess { + const SUCCESS_STATUS= 'success'; /** * @var array Array of Zend\Service\GoGrid\Object */ @@ -63,23 +64,41 @@ class ObjectList implements \Countable, \Iterator, \ArrayAccess */ protected $_method; /** + * @var boolean + */ + protected $_error= true; + /** + * @var string + */ + protected $_errorMsg= ''; + /** + * __construct() + * * @param array $list - * @return void + * @return boolean */ - public function __construct($list = null) + public function __construct($list = array()) { - if (is_array($list)) { - $this->_constructFromArray($list['list']); - } - if (array_key_exists('summary', $list)) { - $this->_summary= $list['summary']; + if (empty($list) || !is_array($list)) { + return false; } if (array_key_exists('status', $list)) { $this->_status= $list['status']; + if ($this->_status!=self::SUCCESS_STATUS) { + $this->_errorMsg= $list['list'][0]['message']; + } else { + $this->_error= false; + } + } + if (array_key_exists('summary', $list)) { + $this->_summary= $list['summary']; } if (array_key_exists('method', $list)) { $this->_method= $list['method']; } + if (!$this->_error) { + $this->_constructFromArray($list['list']); + } } /** * Transforms the Array to array of posts @@ -232,14 +251,22 @@ public function offsetUnset($offset) throw new Exception('You are trying to unset read-only property'); } /** - * Check if the the object list was successful + * Check if the service call was successful * * @return boolen */ public function isSuccessful() { - return !empty($this->_objects); + return ($this->_error===false); } - + public function getError() { + return $this->_errorMsg; + } + /** + * getSummary + * + * @param string $key + * @return string|array + */ public function getSummary($key=null) { if (!empty($key)) { if (array_key_exists($key, $this->_summary)) { @@ -250,9 +277,19 @@ public function getSummary($key=null) { } return $this->_summary; } + /** + * getMethod + * + * @return string + */ public function getMethod() { return $this->_method; } + /** + * getStatus + * + * @return string + */ public function getStatus() { return $this->_status; } From 57a6d450bc6abf917448ca52e3a242a6f1e247ac Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 17:30:37 +0200 Subject: [PATCH 12/45] Server GoGrid Api - first prototype --- library/Zend/Service/GoGrid/Server.php | 171 +++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 library/Zend/Service/GoGrid/Server.php diff --git a/library/Zend/Service/GoGrid/Server.php b/library/Zend/Service/GoGrid/Server.php new file mode 100644 index 00000000000..ab96aa7e6e2 --- /dev/null +++ b/library/Zend/Service/GoGrid/Server.php @@ -0,0 +1,171 @@ +_call(self::API_GRID_SERVER_GET, $options); + return new GoGridObjectList($result); + } + /** + * Add server API + * This call will add a single server object to your grid. + * To create an image sandbox pass the optional isSandbox parameter to true. + * If isSandbox is set to true, the request parameter server.ram is ignored and non-mandatory. + * + * @param string $name + * @param string $image + * @param string $ram + * @param string $ip + * @return Zend\Service\GoGrid\ObjectList + */ + public function add($name,$image,$ram,$ip, $options=array()) { + if (empty($name) || strlen($name)>20) { + throw new Exception\InvalidArgumentException("You must specify the name of the server in a string of 20 character max."); + } + if (empty($image)) { + throw new Exception\InvalidArgumentException("You must specify the server image's ID or name"); + } + if (empty($ram) && (empty($options) || !$options['isSandbox'])) { + throw new Exception\InvalidArgumentException("You must specify the ID or name of the desired RAM option"); + } + if (empty($ip)) { + throw new Exception\InvalidArgumentException("You must specify the IP address of the server"); + } + $options['name']= $name; + $options['image']= $image; + if (!empty($ram)) { + $options['server.ram']= $ram; + } + $options['ip']= $ip; + $result= $this->_call(self::API_GRID_SERVER_ADD, $options); + return new GoGridObjectList($result); + } + /** + * Edit server API + * This call will edit a single server object in your grid. + * You can use this call to edit a server's: + * RAM (Upgrade RAM) + * Server Type (Change between Web/App Server and Database Server) + * Description (Change freeform text description) + * + * @param string|array $server + * @return GoGridObjectList + */ + public function edit($server,$options=array()) + { + if (empty($server)) { + throw new Exception\InvalidArgumentException("The server.edit API needs a id/name server parameters"); + } + $options['server']= $server; + $result= $this->_call(self::API_GRID_SERVER_EDIT, $options); + return new GoGridObjectList($result); + } + /** + * Power server API + * This call will issue a power command to a server object in your grid. + * Supported power commands are: start, stop, and restart + * + * @param string $server + * @param string $power + * @return GoGridObjectList + */ + public function power($server,$power) { + if (empty($server)) { + throw new Exception\InvalidArgumentException("The server.power API needs a id/name server parameter"); + } + $power=strtolower($power); + if (empty($power) || !in_array($power,array(self::API_POWER_START,self::API_POWER_STOP,self::API_POWER_RESTART))) { + throw new Exception\InvalidArgumentException("The server.power API needs the power parameter (start,stop, or restart)"); + } + $options=array(); + $options['server']= $server; + $options['power']= $power; + $result= $this->_call(self::API_GRID_SERVER_POWER, $options); + return new GoGridObjectList($result); + } + /** + * Start a server + * + * @param string $server + * @return GoGridObjectList + */ + public function start($server) { + return $this->power($server,self::API_POWER_START); + } + /** + * Stop a server + * + * @param string $server + * @return GoGridObjectList + */ + public function stop($server) { + return $this->power($server,self::API_POWER_STOP); + } + /** + * Restart a server + * + * @param string $server + * @return GoGridObjectList + */ + public function restart($server) { + return $this->power($server,self::API_POWER_RESTART); + } +} From 38b4d7165189bbe76b149a0ad92484e7d9afa76d Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 18:40:45 +0200 Subject: [PATCH 13/45] Changed the getHttpClient() to public --- library/Zend/Service/GoGrid/GoGrid.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/Zend/Service/GoGrid/GoGrid.php b/library/Zend/Service/GoGrid/GoGrid.php index 82547627712..e601a8b50d7 100644 --- a/library/Zend/Service/GoGrid/GoGrid.php +++ b/library/Zend/Service/GoGrid/GoGrid.php @@ -84,7 +84,7 @@ public function __construct($key, $secret, $apiVer = null) * * @return Zend\Http\Client */ - private function _getHttpClient() + public function getHttpClient() { if (empty($this->_httpClient)) { $this->_httpClient = new HttpClient(); @@ -120,7 +120,7 @@ public function setApiKey($key) */ public function setApiVersion($ver) { - if (!empty($ver) && $ver < self::API_VER) { + if (!empty($ver) && $ver < self::VERSION_API) { $this->_apiVersion = $ver; } } @@ -154,7 +154,7 @@ protected function _call($method, $options=null) if (!empty($options) && !is_array($options)) { throw new Exception\InvalidArgumentException("The options must be an array"); } - $client = $this->_getHttpClient(); + $client = $this->getHttpClient(); $client->setParameterGet('format', self::FORMAT_API); $client->setParameterGet('api_key', $this->_apiKey); $client->setParameterGet('sig', $this->_computeSignature()); @@ -186,7 +186,7 @@ protected function _call($method, $options=null) */ public function getLastResponse() { - return $this->_getHttpClient()->getLastResponse(); + return $this->getHttpClient()->getLastResponse(); } /** * Get the last HTTP request @@ -195,7 +195,7 @@ public function getLastResponse() */ public function getLastRequest() { - return $this->_getHttpClient()->getLastRequest(); + return $this->getHttpClient()->getLastRequest(); } /** * Get the last error type From 64c03e1edd4b7f8937553468bbc9a352a7c2d04f Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 18:42:07 +0200 Subject: [PATCH 14/45] Use of the Http Client Test Adapter --- tests/Zend/Service/GoGrid/JobOfflineTest.php | 65 ++++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/tests/Zend/Service/GoGrid/JobOfflineTest.php b/tests/Zend/Service/GoGrid/JobOfflineTest.php index ef9ca8f63c1..a52393063cb 100644 --- a/tests/Zend/Service/GoGrid/JobOfflineTest.php +++ b/tests/Zend/Service/GoGrid/JobOfflineTest.php @@ -23,33 +23,14 @@ * @namespace */ namespace ZendTest\Service\GoGrid; -use Zend\Service\GoGrid\Job; +use Zend\Service\GoGrid\Job, + Zend\Service\GoGrid\ObjectList, + Zend\Http\Client\Adapter\Test as HttpTest; /** * Test helper */ -/** - * @see Zend_Service_Amazon - */ - -/** - * @see Zend_Service_Amazon_ResultSet - */ - -/** - * @see Zend_Service_Amazon_ResultSet - */ - -/** - * @see Zend_Http_Client_Adapter_Socket - */ - -/** - * @see Zend\Http\Client\Adapter\Test - */ - - /** * @category Zend * @package Zend\Service\GoGrid @@ -67,14 +48,18 @@ class JobOfflineTest extends \PHPUnit_Framework_TestCase * @var Zend\Service\GoGrid\Job */ protected $_job; - /** * HTTP client adapter for testing * * @var Zend\Http\Client\Adapter\Test */ protected $_httpClientAdapterTest; - + /** + * Path to test data files + * + * @var string + */ + protected $_filesPath; /** * Sets up this test case * @@ -83,6 +68,8 @@ class JobOfflineTest extends \PHPUnit_Framework_TestCase public function setUp() { $this->_job = new Job(TESTS_ZEND_SERVICE_GOGRID_OFFLINE_KEY,TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET); + $this->_filesPath = __DIR__ . '/_files'; + $this->_httpClientAdapterTest = new HttpTest(); } @@ -119,22 +106,25 @@ public function testConstructExceptionMissingSecretAttribute() */ public function testJobList() { - $file= file_get_contents(__DIR__."/_files/job_list.json"); - $joblist= new Zend\Service\GoGrid\ObjectList($file); + $this->_job->getHttpClient() + ->setAdapter($this->_httpClientAdapterTest); + + $this->_httpClientAdapterTest->setResponse($this->_loadResponse(__FUNCTION__)); + + $joblist= $this->_job->getList(); $this->assertEquals(count($joblist),2); - $this->assertEquals($joblist['status'],'success'); + $this->assertEquals($joblist->getStatus(),'success'); $job= $joblist[0]; - $this->assertEquals($job->getAttribute('id'), '60531'); - $this->assertEquals($job->getAttribute('owner'),'ankit@gogrid.com'); + $this->assertEquals($job->getAttribute('id'), '583288'); + $this->assertEquals($job->getAttribute('owner'),'enrico@zend.com'); $command= $job->getAttribute('command'); - $this->assertEquals($command['name'],'CreateVirtualServer'); + $this->assertEquals($command['name'],'DeleteVirtualServer'); $history= $job->getAttribute('history'); - $this->assertEquals($history[0]['id'],'10242'); + $this->assertEquals($history[0]['id'],'3303238'); $this->assertEquals(count($history),4); } - /** * testApiVersion * @@ -146,5 +136,14 @@ public function testApiVersion() $this->_job->setApiVersion('1.0'); $this->assertEquals($this->_job->getApiVersion(),'1.0'); } - + /** + * Utility method for returning a string HTTP response, which is loaded from a file + * + * @param string $name + * @return string + */ + protected function _loadResponse($name) + { + return file_get_contents("$this->_filesPath/$name.response"); + } } From 38fce96a94b1fa310cab55439c29fec13c29c23d Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 18:42:57 +0200 Subject: [PATCH 15/45] HTTP mock response --- .../Zend/Service/GoGrid/_files/testJobList.response | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/Zend/Service/GoGrid/_files/testJobList.response diff --git a/tests/Zend/Service/GoGrid/_files/testJobList.response b/tests/Zend/Service/GoGrid/_files/testJobList.response new file mode 100644 index 00000000000..949f8ec415d --- /dev/null +++ b/tests/Zend/Service/GoGrid/_files/testJobList.response @@ -0,0 +1,12 @@ +HTTP/1.0 200 OK +Connection: close +Connection: close +X-mashery-responder: mashery-web5.LAX +Server: Apache-Coyote/1.1 +Set-cookie: JSESSIONID=xxx; Path=/api; Secure +Date: Wed, 27 Apr 2011 16:22:08 GMT +Accept-ranges: bytes +Content-length: 2832 +Content-type: text/html + +{"summary":{"total":2,"start":0,"numpages":8,"returned":2},"status":"success","method":"/grid/job/list","list":[{"id":583288,"history":[{"id":3303238,"updatedon":1303920388228,"state":{"id":7,"description":"Change request is created but not queued yet","name":"Created","object":"option"},"object":"job_history"},{"id":3303239,"updatedon":1303920388296,"state":{"id":1,"description":"Change request is new to the system.","name":"Queued","object":"option"},"object":"job_history"},{"id":3303240,"updatedon":1303920388865,"state":{"id":2,"description":"Change request is is transient state...Processing.","name":"Processing","object":"option"},"object":"job_history"},{"id":3303242,"updatedon":1303920399444,"state":{"id":3,"description":"Change request has succeeded.","name":"Succeeded","object":"option"},"object":"job_history"}],"detail":{"ram":"512MB","description":null,"name":"test","image":"ubuntu_10_04_LTS_32_base","type":"virtual_server","ip":"173.204.231.68"},"currentstate":{"id":3,"description":"Change request has succeeded.","name":"Succeeded","object":"option"},"lastupdatedon":1303920399439,"attempts":1,"objecttype":{"id":1,"description":null,"name":"VirtualServer","object":"option"},"owner":"enrico@zend.com","command":{"id":7,"description":"Delete Virtual Server","name":"DeleteVirtualServer","object":"option"},"datacenter":{"id":2,"description":"US East 1 Datacenter","name":"US-East-1","object":"option"},"createdon":1303920388224,"object":"job"},{"id":583287,"history":[{"id":3303235,"updatedon":1303920382613,"state":{"id":7,"description":"Change request is created but not queued yet","name":"Created","object":"option"},"object":"job_history"},{"id":3303236,"updatedon":1303920382722,"state":{"id":1,"description":"Change request is new to the system.","name":"Queued","object":"option"},"object":"job_history"},{"id":3303237,"updatedon":1303920383157,"state":{"id":2,"description":"Change request is is transient state...Processing.","name":"Processing","object":"option"},"object":"job_history"},{"id":3303241,"updatedon":1303920393624,"state":{"id":3,"description":"Change request has succeeded.","name":"Succeeded","object":"option"},"object":"job_history"}],"detail":{"ram":"512MB","description":null,"name":"test2","image":"ubuntu_10_04_LTS_32_base","type":"virtual_server","ip":"173.204.231.69"},"currentstate":{"id":3,"description":"Change request has succeeded.","name":"Succeeded","object":"option"},"lastupdatedon":1303920393619,"attempts":1,"objecttype":{"id":1,"description":null,"name":"VirtualServer","object":"option"},"owner":"enrico@zend.com","command":{"id":7,"description":"Delete Virtual Server","name":"DeleteVirtualServer","object":"option"},"datacenter":{"id":2,"description":"US East 1 Datacenter","name":"US-East-1","object":"option"},"createdon":1303920382609,"object":"job"}]} \ No newline at end of file From 4f4165aa1cb49629db6325209962f14b131a107f Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 27 Apr 2011 18:43:42 +0200 Subject: [PATCH 16/45] Deleted old json response --- tests/Zend/Service/GoGrid/_files/job_get.json | 90 ---------- .../Zend/Service/GoGrid/_files/job_list.json | 168 ------------------ 2 files changed, 258 deletions(-) delete mode 100644 tests/Zend/Service/GoGrid/_files/job_get.json delete mode 100644 tests/Zend/Service/GoGrid/_files/job_list.json diff --git a/tests/Zend/Service/GoGrid/_files/job_get.json b/tests/Zend/Service/GoGrid/_files/job_get.json deleted file mode 100644 index 5b54daa3dbf..00000000000 --- a/tests/Zend/Service/GoGrid/_files/job_get.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "list": [ - { - "attempts": 1, - "command": { - "description": "Create Virtual Server", - "id": 6, - "name": "CreateVirtualServer", - "object": "option" - }, - "createdon": 1231007644632, - "currentstate": { - "description": "Change request has succeeded.", - "id": 3, - "name": "Succeeded", - "object": "option" - }, - "datacenter": { - "description": "US East 1 Datacenter", - "id": 2, - "name": "US-East-1", - "object": "option" - }, - "detail": { - "description": "tests", - "image": "w2k3_64_iis_asp_mssql2k5xp", - "ip": "208.113.76.34", - "name": "test 2", - "type": "virtual_server" - }, - "history": [ - { - "id": 10242, - "state": { - "description": "Change request is created but not queued yet", - "id": 7, - "name": "Created", - "object": "option" - } - }, - { - "id": 10243, - "state": { - "description": "Change request is new to the system.", - "id": 1, - "name": "Queued", - "object": "option" - } - }, - { - "id": 10244, - "state": { - "description": "Change request is is transient state...Processing.", - "id": 2, - "name": "Processing", - "object": "option" - } - }, - { - "id": 10251, - "state": { - "description": "Change request has succeeded.", - "id": 3, - "name": "Succeeded", - "object": "option" - }, - "updatedon": 1231008346317 - } - ], - "id": 60531, - "lastupdatedon": 1231008346317, - "object": "job", - "objecttype": { - "description": null, - "id": 1, - "name": "VirtualServer", - "object": "option" - }, - "owner": "ankit@gogrid.com" - } - ], - "method": "/grid/job/get", - "status": "success", - "summary": { - "numpages": 1, - "returned": 1, - "start": 0, - "total": 1 - } -} \ No newline at end of file diff --git a/tests/Zend/Service/GoGrid/_files/job_list.json b/tests/Zend/Service/GoGrid/_files/job_list.json deleted file mode 100644 index b14b65135af..00000000000 --- a/tests/Zend/Service/GoGrid/_files/job_list.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "list": [ - { - "attempts": 1, - "command": { - "description": "Create Virtual Server", - "id": 6, - "name": "CreateVirtualServer", - "object": "option" - }, - "createdon": 1231007644632, - "currentstate": { - "description": "Change request has succeeded.", - "id": 3, - "name": "Succeeded", - "object": "option" - }, - "datacenter": { - "description": "US East 1 Datacenter", - "id": 2, - "name": "US-East-1", - "object": "option" - }, - "detail": { - "description": "tests", - "image": "w2k3_64_iis_asp_mssql2k5xp", - "ip": "208.113.76.34", - "name": "test 2", - "type": "virtual_server" - }, - "history": [ - { - "id": 10242, - "state": { - "description": "Change request is created but not queued yet", - "id": 7, - "name": "Created", - "object": "option" - } - }, - { - "id": 10243, - "state": { - "description": "Change request is new to the system.", - "id": 1, - "name": "Queued", - "object": "option" - } - }, - { - "id": 10244, - "state": { - "description": "Change request is is transient state...Processing.", - "id": 2, - "name": "Processing", - "object": "option" - } - }, - { - "id": 10251, - "state": { - "description": "Change request has succeeded.", - "id": 3, - "name": "Succeeded", - "object": "option" - }, - "updatedon": 1231008346317 - } - ], - "id": 60531, - "lastupdatedon": 1231008346317, - "object": "job", - "objecttype": { - "description": null, - "id": 1, - "name": "VirtualServer", - "object": "option" - }, - "owner": "ankit@gogrid.com" - }, - { - "attempts": 1, - "command": { - "description": "Create Virtual Server", - "id": 6, - "name": "CreateVirtualServer", - "object": "option" - }, - "createdon": 1231007606467, - "currentstate": { - "description": "Change request has succeeded.", - "id": 3, - "name": "Succeeded", - "object": "option" - }, - "datacenter": { - "description": "US West 1 Datacenter", - "id": 1, - "name": "US-West-1", - "object": "option" - }, - "detail": { - "description": "tetsss", - "image": "centos51_64_lamp", - "ip": "208.113.76.32", - "name": "test", - "type": "virtual_server" - }, - "history": [ - { - "id": 10239, - "state": { - "description": "Change request is created but not queued yet", - "id": 7, - "name": "Created", - "object": "option" - } - }, - { - "id": 10240, - "state": { - "description": "Change request is new to the system.", - "id": 1, - "name": "Queued", - "object": "option" - } - }, - { - "id": 10241, - "state": { - "description": "Change request is is transient state...Processing.", - "id": 2, - "name": "Processing", - "object": "option" - } - }, - { - "id": 10246, - "state": { - "description": "Change request has succeeded.", - "id": 3, - "name": "Succeeded", - "object": "option" - }, - "updatedon": 1231008211834 - } - ], - "id": 60530, - "lastupdatedon": 1231008211834, - "object": "job", - "objecttype": { - "description": null, - "id": 1, - "name": "VirtualServer", - "object": "option" - }, - "owner": "ankit@gogrid.com" - } - ], - "method": "/grid/job/list", - "status": "success", - "summary": { - "numpages": 1, - "returned": 7, - "start": 0, - "total": 7 - } -} \ No newline at end of file From 51e7849879dc8d296ccaf9acc35aaa6fa2dee562 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Thu, 5 May 2011 13:17:25 +0200 Subject: [PATCH 17/45] first Rackspace prototype --- library/Zend/Service/Rackspace/Exception.php | 34 +++ .../Exception/InvalidArgumentException.php | 39 +++ .../Exception/OutOfBoundsException.php | 39 +++ library/Zend/Service/Rackspace/Files.php | 177 ++++++++++++++ .../Service/Rackspace/Files/Container.php | 130 ++++++++++ .../Service/Rackspace/Files/ContainerList.php | 225 ++++++++++++++++++ .../Zend/Service/Rackspace/Files/Object.php | 81 +++++++ .../Service/Rackspace/Files/ObjectList.php | 225 ++++++++++++++++++ library/Zend/Service/Rackspace/Rackspace.php | 212 +++++++++++++++++ 9 files changed, 1162 insertions(+) create mode 100644 library/Zend/Service/Rackspace/Exception.php create mode 100644 library/Zend/Service/Rackspace/Exception/InvalidArgumentException.php create mode 100644 library/Zend/Service/Rackspace/Exception/OutOfBoundsException.php create mode 100644 library/Zend/Service/Rackspace/Files.php create mode 100644 library/Zend/Service/Rackspace/Files/Container.php create mode 100644 library/Zend/Service/Rackspace/Files/ContainerList.php create mode 100644 library/Zend/Service/Rackspace/Files/Object.php create mode 100644 library/Zend/Service/Rackspace/Files/ObjectList.php create mode 100644 library/Zend/Service/Rackspace/Rackspace.php diff --git a/library/Zend/Service/Rackspace/Exception.php b/library/Zend/Service/Rackspace/Exception.php new file mode 100644 index 00000000000..3f7e790d4a6 --- /dev/null +++ b/library/Zend/Service/Rackspace/Exception.php @@ -0,0 +1,34 @@ +_countContainers)) { + $this->getInfoContainers(); + } + return $this->_countContainers; + } + /** + * Return the size in bytes of all the containers + * + * @return integer + */ + public function getSizeContainers() + { + if (!isset($this->_sizeContainers)) { + $this->getInfoContainers(); + } + return $this->_sizeContainers; + } + /** + * Return the count of objects contained in all the containers + * + * @return integer + */ + public function getCountObjects() + { + if (!isset($this->_countObjects)) { + $this->getInfoContainers(); + } + return $this->_countObjects; + } + /** + * Get all the containers + * + * @param integer $limit + * @param string $marker + * @return ContainerList + */ + public function getContainers($limit=0,$marker=null) + { + $get=array( + 'format' => self::API_FORMAT + ); + if (!empty($limit)) { + $get['limit']= $limit; + } + if (!empty($marker)) { + $get['marker']= $marker; + } + $headers=array( + self::AUTH_TOKEN => $this->getToken() + ); + $result= $this->_httpCall($this->getStorageUrl(),HttpClient::GET,$headers,$get); + if ($result->isSuccessful()) { + return new ContainerList($this,json_decode($result->getBody(),true)); + } + return false; + } + /** + * Get the metadata information of the accounts: + * - total count containers + * - size in bytes of all the containers + * - total count objects in all the containers + * + * @return array|boolean + */ + public function getInfoContainers() + { + $get=array( + 'format' => self::API_FORMAT + ); + $headers=array( + self::AUTH_TOKEN => $this->getToken() + ); + $result= $this->_httpCall($this->getStorageUrl(),HttpClient::HEAD,$headers,$get); + if ($result->isSuccessful()) { + $this->_countContainers= $result->getHeader(self::ACCOUNT_CONTAINER_COUNT); + $this->_sizeContainers= $result->getHeader(self::ACCOUNT_BYTES_USED); + $this->_countObjects= $result->getHeader(self::CONTAINER_OBJ_COUNT); + $output= array( + 'tot_containers' => $this->_countContainers, + 'size_containers' => $this->_sizeContainers, + 'tot_objects' => $this->_countObjects + ); + return $output; + } + return false; + } + /** + * Get all the files of a container + * + * @param string $container + */ + public function getFiles($container) + { + if (empty($container)) { + throw new InvalidArgumentException("You must pass the container name"); + } + $get=array( + 'format' => self::API_FORMAT + ); + $headers=array( + self::AUTH_TOKEN => $this->getToken() + ); + $container= urlencode($container); + $result= $this->_httpCall($this->getStorageUrl().'/'.$container,HttpClient::GET,$headers,$get); + if ($result->isSuccessful()) { + return new ObjectList($this,json_decode($result->getBody(),true)); + } + return false; + } + public function createContainer($container) + { + + } + public function deleteContainer($container) + { + + } + public function getMetadataContainer($container) + { + + } + public function getObject($container) + { + + } +} \ No newline at end of file diff --git a/library/Zend/Service/Rackspace/Files/Container.php b/library/Zend/Service/Rackspace/Files/Container.php new file mode 100644 index 00000000000..ca873385393 --- /dev/null +++ b/library/Zend/Service/Rackspace/Files/Container.php @@ -0,0 +1,130 @@ +_service= $service; + $this->_name= $data['name']; + $this->_objectCount= $data['count']; + $this->_size= $data['bytes']; + } + /** + * Get the name of the container + * + * @return string + */ + public function getName() + { + return $this->_name; + } + /** + * Get the size in bytes of the container + * + * @return integer + */ + public function getSize() + { + return $this->_size; + } + /** + * Get the total count of objects in the container + * + * @return integer + */ + public function getObjectCount() { + return $this->_objectCount; + } + + public function getMetadata() + { + + } + /** + * Get the files of the container + * + * @return Zend\Service\Rackspace\Files\ObjectList + */ + public function getFiles() + { + return $this->_service->getFiles($this->getName()); + } + public function addFile($file) + { + + } + public function deleteFile($file) + { + + } +} \ No newline at end of file diff --git a/library/Zend/Service/Rackspace/Files/ContainerList.php b/library/Zend/Service/Rackspace/Files/ContainerList.php new file mode 100644 index 00000000000..a12834e23f4 --- /dev/null +++ b/library/Zend/Service/Rackspace/Files/ContainerList.php @@ -0,0 +1,225 @@ +_service= $service; + $this->_constructFromArray($list); + } + /** + * Transforms the Array to array of container + * + * @param array $list + * @return void + */ + private function _constructFromArray(array $list) + { + foreach ($list as $container) { + $this->_addObject(new Container($this->_service,$container)); + } + } + /** + * Add an object + * + * @param Zend\Service\Rackspace\Files\Container $obj + * @return Zend\Service\Rackspace\Files\ContainerList + */ + protected function _addObject (Container $obj) + { + $this->_objects[] = $obj; + return $this; + } + /** + * Return number of servers + * + * Implement Countable::count() + * + * @return int + */ + public function count() + { + return count($this->_objects); + } + /** + * Return the current element + * + * Implement Iterator::current() + * + * @return Zend\Service\Rackspace\Files\Container + */ + public function current() + { + return $this->_objects[$this->_iteratorKey]; + } + /** + * Return the key of the current element + * + * Implement Iterator::key() + * + * @return int + */ + public function key() + { + return $this->_iteratorKey; + } + /** + * Move forward to next element + * + * Implement Iterator::next() + * + * @return void + */ + public function next() + { + $this->_iteratorKey += 1; + } + /** + * Rewind the Iterator to the first element + * + * Implement Iterator::rewind() + * + * @return void + */ + public function rewind() + { + $this->_iteratorKey = 0; + } + /** + * Check if there is a current element after calls to rewind() or next() + * + * Implement Iterator::valid() + * + * @return bool + */ + public function valid() + { + $numItems = $this->count(); + if ($numItems > 0 && $this->_iteratorKey < $numItems) { + return true; + } else { + return false; + } + } + /** + * Whether the offset exists + * + * Implement ArrayAccess::offsetExists() + * + * @param int $offset + * @return bool + */ + public function offsetExists($offset) + { + return ($offset < $this->count()); + } + /** + * Return value at given offset + * + * Implement ArrayAccess::offsetGet() + * + * @param int $offset + * @throws OutOfBoundsException + * @return Zend\Service\Rackspace\Files\Container + */ + public function offsetGet($offset) + { + if ($this->offsetExists($offset)) { + return $this->_objects[$offset]; + } else { + throw new OutOfBoundsException('Illegal index'); + } + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetSet() + * + * @param int $offset + * @param string $value + * @throws Zend\Service\Rackspace\Exception + */ + public function offsetSet($offset, $value) + { + throw new Exception('You are trying to set read-only property'); + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetUnset() + * + * @param int $offset + * @throws Zend\Service\Rackspace\Exception + */ + public function offsetUnset($offset) + { + throw new Exception('You are trying to unset read-only property'); + } +} diff --git a/library/Zend/Service/Rackspace/Files/Object.php b/library/Zend/Service/Rackspace/Files/Object.php new file mode 100644 index 00000000000..4f149991361 --- /dev/null +++ b/library/Zend/Service/Rackspace/Files/Object.php @@ -0,0 +1,81 @@ +_name; + } + public function getHash() { + return $this->_hash; + } + public function getSize() { + return $this->_size; + } + public function getContentType() { + return $this->_contentType; + } + public function getLastModified() { + return $this->_lastModified; + } + + public function __construct(RackspaceFiles $service,$data) + { + if (!($service instanceof RackspaceFiles) || !is_array($data)) { + throw new InvalidArgumentException("You must pass a RackspaceFiles and an array"); + } + if (!array_key_exists('name', $data)) { + throw new InvalidArgumentException("You must pass the name of the object in the array (name)"); + } + if (!array_key_exists('hash', $data)) { + throw new InvalidArgumentException("You must pass the hash of the object in the array (hash)"); + } + if (!array_key_exists('bytes', $data)) { + throw new InvalidArgumentException("You must pass the byte size of the object in the array (bytes)"); + } + if (!array_key_exists('content_type', $data)) { + throw new InvalidArgumentException("You must pass the content type of the object in the array (content_type)"); + } + if (!array_key_exists('last_modified', $data)) { + throw new InvalidArgumentException("You must pass the last modified data of the object in the array (last_modified)"); + } + $this->_name= $data['name']; + $this->_hash= $data['hash']; + $this->_size= $data['bytes']; + $this->_contentType= $data['content_type']; + $this->_lastModified= $data['last_modified']; + $this->_service= $service; + } + public function getMetadata() { + + } +} diff --git a/library/Zend/Service/Rackspace/Files/ObjectList.php b/library/Zend/Service/Rackspace/Files/ObjectList.php new file mode 100644 index 00000000000..8755adc69bf --- /dev/null +++ b/library/Zend/Service/Rackspace/Files/ObjectList.php @@ -0,0 +1,225 @@ +_service= $service; + $this->_constructFromArray($list); + } + /** + * Transforms the Array to array of container + * + * @param array $list + * @return void + */ + private function _constructFromArray(array $list) + { + foreach ($list as $obj) { + $this->_addObject(new Object($this->_service,$obj)); + } + } + /** + * Add an object + * + * @param Zend\Service\Rackspace\Files\Object $obj + * @return Zend\Service\Rackspace\Files\ObjectList + */ + protected function _addObject (Object $obj) + { + $this->_objects[] = $obj; + return $this; + } + /** + * Return number of servers + * + * Implement Countable::count() + * + * @return int + */ + public function count() + { + return count($this->_objects); + } + /** + * Return the current element + * + * Implement Iterator::current() + * + * @return Zend\Service\Rackspace\Files\Object + */ + public function current() + { + return $this->_objects[$this->_iteratorKey]; + } + /** + * Return the key of the current element + * + * Implement Iterator::key() + * + * @return int + */ + public function key() + { + return $this->_iteratorKey; + } + /** + * Move forward to next element + * + * Implement Iterator::next() + * + * @return void + */ + public function next() + { + $this->_iteratorKey += 1; + } + /** + * Rewind the Iterator to the first element + * + * Implement Iterator::rewind() + * + * @return void + */ + public function rewind() + { + $this->_iteratorKey = 0; + } + /** + * Check if there is a current element after calls to rewind() or next() + * + * Implement Iterator::valid() + * + * @return bool + */ + public function valid() + { + $numItems = $this->count(); + if ($numItems > 0 && $this->_iteratorKey < $numItems) { + return true; + } else { + return false; + } + } + /** + * Whether the offset exists + * + * Implement ArrayAccess::offsetExists() + * + * @param int $offset + * @return bool + */ + public function offsetExists($offset) + { + return ($offset < $this->count()); + } + /** + * Return value at given offset + * + * Implement ArrayAccess::offsetGet() + * + * @param int $offset + * @throws OutOfBoundsException + * @return Zend\Service\Rackspace\Files\Object + */ + public function offsetGet($offset) + { + if ($this->offsetExists($offset)) { + return $this->_objects[$offset]; + } else { + throw new OutOfBoundsException('Illegal index'); + } + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetSet() + * + * @param int $offset + * @param string $value + * @throws Zend\Service\Rackspace\Exception + */ + public function offsetSet($offset, $value) + { + throw new Exception('You are trying to set read-only property'); + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetUnset() + * + * @param int $offset + * @throws Zend\Service\Rackspace\Exception + */ + public function offsetUnset($offset) + { + throw new Exception('You are trying to unset read-only property'); + } +} diff --git a/library/Zend/Service/Rackspace/Rackspace.php b/library/Zend/Service/Rackspace/Rackspace.php new file mode 100644 index 00000000000..3c8aa327451 --- /dev/null +++ b/library/Zend/Service/Rackspace/Rackspace.php @@ -0,0 +1,212 @@ +setUser($user); + $this->setKey($key); + $this->setAuthUrl($authUrl); + } + + public function getUser() + { + return $this->_user; + } + + public function getKey() + { + return $this->_key; + } + + public function getAuthUrl() + { + return $this->_authUrl; + } + public function getStorageUrl() { + if (empty($this->_storageUrl)) { + if (!$this->authenticate()) { + return false; + } + } + return $this->_storageUrl; + } + public function getCdnUrl() { + if (empty($this->_cdnUrl)) { + if (!$this->authenticate()) { + return false; + } + } + return $this->_cdnUrl(); + } + public function setUser($user) + { + if (!empty($user)) { + $this->_user = $user; + } + } + public function setKey($key) + { + if (!empty($key)) { + $this->_key = $key; + } + } + public function setAuthUrl($url) + { + if (!empty($url) && in_array($url, array(self::US_AUTH_URL, self::UK_AUTH_URL))) { + $this->_authUrl = $url; + } + } + public function getToken() + { + if (empty($this->_token)) { + if (!$this->authenticate()) { + return false; + } + } + return $this->_token; + } + public function getErrorMsg() { + return $this->_errorMsg; + } + /** + * get the HttpClient instance + * + * @return Zend\Http\Client + */ + public function getHttpClient() + { + if (empty($this->_httpClient)) { + $this->_httpClient = new HttpClient(); + } + return $this->_httpClient; + } + /** + * HTTP call + * + * @param string $url + * @param string $method + * @param array $headers + * @param array $get + * @param string $body + * @return Zend\Http\Response + */ + protected function _httpCall($url,$method,$headers,$get=null,$body=null) + { + $client = $this->getHttpClient(); + $client->resetParameters(true); + $client->setHeaders($headers); + $client->setMethod($method); + if (!empty($get)) { + $client->setParameterGet($get); + } + if (!empty($body)) { + $client->setRawData($body); + } + $client->setUri($url); + return $client->request(); + } + /** + * Authentication + * + * @return boolean + */ + public function authenticate() + { + $headers= array ( + self::AUTH_USER_HEADER => $this->_user, + self::AUTH_KEY_HEADER => $this->_key + ); + $result= $this->_httpCall($this->_authUrl,HttpClient::GET, $headers); + if ($result->getStatus()==204) { + $this->_token= $result->getHeader(self::AUTH_TOKEN); + $this->_storageUrl= $result->getHeader(self::STORAGE_URL); + $this->_cdnUrl= $result->getHeader(self::CDNM_URL); + return true; + } else { + $this->_errorMsg= $result->getBody(); + } + return false; + } +} \ No newline at end of file From 319ddc712c76adf11d87d59ca16fd4f9cc753298 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Fri, 6 May 2011 19:12:13 +0200 Subject: [PATCH 18/45] Added the create/delete containers API plus code refactoring --- library/Zend/Service/Rackspace/Files.php | 110 ++++++++++++------- library/Zend/Service/Rackspace/Rackspace.php | 31 +++++- 2 files changed, 99 insertions(+), 42 deletions(-) diff --git a/library/Zend/Service/Rackspace/Files.php b/library/Zend/Service/Rackspace/Files.php index 377717f363b..7dda4d2fd99 100644 --- a/library/Zend/Service/Rackspace/Files.php +++ b/library/Zend/Service/Rackspace/Files.php @@ -22,6 +22,7 @@ namespace Zend\Service\Rackspace; use Zend\Service\Rackspace\Rackspace as RackspaceAbstract, + Zend\Service\Rackspace\Files\Container, Zend\Service\Rackspace\Files\ContainerList, Zend\Service\Rackspace\Files\ObjectList, Zend\Http\Client as HttpClient, @@ -81,25 +82,12 @@ public function getCountObjects() /** * Get all the containers * - * @param integer $limit - * @param string $marker - * @return ContainerList + * @param array $options + * @return ContainerList|boolean */ - public function getContainers($limit=0,$marker=null) + public function getContainers($options=array()) { - $get=array( - 'format' => self::API_FORMAT - ); - if (!empty($limit)) { - $get['limit']= $limit; - } - if (!empty($marker)) { - $get['marker']= $marker; - } - $headers=array( - self::AUTH_TOKEN => $this->getToken() - ); - $result= $this->_httpCall($this->getStorageUrl(),HttpClient::GET,$headers,$get); + $result= $this->_httpCall($this->getStorageUrl(),HttpClient::GET,null,$options); if ($result->isSuccessful()) { return new ContainerList($this,json_decode($result->getBody(),true)); } @@ -115,13 +103,7 @@ public function getContainers($limit=0,$marker=null) */ public function getInfoContainers() { - $get=array( - 'format' => self::API_FORMAT - ); - $headers=array( - self::AUTH_TOKEN => $this->getToken() - ); - $result= $this->_httpCall($this->getStorageUrl(),HttpClient::HEAD,$headers,$get); + $result= $this->_httpCall($this->getStorageUrl(),HttpClient::HEAD); if ($result->isSuccessful()) { $this->_countContainers= $result->getHeader(self::ACCOUNT_CONTAINER_COUNT); $this->_sizeContainers= $result->getHeader(self::ACCOUNT_BYTES_USED); @@ -139,32 +121,86 @@ public function getInfoContainers() * Get all the files of a container * * @param string $container + * @param array $options + * @return ContainerObject|boolean */ - public function getFiles($container) + public function getFiles($container,$options=array()) { if (empty($container)) { - throw new InvalidArgumentException("You must pass the container name"); + throw new InvalidArgumentException("You must specify the container name"); } - $get=array( - 'format' => self::API_FORMAT - ); - $headers=array( - self::AUTH_TOKEN => $this->getToken() - ); - $container= urlencode($container); - $result= $this->_httpCall($this->getStorageUrl().'/'.$container,HttpClient::GET,$headers,$get); + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container),HttpClient::GET,null,$options); if ($result->isSuccessful()) { return new ObjectList($this,json_decode($result->getBody(),true)); } return false; } - public function createContainer($container) + /** + * Create a container + * + * @param string $container + * @param array $metadata + * @return boolean + */ + public function createContainer($container,$metadata=array()) { - + if (empty($container)) { + throw new InvalidArgumentException("You must specify the container name"); + } + $headers=array(); + if (!empty($metadata)) { + foreach ($metadata as $key => $value) { + $headers[self::METADATA_CONTAINER_HEADER.rawurlencode($key)]= rawurlencode($value); + } + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container),HttpClient::PUT,$headers); + $status= $result->getStatus(); + switch ($status) { + case '201': // break intentionally omitted + $data= array( + 'name' => $container, + 'count' => 0, + 'bytes' => 0 + ); + return new Container($this,$data); + case '202': + $this->_errorMsg= 'The container already exists'; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $result->getStatus(); + return false; } + /** + * Delete a container (only if it's empty) + * + * @param sting $container + * @return boolean + */ public function deleteContainer($container) { - + if (empty($container)) { + throw new InvalidArgumentException("You must specify the container name"); + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container),HttpClient::DELETE); + $status= $result->getStatus(); + switch ($status) { + case '204': // break intentionally omitted + return true; + case '409': + $this->_errorMsg= 'The container is not empty, I cannot delete it.'; + break; + case '404': + $this->_errorMsg= 'The container was not found.'; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $result->getStatus(); + return false; } public function getMetadataContainer($container) { diff --git a/library/Zend/Service/Rackspace/Rackspace.php b/library/Zend/Service/Rackspace/Rackspace.php index 3c8aa327451..2a6f4644de0 100644 --- a/library/Zend/Service/Rackspace/Rackspace.php +++ b/library/Zend/Service/Rackspace/Rackspace.php @@ -28,13 +28,15 @@ abstract class Rackspace { const US_AUTH_URL= 'https://auth.api.rackspacecloud.com/v1.0'; const UK_AUTH_URL= 'https://lon.auth.api.rackspacecloud.com/v1.0'; + const API_FORMAT= 'json'; const USER_AGENT= 'Zend\Service\Rackspace'; const ACCOUNT_CONTAINER_COUNT= "X-account-container-count"; const ACCOUNT_BYTES_USED= "X-account-bytes-used"; const CONTAINER_OBJ_COUNT= "X-account-object-count"; const CONTAINER_BYTES_USE= "X-Container-Bytes-Used"; - const METADATA_HEADER= "X-Object-Meta-"; - const MANIFEST_HEADER= "X-Object-Manifest"; + const METADATA_OBJECT_HEADER= "X-Object-Meta-"; + const METADATA_CONTAINER_HEADER= "X-Container-Meta-"; + const MANIFEST_OBJECT_HEADER= "X-Object-Manifest"; const CDN_URI= "X-CDN-URI"; const CDN_SSL_URI= "X-CDN-SSL-URI"; const CDN_ENABLED= "X-CDN-Enabled"; @@ -61,6 +63,7 @@ abstract class Rackspace */ protected $_httpClient; protected $_errorMsg; + protected $_errorStatus; protected $_storageUrl; protected $_cdnUrl; @@ -147,9 +150,22 @@ public function getToken() } return $this->_token; } + /** + * Get the error msg of the last REST call + * + * @return string + */ public function getErrorMsg() { return $this->_errorMsg; } + /** + * Get the error status of the last REST call + * + * @return strig + */ + public function getErrorStatus() { + return $this->_errorStatus; + } /** * get the HttpClient instance * @@ -172,15 +188,19 @@ public function getHttpClient() * @param string $body * @return Zend\Http\Response */ - protected function _httpCall($url,$method,$headers,$get=null,$body=null) + protected function _httpCall($url,$method,$headers=array(),$get=array(),$body=null) { $client = $this->getHttpClient(); $client->resetParameters(true); + if (!array_key_exists(self::AUTH_USER_HEADER, $headers)) { + $headers[self::AUTH_TOKEN]= $this->getToken(); + } $client->setHeaders($headers); $client->setMethod($method); - if (!empty($get)) { - $client->setParameterGet($get); + if (!array_key_exists('format', $get)) { + $get['format']= self::API_FORMAT; } + $client->setParameterGet($get); if (!empty($body)) { $client->setRawData($body); } @@ -206,6 +226,7 @@ public function authenticate() return true; } else { $this->_errorMsg= $result->getBody(); + $this->_errorStatus= $result->getStatus(); } return false; } From faa0e036ed8b72f9af440cd7b885cd95a7d16839 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 11 May 2011 12:11:04 +0200 Subject: [PATCH 19/45] Comlpeted the first prototype of Cloud Files --- library/Zend/Service/Rackspace/Files.php | 514 +++++++++++++++++- .../Service/Rackspace/Files/Container.php | 371 ++++++++++++- .../Zend/Service/Rackspace/Files/Object.php | 219 +++++++- .../Service/Rackspace/Files/ObjectList.php | 20 +- library/Zend/Service/Rackspace/Rackspace.php | 134 ++++- 5 files changed, 1162 insertions(+), 96 deletions(-) diff --git a/library/Zend/Service/Rackspace/Files.php b/library/Zend/Service/Rackspace/Files.php index 7dda4d2fd99..79b10b95979 100644 --- a/library/Zend/Service/Rackspace/Files.php +++ b/library/Zend/Service/Rackspace/Files.php @@ -24,13 +24,51 @@ use Zend\Service\Rackspace\Rackspace as RackspaceAbstract, Zend\Service\Rackspace\Files\Container, Zend\Service\Rackspace\Files\ContainerList, + Zend\Service\Rackspace\Files\Object, Zend\Service\Rackspace\Files\ObjectList, Zend\Http\Client as HttpClient, Zend\Service\Rackspace\Exception\InvalidArgumentException; class Files extends RackspaceAbstract { - const API_FORMAT= 'json'; + const ERROR_CONTAINER_NOT_EMPTY= 'The container is not empty, I cannot delete it.'; + const ERROR_CONTAINER_NOT_FOUND= 'The container was not found.'; + const ERROR_OBJECT_NOT_FOUND= 'The object was not found.'; + const ERROR_OBJECT_MISSING_PARAM= 'Missing Content-Length or Content-Type header in the request'; + const ERROR_OBJECT_CHECKSUM= 'Checksum of the file content failed'; + const ERROR_CONTAINER_EXIST= 'The container already exists'; + const ERROR_PARAM_NO_NAME_CONTAINER= 'You must specify the container name'; + const ERROR_PARAM_NO_NAME_OBJECT= 'You must specify the object name'; + const ERROR_PARAM_NO_FILE= 'You must specify the content of the file'; + const ERROR_PARAM_NO_NAME_SOURCE_CONTAINER= 'You must specify the source container name'; + const ERROR_PARAM_NO_NAME_SOURCE_OBJECT= 'You must specify the source object name'; + const ERROR_PARAM_NO_NAME_DEST_CONTAINER= 'You must specify the destination container name'; + const ERROR_PARAM_NO_NAME_DEST_OBJECT= 'You must specify the destination object name'; + const ERROR_CDN_TTL_OUT_OF_RANGE= 'TTL must be a number in seconds, min is 900 sec and maximum is 1577836800 (50 years)'; + const ERROR_PARAM_UPDATE_CDN= 'You must specify at least one the parameters: ttl, cdn_enabled or log_retention'; + const HEADER_CONTENT_TYPE= 'Content-type'; + const HEADER_HASH= 'Etag'; + const HEADER_LAST_MODIFIED= 'Last-modified'; + const HEADER_CONTENT_LENGTH= 'Content-length'; + const HEADER_COPY_FROM= 'X-Copy-From'; + const METADATA_OBJECT_HEADER= "X-object-meta-"; + const METADATA_CONTAINER_HEADER= "X-container-meta-"; + const CDN_URI= "X-CDN-URI"; + const CDN_SSL_URI= "X-CDN-SSL-URI"; + const CDN_ENABLED= "X-CDN-Enabled"; + const CDN_LOG_RETENTION= "X-Log-Retention"; + const CDN_ACL_USER_AGENT= "X-User-Agent-ACL"; + const CDN_ACL_REFERRER= "X-Referrer-ACL"; + const CDN_TTL= "X-TTL"; + const CDN_TTL_MIN= 900; + const CDN_TTL_MAX= 1577836800; + const CDN_EMAIL= "X-Purge-Email"; + const ACCOUNT_CONTAINER_COUNT= "X-account-container-count"; + const ACCOUNT_BYTES_USED= "X-account-bytes-used"; + const ACCOUNT_OBJ_COUNT= "X-account-object-count"; + const CONTAINER_OBJ_COUNT= "X-container-object-count"; + const CONTAINER_BYTES_USE= "X-container-bytes-used"; + const MANIFEST_OBJECT_HEADER= "X-Object-Manifest"; /** * @var integer */ @@ -83,12 +121,30 @@ public function getCountObjects() * Get all the containers * * @param array $options - * @return ContainerList|boolean + * @return Zend\Service\Rackspace\Files\ContainerList|boolean */ public function getContainers($options=array()) { $result= $this->_httpCall($this->getStorageUrl(),HttpClient::GET,null,$options); if ($result->isSuccessful()) { + $this->_countContainers= $result->getHeader(self::ACCOUNT_CONTAINER_COUNT); + $this->_sizeContainers= $result->getHeader(self::ACCOUNT_BYTES_USED); + $this->_countObjects= $result->getHeader(self::ACCOUNT_OBJ_COUNT); + return new ContainerList($this,json_decode($result->getBody(),true)); + } + return false; + } + /** + * Get all the CDN containers + * + * @param array $options + * @return Zend\Service\Rackspace\Files\ContainerList|boolean + */ + public function getCdnContainers($options=array()) + { + $options['enabled_only']= true; + $result= $this->_httpCall($this->getCdnUrl(),HttpClient::GET,null,$options); + if ($result->isSuccessful()) { return new ContainerList($this,json_decode($result->getBody(),true)); } return false; @@ -107,7 +163,7 @@ public function getInfoContainers() if ($result->isSuccessful()) { $this->_countContainers= $result->getHeader(self::ACCOUNT_CONTAINER_COUNT); $this->_sizeContainers= $result->getHeader(self::ACCOUNT_BYTES_USED); - $this->_countObjects= $result->getHeader(self::CONTAINER_OBJ_COUNT); + $this->_countObjects= $result->getHeader(self::ACCOUNT_OBJ_COUNT); $output= array( 'tot_containers' => $this->_countContainers, 'size_containers' => $this->_sizeContainers, @@ -118,20 +174,20 @@ public function getInfoContainers() return false; } /** - * Get all the files of a container + * Get all the objects of a container * * @param string $container * @param array $options - * @return ContainerObject|boolean + * @return Zend\Service\Rackspace\Files\ObjectList|boolean */ - public function getFiles($container,$options=array()) + public function getObjects($container,$options=array()) { if (empty($container)) { - throw new InvalidArgumentException("You must specify the container name"); + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); } $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container),HttpClient::GET,null,$options); if ($result->isSuccessful()) { - return new ObjectList($this,json_decode($result->getBody(),true)); + return new ObjectList($this,json_decode($result->getBody(),true),$container); } return false; } @@ -140,12 +196,12 @@ public function getFiles($container,$options=array()) * * @param string $container * @param array $metadata - * @return boolean + * @return Zend\Service\Rackspace\Files\Container|boolean */ public function createContainer($container,$metadata=array()) { if (empty($container)) { - throw new InvalidArgumentException("You must specify the container name"); + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); } $headers=array(); if (!empty($metadata)) { @@ -160,17 +216,18 @@ public function createContainer($container,$metadata=array()) $data= array( 'name' => $container, 'count' => 0, - 'bytes' => 0 + 'bytes' => 0, + 'metadata' => $metadata ); return new Container($this,$data); case '202': - $this->_errorMsg= 'The container already exists'; + $this->_errorMsg= self::ERROR_CONTAINER_EXIST; break; default: $this->_errorMsg= $result->getBody(); break; } - $this->_errorStatus= $result->getStatus(); + $this->_errorStatus= $status; return false; } /** @@ -182,7 +239,7 @@ public function createContainer($container,$metadata=array()) public function deleteContainer($container) { if (empty($container)) { - throw new InvalidArgumentException("You must specify the container name"); + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); } $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container),HttpClient::DELETE); $status= $result->getStatus(); @@ -190,24 +247,441 @@ public function deleteContainer($container) case '204': // break intentionally omitted return true; case '409': - $this->_errorMsg= 'The container is not empty, I cannot delete it.'; + $this->_errorMsg= self::ERROR_CONTAINER_NOT_EMPTY; break; case '404': - $this->_errorMsg= 'The container was not found.'; + $this->_errorMsg= self::ERROR_CONTAINER_NOT_FOUND; break; default: $this->_errorMsg= $result->getBody(); break; } - $this->_errorStatus= $result->getStatus(); + $this->_errorStatus= $status; return false; } + /** + * Get the metadata of a container + * + * @param string $container + * @return array|boolean + */ public function getMetadataContainer($container) { - + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container),HttpClient::HEAD); + $status= $result->getStatus(); + switch ($status) { + case '204': // break intentionally omitted + $headers= $result->getHeaders(); + $count= strlen(self::METADATA_CONTAINER_HEADER); + $metadata= array(); + foreach ($headers as $key => $value) { + if (strpos($key,self::METADATA_CONTAINER_HEADER)!==false) { + $metadata[substr($key, $count)]= $value; + } + } + $data= array ( + 'name' => $container, + 'count' => $headers[self::CONTAINER_OBJ_COUNT], + 'bytes' => $headers[self::CONTAINER_BYTES_USE], + 'metadata' => $metadata + ); + return $data; + case '404': + $this->_errorMsg= self::ERROR_CONTAINER_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Get a container + * + * @param string $container + * @return Container|boolean + */ + public function getContainer($container) { + $result= $this->getMetadataContainer($container); + if (!empty($result)) { + return new Container($this,$result); + } + return false; } - public function getObject($container) + /** + * Get an object in a container + * + * @param string $container + * @param string $object + * @param array $headers + * @return Zend\Service\Rackspace\Files\Object|boolean + */ + public function getObject($container,$object,$headers=array()) { - + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + if (empty($object)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_OBJECT); + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container).'/'.rawurlencode($object),HttpClient::GET); + $status= $result->getStatus(); + switch ($status) { + case '200': // break intentionally omitted + $data= array( + 'name' => $object, + 'container' => $container, + 'hash' => $result->getHeader(self::HEADER_HASH), + 'bytes' => $result->getHeader(self::HEADER_CONTENT_LENGTH), + 'last_modified' => $result->getHeader(self::HEADER_LAST_MODIFIED), + 'content_type' => $result->getHeader(self::HEADER_CONTENT_TYPE), + 'file' => $result->getBody() + ); + return new Object($this,$data); + case '404': + $this->_errorMsg= self::ERROR_OBJECT_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Store an object in a container + * + * @param string $container + * @param string $object + * @param string $file + * @param array $metadata + * @return boolean + */ + public function storeObject($container,$object,$file,$metadata=array()) { + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + if (empty($object)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_OBJECT); + } + if (empty($file)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_FILE); + } + if (!empty($metadata) && is_array($metadata)) { + foreach ($metadata as $key => $value) { + $headers[self::METADATA_OBJECT_HEADER.$key]= $value; + } + } + $headers[self::HEADER_HASH]= md5($file); + $headers[self::HEADER_CONTENT_LENGTH]= strlen($file); + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container).'/'.rawurlencode($object),HttpClient::PUT,$headers,null,$file); + $status= $result->getStatus(); + switch ($status) { + case '201': // break intentionally omitted + return true; + case '412': + $this->_errorMsg= self::ERROR_OBJECT_MISSING_PARAM; + break; + case '422': + $this->_errorMsg= self::ERROR_OBJECT_CHECKSUM; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Delete an object in a container + * + * @param string $container + * @param string $object + * @return boolean + */ + public function deleteObject($container,$object) { + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + if (empty($object)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_OBJECT); + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container).'/'.rawurlencode($object),HttpClient::DELETE); + $status= $result->getStatus(); + switch ($status) { + case '204': // break intentionally omitted + return true; + case '404': + $this->_errorMsg= self::ERROR_OBJECT_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Copy an object from a container to another + * + * @param string $container_source + * @param string $obj_source + * @param string $container_dest + * @param string $obj_dest + * @param array $metadata + * @param string $content_type + * @return boolean + */ + public function copyObject($container_source,$obj_source,$container_dest,$obj_dest,$metadata=array(),$content_type=null) { + if (empty($container_source)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_SOURCE_CONTAINER); + } + if (empty($obj_source)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_SOURCE_OBJECT); + } + if (empty($container_dest)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_DEST_CONTAINER); + } + if (empty($obj_dest)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_DEST_OBJECT); + } + $headers= array( + self::HEADER_COPY_FROM => '/'.rawurlencode($container_source).'/'.rawurlencode($obj_source), + self::HEADER_CONTENT_LENGTH => 0 + ); + if (!empty($content_type)) { + $headers[self::HEADER_CONTENT_TYPE]= $content_type; + } + if (!empty($metadata) && is_array($metadata)) { + foreach ($metadata as $key => $value) { + $headers[self::METADATA_OBJECT_HEADER.$key]= $value; + } + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container_dest).'/'.rawurlencode($obj_dest),HttpClient::PUT,$headers); + $status= $result->getStatus(); + var_dump($status); + switch ($status) { + case '201': // break intentionally omitted + return true; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Get the metadata of an object + * + * @param string $container + * @param string $object + * @return array|boolean + */ + public function getMetadataObject($container,$object) { + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + if (empty($object)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_OBJECT); + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container).'/'.rawurlencode($object),HttpClient::HEAD); + $status= $result->getStatus(); + switch ($status) { + case '200': // break intentionally omitted + $headers= $result->getHeaders(); + $count= strlen(self::METADATA_OBJECT_HEADER); + $metadata= array(); + foreach ($headers as $key => $value) { + if (strpos($key,self::METADATA_OBJECT_HEADER)!==false) { + $metadata[substr($key, $count)]= $value; + } + } + $data= array ( + 'name' => $object, + 'container' => $container, + 'hash' => $headers[self::HEADER_HASH], + 'bytes' => $headers[self::HEADER_CONTENT_LENGTH], + 'content_type' => $headers[self::HEADER_CONTENT_TYPE], + 'last_modified' => $headers[self::HEADER_LAST_MODIFIED], + 'metadata' => $metadata + ); + return $data; + case '404': + $this->_errorMsg= self::ERROR_OBJECT_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Set the metadata of a object in a container + * The old metadata values are replaced with the new one + * + * @param string $container + * @param string $object + * @param array $metadata + * @return boolean + */ + public function setMetadataObject($container,$object,$metadata=array()) + { + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + if (empty($object)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_OBJECT); + } + $headers=array(); + if (!empty($metadata) && is_array($metadata)) { + foreach ($metadata as $key => $value) { + $headers[self::METADATA_OBJECT_HEADER.$key]= $value; + } + } + $result= $this->_httpCall($this->getStorageUrl().'/'.rawurlencode($container).'/'.rawurlencode($object),HttpClient::POST,$headers); + $status= $result->getStatus(); + switch ($status) { + case '202': // break intentionally omitted + return true; + case '404': + $this->_errorMsg= self::ERROR_OBJECT_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Enable the CDN for a container + * + * @param string $container + * @param integer $ttl + * @return array|boolean + */ + public function enableCdnContainer ($container,$ttl=self::CDN_TTL_MIN) { + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + $headers=array(); + if (is_numeric($ttl) && ($ttl>=self::CDN_TTL_MIN) && ($ttl<=self::CDN_TTL_MAX)) { + $headers[self::CDN_TTL]= $ttl; + } else { + throw new InvalidArgumentException(self::ERROR_CDN_TTL_OUT_OF_RANGE); + } + $result= $this->_httpCall($this->getCdnUrl().'/'.rawurlencode($container),HttpClient::PUT,$headers); + $status= $result->getStatus(); + switch ($status) { + case '201': // break intentionally omitted + $data= array ( + 'cdn_uri' => $result->getHeader(self::CDN_URI), + 'cdn_uri_ssl' => $result->getHeader(self::CDN_SSL_URI) + ); + return $data; + case '404': + $this->_errorMsg= self::ERROR_CONTAINER_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Update the attribute of a CDN container + * + * @param string $container + * @param integer $ttl + * @param boolean $cdn_enabled + * @param boolean $log + * @return array|boolean + */ + public function updateCdnContainer($container,$ttl=null,$cdn_enabled=null,$log=null) + { + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + if (empty($ttl) && (!isset($cdn_enabled)) && (!isset($log))) { + throw new InvalidArgumentException(self::ERROR_PARAM_UPDATE_CDN); + } + $headers=array(); + if (isset($ttl)) { + if (is_numeric($ttl) && ($ttl>=self::CDN_TTL_MIN) && ($ttl<=self::CDN_TTL_MAX)) { + $headers[self::CDN_TTL]= $ttl; + } else { + throw new InvalidArgumentException(self::ERROR_CDN_TTL_OUT_OF_RANGE); + } + } + if (isset($cdn_enabled)) { + if ($cdn_enabled===true) { + $headers[self::CDN_ENABLED]= 'true'; + } else { + $headers[self::CDN_ENABLED]= 'false'; + } + } + if (isset($log)) { + if ($log===true) { + $headers[self::CDN_LOG_RETENTION]= 'true'; + } else { + $headers[self::CDN_LOG_RETENTION]= 'false'; + } + } + $result= $this->_httpCall($this->getCdnUrl().'/'.rawurlencode($container),HttpClient::POST,$headers); + $status= $result->getStatus(); + switch ($status) { + case '202': // break intentionally omitted + $data= array ( + 'cdn_uri' => $result->getHeader(self::CDN_URI), + 'cdn_uri_ssl' => $result->getHeader(self::CDN_SSL_URI) + ); + return $data; + case '404': + $this->_errorMsg= self::ERROR_CONTAINER_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; + } + /** + * Get the information about a Cdn container + * + * @param string $container + * @return array|boolean + */ + public function getInfoCdn($container) { + if (empty($container)) { + throw new InvalidArgumentException(self::ERROR_PARAM_NO_NAME_CONTAINER); + } + $result= $this->_httpCall($this->getCdnUrl().'/'.rawurlencode($container),HttpClient::HEAD); + $status= $result->getStatus(); + switch ($status) { + case '204': // break intentionally omitted + $data= array ( + 'ttl' => $result->getHeader(self::CDN_TTL), + 'cdn_uri' => $result->getHeader(self::CDN_URI), + 'cdn_uri_ssl' => $result->getHeader(self::CDN_SSL_URI) + ); + $data['cdn_enabled']= (strtolower($result->getHeader(self::CDN_ENABLED))!=='false'); + $data['log_retention']= (strtolower($result->getHeader(self::CDN_LOG_RETENTION))!=='false'); + return $data; + case '404': + $this->_errorMsg= self::ERROR_CONTAINER_NOT_FOUND; + break; + default: + $this->_errorMsg= $result->getBody(); + break; + } + $this->_errorStatus= $status; + return false; } } \ No newline at end of file diff --git a/library/Zend/Service/Rackspace/Files/Container.php b/library/Zend/Service/Rackspace/Files/Container.php index ca873385393..0013fde91c3 100644 --- a/library/Zend/Service/Rackspace/Files/Container.php +++ b/library/Zend/Service/Rackspace/Files/Container.php @@ -1,4 +1,5 @@ _service = $service; + $this->_name = $data['name']; + if (!empty($data['cdn_enabled'])) { + $this->_cdn= (strtolower($data['cdn_enabled'])!=='false'); + $this->_ttl= $data['ttl']; + $this->_logRetention= (strtolower($data['log_retention'])!=='false'); + $this->_cdnUri= $data['cdn_uri']; + if (!empty($data['cdn_uri_ssl'])) { + $this->_cdnUriSsl= $data['cdn_uri_ssl']; + } + } else { + $this->_objectCount = $data['count']; + $this->_size = $data['bytes']; + if (!empty($data['metadata']) && is_array($data['metadata'])) { + $this->_metadata = $data['metadata']; + $this->_getMetadata = true; + } } - $this->_service= $service; - $this->_name= $data['name']; - $this->_objectCount= $data['count']; - $this->_size= $data['bytes']; } /** * Get the name of the container @@ -95,6 +169,9 @@ public function getName() */ public function getSize() { + if (!isset($this->_size)) { + $null= $this->getMetadata(); + } return $this->_size; } /** @@ -102,29 +179,269 @@ public function getSize() * * @return integer */ - public function getObjectCount() { + public function getObjectCount() + { + if (!isset($this->_size)) { + $null= $this->getMetadata(); + } return $this->_objectCount; } - - public function getMetadata() + /** + * Return true if the container is CDN enabled + * + * @return boolean + */ + public function isCdnEnabled() { - + if (!isset($this->_cdn)) { + $this->updateCdnInfo(); + } + return $this->_cdn; } /** - * Get the files of the container + * Get the TTL of the CDN + * + * @return integer + */ + public function getCdnTtl() { + if (!isset($this->_ttl)) { + $this->updateCdnInfo(); + } + return $this->_ttl; + } + /** + * Return true if the log retention is enabled for the CDN + * + * @return boolean + */ + public function isCdnLogEnabled() + { + if (!isset($this->_logRetention)) { + $this->updateCdnInfo(); + } + return $this->_logRetention; + } + /** + * Get the CDN URI + * + * @return string + */ + public function getCdnUri() + { + if (!isset($this->_cdnUri)) { + $this->updateCdnInfo(); + } + return $this->_cdnUri; + } + /** + * Get the CDN URI SSL + * + * @return string + */ + public function getCdnUriSsl() + { + if (!isset($this->_cdnUriSsl)) { + $this->updateCdnInfo(); + } + return $this->_cdnUriSsl; + } + /** + * Get the metadata of the container + * + * If $key is empty return the array of metadata + * + * @param string $key + * @return array|string + */ + public function getMetadata($key=null) + { + if (empty($this->_metadata) && (!$this->_getMetadata)) { + $result = $this->_service->getMetadataContainer($this->getName()); + if (!empty($result)) { + $this->_objectCount = $result['tot_objects']; + $this->_size = $result['size']; + if (!empty($result['metadata']) && is_array($result['metadata'])) { + $this->_metadata = $result['metadata']; + } + } + $this->_getMetadata = true; + } + if (!empty($this->_metadata[$key])) { + return $this->_metadata[$key]; + } + return $this->_metadata; + } + /** + * Get all the object of the container * * @return Zend\Service\Rackspace\Files\ObjectList */ - public function getFiles() + public function getObjects() { - return $this->_service->getFiles($this->getName()); + return $this->_service->getObjects($this->getName()); } - public function addFile($file) + /** + * Get an object of the container + * + * @param string $name + * @param array $headers + * @return Zend\Service\Rackspace\Files\Object|boolean + */ + public function getObject($name, $headers=array()) { - + return $this->_service->getObject($this->getName(), $name, $headers); } - public function deleteFile($file) + /** + * Add an object in the container + * + * @param string $name + * @param string $file the content of the object + * @param array $metadata + * @return boolen + */ + public function addObject($name, $file, $metadata=array()) { - + return $this->_service->storeObject($this->getName(), $name, $file, $metadata); + } + /** + * Delete an object in the container + * + * @param string $obj + * @return boolean + */ + public function deleteObject($obj) + { + return $this->_service->deleteObject($this->getName(), $obj); + } + /** + * Copy an object to another container + * + * @param string $obj_source + * @param string $container_dest + * @param string $obj_dest + * @param array $metadata + * @param string $content_type + * @return boolean + */ + public function copyObject($obj_source, $container_dest, $obj_dest, $metadata=array(), $content_type=null) + { + return $this->_service->copyObject($this->getName(), $obj_source, $container_dest, $obj_dest, $metadata, $content_type); + } + /** + * Get the metadata of an object in the container + * + * @param string $object + * @return array + */ + public function getMetadataObject($object) + { + return $this->_service->getMetadataObject($this->getName(),$object); + } + /** + * Set the metadata of an object in the container + * + * @param string $object + * @param array $metadata + * @return boolean + */ + public function setMetadataObject($object,$metadata=array()) { + return $this->_service->setMetadataObject($this->getName(),$object,$metadata); + } + /** + * Enable the CDN for the container + * + * @param integer $ttl + * @return array|boolean + */ + public function enableCdn($ttl=RackspaceFiles::CDN_TTL_MIN) { + $result= $this->_service->enableCdnContainer($this->getName(),$ttl); + if ($result!==false) { + $this->_cdn= true; + $this->_ttl= $ttl; + $this->_logRetention= true; + $this->_cdnUri= $result['cdn_uri']; + $this->_cdnUriSsl= $result['cdn_uri_ssl']; + } + return $result; + } + /** + * Disable the CDN for the container + * + * @return boolean + */ + public function disableCdn() { + $result= $this->_service->updateCdnContainer($this->getName(),null,false); + if ($result!==false) { + $this->_cdn= false; + $this->_resetParamsCdn(); + return true; + } + return false; + } + /** + * Change the TTL for the CDN container + * + * @param integer $ttl + * @return boolean + */ + public function changeTtlCdn($ttl) { + $result= $this->_service->updateCdnContainer($this->getName(),$ttl); + if ($result!==false) { + $this->_ttl= $ttl; + return true; + } + return false; + } + /** + * Enable the log retention for the CDN + * + * @return boolean + */ + public function enableLogCdn() { + $result= $this->_service->updateCdnContainer($this->getName(),null,null,true); + if ($result!==false) { + $this->_logRetention= true; + return true; + } + return false; + } + /** + * Disable the log retention for the CDN + * + * @return boolean + */ + public function disableLogCdn() { + $result= $this->_service->updateCdnContainer($this->getName(),null,null,false); + if ($result!==false) { + $this->_logRetention= false; + return true; + } + return false; + } + /** + * Update the CDN information + * + * @return boolean + */ + public function updateCdnInfo() { + $result= $this->_service->getInfoCdn($this->getName()); + if ($result!==false) { + $this->_cdn= (strtolower($result['cdn_enabled'])!=='false'); + $this->_ttl= $result['ttl']; + $this->_logRetention= (strtolower($result['log_retention'])!=='false'); + $this->_cdnUri= $result['cdn_uri']; + $this->_cdnUriSsl= $result['cdn_uri_ssl']; + return true; + } + return false; + } + /** + * Reset all the parameters related to the CDN container + */ + private function _resetParamsCdn() { + $this->_ttl= null; + $this->_logRetention= null; + $this->_cdnUri= null; + $this->_cdnUriSsl= null; } } \ No newline at end of file diff --git a/library/Zend/Service/Rackspace/Files/Object.php b/library/Zend/Service/Rackspace/Files/Object.php index 4f149991361..156197a85cf 100644 --- a/library/Zend/Service/Rackspace/Files/Object.php +++ b/library/Zend/Service/Rackspace/Files/Object.php @@ -26,28 +26,71 @@ class Object { + /** + * Name of the object + * + * @var string + */ protected $_name; + /** + * MD5 value of the object's content + * + * @var string + */ protected $_hash; + /** + * Size in bytes of the object's content + * + * @var integer + */ protected $_size; + /** + * Content type of the object's content + * + * @var + */ protected $_contentType; + /** + * Date of the last modified of the object + * + * @var string + */ protected $_lastModified; - - public function getName() { - return $this->_name; - } - public function getHash() { - return $this->_hash; - } - public function getSize() { - return $this->_size; - } - public function getContentType() { - return $this->_contentType; - } - public function getLastModified() { - return $this->_lastModified; - } - + /** + * Object content + * + * @var string + */ + protected $_file; + /** + * Name of the container where the object is stored + * + * @var string + */ + protected $_container; + /** + * If it's true means we called the getMetadata API + * + * @var boolean + */ + private $_getMetadata = false; + /** + * __construct() + * + * You must pass the RackspaceFiles object of the caller and an associative + * array with the keys "name", "container", "hash", "bytes", "content_type", + * "last_modified", "file" where: + * name= name of the object + * container= name of the container where the object is stored + * hash= the MD5 of the object's content + * bytes= size in bytes of the object's content + * content_type= content type of the object's content + * last_modified= date of the last modified of the object + * file= content of the object + * + * @param RackspaceFiles $service + * @param array $data + */ public function __construct(RackspaceFiles $service,$data) { if (!($service instanceof RackspaceFiles) || !is_array($data)) { @@ -56,6 +99,9 @@ public function __construct(RackspaceFiles $service,$data) if (!array_key_exists('name', $data)) { throw new InvalidArgumentException("You must pass the name of the object in the array (name)"); } + if (!array_key_exists('container', $data)) { + throw new InvalidArgumentException("You must pass the container of the object in the array (container)"); + } if (!array_key_exists('hash', $data)) { throw new InvalidArgumentException("You must pass the hash of the object in the array (hash)"); } @@ -69,13 +115,148 @@ public function __construct(RackspaceFiles $service,$data) throw new InvalidArgumentException("You must pass the last modified data of the object in the array (last_modified)"); } $this->_name= $data['name']; + $this->_container= $data['container']; $this->_hash= $data['hash']; $this->_size= $data['bytes']; $this->_contentType= $data['content_type']; $this->_lastModified= $data['last_modified']; + if (!empty($data['file'])) { + $this->_file= $data['file']; + } $this->_service= $service; } - public function getMetadata() { - + /** + * Get name + * + * @return string + */ + public function getName() { + return $this->_name; + } + /** + * Get the name of the container + * + * @return string + */ + public function getContainer() { + return $this->_container; + } + /** + * Get the MD5 of the object's content + * + * @return string + */ + public function getHash() { + return $this->_hash; + } + /** + * Get the size of the object's content + * + * @return integer + */ + public function getSize() { + return $this->_size; + } + /** + * Get the content type of the object's content + * + * @return string + */ + public function getContentType() { + return $this->_contentType; + } + /** + * Get the data of the last modified of the object + * + * @return string + */ + public function getLastModified() { + return $this->_lastModified; + } + /** + * Get the content of the object + * + * @return string + */ + public function getFile() { + return $this->_file; + } + /** + * Get the metadata of the object + * If you don't pass the $key it returns the entire array of metadata value + * + * @param string $key + * @return string|array + */ + public function getMetadata($key=null) { + if (empty($this->_metadata) && (!$this->_getMetadata)) { + $result= $this->_service->getMetadataObject($this->_container,$this->_name); + if (!empty($result)) { + $this->_hash= $data['hash']; + $this->_size= $data['bytes']; + $this->_contentType= $data['content_type']; + $this->_lastModified= $data['last_modified']; + if (!empty($result['metadata'])) { + $this->_metadata= $result['metadata']; + } + } + $this->_getMetadata= true; + } + if (!empty($this->_metadata[$key])) { + return $this->_metadata[$key]; + } + return $this->_metadata; + } + /** + * Set the metadata value + * The old metadata values are replaced with the new one + * + * @param array $metadata + * @return boolean + */ + public function setMetadata($metadata) { + return $this->_service->setMetadataObject($this->_container,$this->_name,$metadata); + } + /** + * Copy the object to another container + * You can add metadata information to the destination object, change the + * content_type and the name of the object + * + * @param string $container_dest + * @param string $name_dest + * @param array $metadata + * @param string $content_type + * @return boolean + */ + public function copyTo($container_dest,$name_dest,$metadata=array(),$content_type=null) { + return $this->_service->copyObject($this->_container,$this->_name,$container_dest,$name_dest,$metadata,$content_type); + } + /** + * Get the CDN URL of the object + * + * @return string + */ + public function getCdnUrl() { + $result= $this->_service->getInfoCdn($this->_container); + if ($result!==false) { + if ($result['cdn_enabled']) { + return $result['cdn_uri'].'/'.$this->_name; + } + } + return false; + } + /** + * Get the CDN SSL URL of the object + * + * @return string + */ + public function getCdnUrlSsl() { + $result= $this->_service->getInfoCdn($this->_container); + if ($result!==false) { + if ($result['cdn_enabled']) { + return $result['cdn_uri_ssl'].'/'.$this->_name; + } + } + return false; } } diff --git a/library/Zend/Service/Rackspace/Files/ObjectList.php b/library/Zend/Service/Rackspace/Files/ObjectList.php index 8755adc69bf..867746265b0 100644 --- a/library/Zend/Service/Rackspace/Files/ObjectList.php +++ b/library/Zend/Service/Rackspace/Files/ObjectList.php @@ -58,18 +58,31 @@ class ObjectList implements \Countable, \Iterator, \ArrayAccess * @var RackspaceFiles */ protected $_service; + /** + * The container name of the object list + * + * @var string + */ + protected $_container; /** * __construct() * * @param array $list * @return boolean */ - public function __construct(RackspaceFiles $service,$list = array()) + public function __construct(RackspaceFiles $service,$list,$container) { - if (!($service instanceof RackspaceFiles) || !is_array($list)) { - throw new InvalidArgumentException("You must pass a RackspaceFiles object and an array"); + if (!($service instanceof RackspaceFiles)) { + throw new InvalidArgumentException("You must pass a RackspaceFiles object"); + } + if (empty($list)) { + throw new InvalidArgumentException("You must pass an array of data objects"); + } + if (empty($container)) { + throw new InvalidArgumentException("You must pass the container of the object list"); } $this->_service= $service; + $this->_container= $container; $this->_constructFromArray($list); } /** @@ -81,6 +94,7 @@ public function __construct(RackspaceFiles $service,$list = array()) private function _constructFromArray(array $list) { foreach ($list as $obj) { + $obj['container']= $this->_container; $this->_addObject(new Object($this->_service,$obj)); } } diff --git a/library/Zend/Service/Rackspace/Rackspace.php b/library/Zend/Service/Rackspace/Rackspace.php index 2a6f4644de0..e7bf1fe4d69 100644 --- a/library/Zend/Service/Rackspace/Rackspace.php +++ b/library/Zend/Service/Rackspace/Rackspace.php @@ -30,21 +30,6 @@ abstract class Rackspace const UK_AUTH_URL= 'https://lon.auth.api.rackspacecloud.com/v1.0'; const API_FORMAT= 'json'; const USER_AGENT= 'Zend\Service\Rackspace'; - const ACCOUNT_CONTAINER_COUNT= "X-account-container-count"; - const ACCOUNT_BYTES_USED= "X-account-bytes-used"; - const CONTAINER_OBJ_COUNT= "X-account-object-count"; - const CONTAINER_BYTES_USE= "X-Container-Bytes-Used"; - const METADATA_OBJECT_HEADER= "X-Object-Meta-"; - const METADATA_CONTAINER_HEADER= "X-Container-Meta-"; - const MANIFEST_OBJECT_HEADER= "X-Object-Manifest"; - const CDN_URI= "X-CDN-URI"; - const CDN_SSL_URI= "X-CDN-SSL-URI"; - const CDN_ENABLED= "X-CDN-Enabled"; - const CDN_LOG_RETENTION= "X-Log-Retention"; - const CDN_ACL_USER_AGENT= "X-User-Agent-ACL"; - const CDN_ACL_REFERRER= "X-Referrer-ACL"; - const CDN_TTL= "X-TTL"; - const CDNM_URL= "X-CDN-Management-Url"; const STORAGE_URL= "X-Storage-Url"; const AUTH_TOKEN= "X-Auth-Token"; const AUTH_USER_HEADER= "X-Auth-User"; @@ -52,21 +37,59 @@ abstract class Rackspace const AUTH_USER_HEADER_LEGACY= "X-Storage-User"; const AUTH_KEY_HEADER_LEGACY= "X-Storage-Pass"; const AUTH_TOKEN_LEGACY= "X-Storage-Token"; - const CDN_EMAIL= "X-Purge-Email"; - + const CDNM_URL= "X-CDN-Management-Url"; + /** + * Rackspace Key + * + * @var string + */ protected $_key; + /** + * Rackspace account name + * + * @var string + */ protected $_user; + /** + * Token of authentication + * + * @var string + */ protected $_token; + /** + * Authentication URL + * + * @var string + */ protected $_authUrl; /** * @var Zend\Http\Client */ protected $_httpClient; + /** + * Error Msg + * + * @var string + */ protected $_errorMsg; + /** + * HTTP error status + * + * @var string + */ protected $_errorStatus; + /** + * Storage URL + * + * @var string + */ protected $_storageUrl; + /** + * CDN URL + * + * @var string + */ protected $_cdnUrl; - /** * __construct() * @@ -92,21 +115,38 @@ public function __construct($user, $key, $authUrl=self::US_AUTH_URL) $this->setKey($key); $this->setAuthUrl($authUrl); } - + /** + * Get User account + * + * @return string + */ public function getUser() { return $this->_user; } - + /** + * Get user key + * + * @return string + */ public function getKey() { return $this->_key; } - + /** + * Get authentication URL + * + * @return string + */ public function getAuthUrl() { return $this->_authUrl; } + /** + * Get the storage URL + * + * @return string|boolean + */ public function getStorageUrl() { if (empty($this->_storageUrl)) { if (!$this->authenticate()) { @@ -115,32 +155,62 @@ public function getStorageUrl() { } return $this->_storageUrl; } + /** + * Get the CDN URL + * + * @return string|boolean + */ public function getCdnUrl() { if (empty($this->_cdnUrl)) { if (!$this->authenticate()) { return false; } } - return $this->_cdnUrl(); + return $this->_cdnUrl; } + /** + * Set the user account + * + * @param string $user + * @return void + */ public function setUser($user) { if (!empty($user)) { $this->_user = $user; } } + /** + * Set the authentication key + * + * @param string $key + * @return void + */ public function setKey($key) { if (!empty($key)) { $this->_key = $key; } } + /** + * Set the Authentication URL + * + * @param string $url + * @return void + */ public function setAuthUrl($url) { if (!empty($url) && in_array($url, array(self::US_AUTH_URL, self::UK_AUTH_URL))) { $this->_authUrl = $url; + } else { + throw new Exception\InvalidArgumentException("The authentication URL is not valid"); } } + /** + * Get the authentication token + * + * @return string + */ public function getToken() { if (empty($this->_token)) { @@ -178,6 +248,15 @@ public function getHttpClient() } return $this->_httpClient; } + /** + * Return true is the last call was successful + * + * @return boolean + */ + public function isSuccessful() + { + return ($this->_errorMsg==''); + } /** * HTTP call * @@ -192,12 +271,12 @@ protected function _httpCall($url,$method,$headers=array(),$get=array(),$body=nu { $client = $this->getHttpClient(); $client->resetParameters(true); - if (!array_key_exists(self::AUTH_USER_HEADER, $headers)) { + if (empty($headers[self::AUTH_USER_HEADER])) { $headers[self::AUTH_TOKEN]= $this->getToken(); } $client->setHeaders($headers); $client->setMethod($method); - if (!array_key_exists('format', $get)) { + if (empty($get['format'])) { $get['format']= self::API_FORMAT; } $client->setParameterGet($get); @@ -205,6 +284,8 @@ protected function _httpCall($url,$method,$headers=array(),$get=array(),$body=nu $client->setRawData($body); } $client->setUri($url); + $this->_errorMsg=''; + $this->_errorStatus=''; return $client->request(); } /** @@ -224,10 +305,9 @@ public function authenticate() $this->_storageUrl= $result->getHeader(self::STORAGE_URL); $this->_cdnUrl= $result->getHeader(self::CDNM_URL); return true; - } else { - $this->_errorMsg= $result->getBody(); - $this->_errorStatus= $result->getStatus(); } + $this->_errorMsg= $result->getBody(); + $this->_errorStatus= $result->getStatus(); return false; } } \ No newline at end of file From 216b352079b18723f99f600687e47aa8544db882 Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 11 May 2011 12:12:19 +0200 Subject: [PATCH 20/45] First version of the OffLine test for Zend\Service\Rackspace\Files (no complete) --- tests/TestConfiguration.php.dist | 10 +- tests/Zend/Service/GoGrid/JobOfflineTest.php | 6 +- .../Service/Rackspace/Files/OfflineTest.php | 137 +++++++++++++ .../Files/_files/testGetContainer.response | 8 + .../Files/_files/testGetContainers.response | 10 + tests/Zend/Service/Rackspace/OfflineTest.php | 191 ++++++++++++++++++ .../_files/testAuthenticate.response | 11 + .../_files/testAuthenticateError.response | 8 + tests/runtests.sh | 2 +- 9 files changed, 377 insertions(+), 6 deletions(-) create mode 100644 tests/Zend/Service/Rackspace/Files/OfflineTest.php create mode 100644 tests/Zend/Service/Rackspace/Files/_files/testGetContainer.response create mode 100644 tests/Zend/Service/Rackspace/Files/_files/testGetContainers.response create mode 100644 tests/Zend/Service/Rackspace/OfflineTest.php create mode 100644 tests/Zend/Service/Rackspace/_files/testAuthenticate.response create mode 100644 tests/Zend/Service/Rackspace/_files/testAuthenticateError.response diff --git a/tests/TestConfiguration.php.dist b/tests/TestConfiguration.php.dist index 7b5ec083ede..040aaa0a128 100644 --- a/tests/TestConfiguration.php.dist +++ b/tests/TestConfiguration.php.dist @@ -577,8 +577,8 @@ define('TESTS_ZEND_SERVICE_FLICKR_ONLINE_APIKEY', 'Enter API key here'); * Zend_service_GoGrid offline tests */ -define ('TESTS_ZEND_SERVICE_GOGRID_OFFLINE_KEY','test'); -define ('TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET','test'); +define ('TESTS_ZEND_SERVICE_GOGRID_ONLINE_KEY','Enter GoGrid key here'); +define ('TESTS_ZEND_SERVICE_GOGRID_ONLINE_SECRET','Enter GoGrid secret here'); /** * Zend_Service_LiveDocx configuration @@ -591,6 +591,12 @@ define ('TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET','test'); define('TESTS_ZEND_SERVICE_LIVEDOCX_USERNAME', false); define('TESTS_ZEND_SERVICE_LIVEDOCX_PASSWORD', false); +/** + * Zend_Service_Rackspace tests + */ +define ('TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_USER','Enter Rackspace user here'); +define ('TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_KEY','Enter Rackspace key here'); + /** * Zend_Service_ReCaptcha tests */ diff --git a/tests/Zend/Service/GoGrid/JobOfflineTest.php b/tests/Zend/Service/GoGrid/JobOfflineTest.php index a52393063cb..e91aa4bcaf9 100644 --- a/tests/Zend/Service/GoGrid/JobOfflineTest.php +++ b/tests/Zend/Service/GoGrid/JobOfflineTest.php @@ -67,7 +67,7 @@ class JobOfflineTest extends \PHPUnit_Framework_TestCase */ public function setUp() { - $this->_job = new Job(TESTS_ZEND_SERVICE_GOGRID_OFFLINE_KEY,TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET); + $this->_job = new Job(TESTS_ZEND_SERVICE_GOGRID_ONLINE_KEY,TESTS_ZEND_SERVICE_GOGRID_ONLINE_SECRET); $this->_filesPath = __DIR__ . '/_files'; $this->_httpClientAdapterTest = new HttpTest(); @@ -84,7 +84,7 @@ public function testConstructExceptionMissingKeyAttribute() 'Zend\Service\GoGrid\Exception\InvalidArgumentException', 'The key cannot be empty' ); - $job= new Job(null,TESTS_ZEND_SERVICE_GOGRID_OFFLINE_SECRET); + $job= new Job(null,TESTS_ZEND_SERVICE_GOGRID_ONLINE_SECRET); } /** * Ensures that __construct() throws an exception when given an empty secret attribute @@ -97,7 +97,7 @@ public function testConstructExceptionMissingSecretAttribute() 'Zend\Service\GoGrid\Exception\InvalidArgumentException', 'The secret cannot be empty' ); - $job= new Job(TESTS_ZEND_SERVICE_GOGRID_OFFLINE_KEY,null); + $job= new Job(TESTS_ZEND_SERVICE_GOGRID_ONLINE_KEY,null); } /** * testJobList diff --git a/tests/Zend/Service/Rackspace/Files/OfflineTest.php b/tests/Zend/Service/Rackspace/Files/OfflineTest.php new file mode 100644 index 00000000000..fe2ea85753a --- /dev/null +++ b/tests/Zend/Service/Rackspace/Files/OfflineTest.php @@ -0,0 +1,137 @@ +_files = new RackspaceFiles(TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_USER,TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_KEY); + $this->_filesPath = __DIR__ . '/_files'; + $this->_httpClientAdapterTest = new HttpTest(); + } + /** + * Utility method for returning a string HTTP response, which is loaded from a file + * + * @param string $name + * @return string + */ + protected function _loadResponse($name) + { + return file_get_contents("$this->_filesPath/$name.response"); + } + /** + * Test the get of all the containers + * + * @return void + */ + public function testGetContainers() + { + $this->_files->getHttpClient() + ->setAdapter($this->_httpClientAdapterTest); + + $this->_httpClientAdapterTest->setResponse($this->_loadResponse('../../_files/testAuthenticate')); + $this->assertTrue($this->_files->authenticate(),'Authentication failed'); + + $this->_httpClientAdapterTest->setResponse($this->_loadResponse(__FUNCTION__)); + $containers= $this->_files->getContainers(); + $this->assertTrue($this->_files->isSuccessful(),'Get containers failed'); + $this->assertEquals($this->_files->getCountContainers(),3,'Total containers count is wrong'); + $this->assertEquals($this->_files->getSizeContainers(),27809,'Total objects size is wrong'); + $this->assertEquals($this->_files->getCountObjects(),6,'Total objects count is wrong'); + $this->assertEquals($containers[1]->getName(),'foo'); + $this->assertEquals($containers[1]->getObjectCount(),2); + $this->assertEquals($containers[1]->getSize(),9756); + $this->assertEquals($containers[2]->getName(),'test'); + $this->assertEquals($containers[2]->getObjectCount(),3); + $this->assertEquals($containers[2]->getSize(),17839); + } + /** + * Test the get of a container + * + * @return void + */ + public function testGetContainer() + { + $this->_files->getHttpClient() + ->setAdapter($this->_httpClientAdapterTest); + + $this->_httpClientAdapterTest->setResponse($this->_loadResponse('../../_files/testAuthenticate')); + $this->assertTrue($this->_files->authenticate(),'Authentication failed'); + + $this->_httpClientAdapterTest->setResponse($this->_loadResponse(__FUNCTION__)); + + $container= $this->_files->getContainer('foo'); + $this->assertTrue($this->_files->isSuccessful(),'Get container failed'); + $this->assertEquals($container->getName(),'foo','The name of container is wrong'); + $this->assertEquals($container->getSize(),9756,'The size in bytes is wrong'); + $this->assertEquals($container->getObjectCount(),2,'The objects count is wrong'); + $metadata= array( + 'foo' => 'bar', + 'foo2' => 'bar2' + ); + $this->assertEquals($container->getMetadata(),$metadata,'The metadata is wrong'); + } +} diff --git a/tests/Zend/Service/Rackspace/Files/_files/testGetContainer.response b/tests/Zend/Service/Rackspace/Files/_files/testGetContainer.response new file mode 100644 index 00000000000..b049cbc2b13 --- /dev/null +++ b/tests/Zend/Service/Rackspace/Files/_files/testGetContainer.response @@ -0,0 +1,8 @@ +HTTP/1.1 204 No Content +Date: Wed, 11 May 2011 10:03:17 GMT +X-container-meta-foo: bar +X-container-meta-foo2: bar2 +X-container-bytes-used: 9756 +X-container-object-count: 2 +Connection: close +Content-length: 0 diff --git a/tests/Zend/Service/Rackspace/Files/_files/testGetContainers.response b/tests/Zend/Service/Rackspace/Files/_files/testGetContainers.response new file mode 100644 index 00000000000..c7ec4e7bb9a --- /dev/null +++ b/tests/Zend/Service/Rackspace/Files/_files/testGetContainers.response @@ -0,0 +1,10 @@ +HTTP/1.1 200 OK +Content-type: application/json; charset=utf8 +Date: Wed, 11 May 2011 09:02:43 GMT +X-account-container-count: 3 +Connection: close +X-account-object-count: 6 +X-account-bytes-used: 27809 +Content-length: 129 + +[{"name":".CDN_ACCESS_LOGS","count":1,"bytes":214},{"name":"foo","count":2,"bytes":9756},{"name":"test","count":3,"bytes":17839}] \ No newline at end of file diff --git a/tests/Zend/Service/Rackspace/OfflineTest.php b/tests/Zend/Service/Rackspace/OfflineTest.php new file mode 100644 index 00000000000..2b428017a4b --- /dev/null +++ b/tests/Zend/Service/Rackspace/OfflineTest.php @@ -0,0 +1,191 @@ +_files = new RackspaceFiles(TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_USER,TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_KEY); + $this->_filesPath = __DIR__ . '/_files'; + $this->_httpClientAdapterTest = new HttpTest(); + } + /** + * Utility method for returning a string HTTP response, which is loaded from a file + * + * @param string $name + * @return string + */ + protected function _loadResponse($name) + { + return file_get_contents("$this->_filesPath/$name.response"); + } + /** + * Ensures that __construct() throws an exception when given an empty key attribute + * + * @return void + */ + public function testConstructExceptionMissingUserAttribute() + { + $this->setExpectedException( + 'Zend\Service\Rackspace\Exception\InvalidArgumentException', + 'The user cannot be empty' + ); + $file= new RackspaceFiles(null,TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_KEY); + } + /** + * Ensures that __construct() throws an exception when given an empty secret attribute + * + * @return void + */ + public function testConstructExceptionMissingKeyAttribute() + { + $this->setExpectedException( + 'Zend\Service\Rackspace\Exception\InvalidArgumentException', + 'The key cannot be empty' + ); + $file= new RackspaceFiles(TESTS_ZEND_SERVICE_RACKSPACE_ONLINE_USER,null); + } + /** + * Test the default authentication URL + * + * @return void + */ + public function testDefaultAuthUrl() + { + $this->assertEquals($this->_files->getAuthUrl(),RackspaceFiles::US_AUTH_URL,'The default Authentication URL is changed'); + } + /** + * Test the set of the key + * + * @return void + */ + public function testSetKey() + { + $key= '1234567890'; + $this->_files->setKey($key); + $this->assertEquals($this->_files->getKey(),$key); + } + /** + * Test the set of the user + * + * @return void + */ + public function testSetUser() + { + $user= 'test'; + $this->_files->setUser($user); + $this->assertEquals($this->_files->getUser(),$user); + } + /** + * Test the set of an invalid authentication URL + * + * @return void + */ + public function testSetInvalidAuthUrl() + { + $this->setExpectedException( + 'Zend\Service\Rackspace\Exception\InvalidArgumentException', + 'The authentication URL is not valid' + ); + $this->_files->setAuthUrl('http://test'); + } + /** + * Check the authentication and the results (token, storage_url, cdn_url) + * + * @return void + */ + public function testAuthenticate() + { + $this->_files->getHttpClient() + ->setAdapter($this->_httpClientAdapterTest); + + $this->_httpClientAdapterTest->setResponse($this->_loadResponse(__FUNCTION__)); + + $this->assertTrue($this->_files->authenticate(),'Authentication failed'); + $this->assertTrue($this->_files->isSuccessful(),'Authentication call failed'); + $this->assertEquals($this->_files->getToken(),'0f0223cd-f157-4d04-bb2d-ccda1a5643af','The token is not valid'); + $this->assertEquals($this->_files->getStorageUrl(),'https://storage101.ord1.clouddrive.com/v1/MossoCloudFS_2abf18d2-f3f8-45fd-940e-a7b38a195023','The storage URL is not valid'); + $this->assertEquals($this->_files->getCdnUrl(),'https://cdn2.clouddrive.com/v1/MossoCloudFS_2abf18d2-f3f8-45fd-940e-a7b38a195023','The CDN URL is not valid'); + } + /** + * Test the authentication error (401 Unauthorized - Bad username or password) + * + * @return void + */ + public function testAuthenticateError() + { + $this->_files->getHttpClient() + ->setAdapter($this->_httpClientAdapterTest); + + $this->_httpClientAdapterTest->setResponse($this->_loadResponse(__FUNCTION__)); + + $this->assertFalse($this->_files->authenticate()); + $this->assertFalse($this->_files->isSuccessful()); + $this->assertEquals($this->_files->getErrorStatus(),'401'); + $this->assertEquals($this->_files->getErrorMsg(),'Bad username or password'); + + } +} diff --git a/tests/Zend/Service/Rackspace/_files/testAuthenticate.response b/tests/Zend/Service/Rackspace/_files/testAuthenticate.response new file mode 100644 index 00000000000..ca9e6fa3039 --- /dev/null +++ b/tests/Zend/Service/Rackspace/_files/testAuthenticate.response @@ -0,0 +1,11 @@ +HTTP/1.1 204 No Content +Date: Wed, 11 May 2011 08:04:48 GMT +Server: Apache/2.2.3 (Mosso Engineering) +X-storage-url: https://storage101.ord1.clouddrive.com/v1/MossoCloudFS_2abf18d2-f3f8-45fd-940e-a7b38a195023 +X-storage-token: 0f0223cd-f157-4d04-bb2d-ccda1a5643af +X-cdn-management-url: https://cdn2.clouddrive.com/v1/MossoCloudFS_2abf18d2-f3f8-45fd-940e-a7b38a195023 +X-auth-token: 0f0223cd-f157-4d04-bb2d-ccda1a5643af +X-server-management-url: https://servers.api.rackspacecloud.com/v1.0/583924 +Content-length: 0 +Connection: close +Content-type: application/octet-stream diff --git a/tests/Zend/Service/Rackspace/_files/testAuthenticateError.response b/tests/Zend/Service/Rackspace/_files/testAuthenticateError.response new file mode 100644 index 00000000000..c9e77e7a028 --- /dev/null +++ b/tests/Zend/Service/Rackspace/_files/testAuthenticateError.response @@ -0,0 +1,8 @@ +HTTP/1.1 401 Unauthorized +Date: Wed, 11 May 2011 08:42:20 GMT +Server: Apache/2.2.3 (Mosso Engineering) +Content-length: 24 +Connection: close +Content-type: application/octet-stream + +Bad username or password \ No newline at end of file diff --git a/tests/runtests.sh b/tests/runtests.sh index 660bfb0a74f..ba08bcfe0f8 100755 --- a/tests/runtests.sh +++ b/tests/runtests.sh @@ -46,7 +46,7 @@ while [ -n "$1" ] ; do PHPUNIT_GROUPS="" break ;; - Akismet|Amazon|Amazon_Ec2|Amazon_S3|Amazon_Sqs|Audioscrobbler|Delicious|Flickr|GoGrid|LiveDocx|Nirvanix|ReCaptcha|Simpy|SlideShare|StrikeIron|Technorati|Twitter|WindowsAzure|Yahoo) + Akismet|Amazon|Amazon_Ec2|Amazon_S3|Amazon_Sqs|Audioscrobbler|Delicious|Flickr|GoGrid|LiveDocx|Nirvanix|Rackspace|ReCaptcha|Simpy|SlideShare|StrikeIron|Technorati|Twitter|WindowsAzure|Yahoo) PHPUNIT_GROUPS="${PHPUNIT_GROUPS:+"$PHPUNIT_GROUPS,"}Zend_Service_$1" shift ;; Ec2|S3) From c7b8453c27605ccf6d5d2a265de3780ca733cc1c Mon Sep 17 00:00:00 2001 From: ezimuel Date: Wed, 11 May 2011 14:29:46 +0200 Subject: [PATCH 21/45] Changed the enableCdn() return in boolean --- library/Zend/Service/Rackspace/Files/Container.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/Zend/Service/Rackspace/Files/Container.php b/library/Zend/Service/Rackspace/Files/Container.php index 0013fde91c3..8f301653dd0 100644 --- a/library/Zend/Service/Rackspace/Files/Container.php +++ b/library/Zend/Service/Rackspace/Files/Container.php @@ -351,7 +351,7 @@ public function setMetadataObject($object,$metadata=array()) { * Enable the CDN for the container * * @param integer $ttl - * @return array|boolean + * @return boolean */ public function enableCdn($ttl=RackspaceFiles::CDN_TTL_MIN) { $result= $this->_service->enableCdnContainer($this->getName(),$ttl); @@ -361,8 +361,9 @@ public function enableCdn($ttl=RackspaceFiles::CDN_TTL_MIN) { $this->_logRetention= true; $this->_cdnUri= $result['cdn_uri']; $this->_cdnUriSsl= $result['cdn_uri_ssl']; + return true; } - return $result; + return false; } /** * Disable the CDN for the container From 41e3e27a29c3c9a0947a80009cdff81ccdb95066 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Mon, 16 May 2011 15:19:01 -0500 Subject: [PATCH 22/45] Initial commit of Matthew's Zend\Di prototype Tests conform to ZendTest namespace --- library/Zend/Di/Configuration.php | 212 +++++++++++ library/Zend/Di/ContainerBuilder.php | 310 ++++++++++++++++ library/Zend/Di/Definition.php | 333 +++++++++++++++++ library/Zend/Di/DependencyDefinition.php | 35 ++ library/Zend/Di/DependencyEnabled.php | 8 + library/Zend/Di/DependencyInjection.php | 45 +++ .../Zend/Di/DependencyInjectionContainer.php | 61 +++ library/Zend/Di/DependencyInjector.php | 298 +++++++++++++++ library/Zend/Di/DependencyReference.php | 8 + library/Zend/Di/Exception.php | 6 + .../Di/Exception/ClassNotFoundException.php | 9 + .../Di/Exception/InvalidArgumentException.php | 10 + .../Di/Exception/InvalidCallbackException.php | 6 + .../Exception/InvalidParamNameException.php | 11 + .../Di/Exception/InvalidPositionException.php | 11 + .../Zend/Di/Exception/RuntimeException.php | 8 + .../Exception/UndefinedReferenceException.php | 9 + library/Zend/Di/InjectibleMethod.php | 9 + library/Zend/Di/InjectibleMethods.php | 19 + library/Zend/Di/Method.php | 58 +++ library/Zend/Di/Methods.php | 75 ++++ library/Zend/Di/Reference.php | 41 +++ library/Zend/Di/ServiceLocation.php | 8 + library/Zend/Di/ServiceLocator.php | 82 +++++ tests/Zend/Di/ConfigurationTest.php | 180 +++++++++ tests/Zend/Di/ContainerBuilderTest.php | 263 +++++++++++++ tests/Zend/Di/DefinitionTest.php | 206 +++++++++++ .../Di/DependencyInjectionContainerTest.php | 57 +++ tests/Zend/Di/DependencyInjectorTest.php | 348 ++++++++++++++++++ tests/Zend/Di/MethodTest.php | 32 ++ tests/Zend/Di/MethodsTest.php | 61 +++ tests/Zend/Di/ReferenceTest.php | 16 + tests/Zend/Di/ServiceLocatorTest.php | 102 +++++ tests/Zend/Di/TestAsset/ComposedClass.php | 6 + .../Zend/Di/TestAsset/ContainerExtension.php | 26 ++ tests/Zend/Di/TestAsset/DummyParams.php | 12 + tests/Zend/Di/TestAsset/InjectedMethod.php | 10 + tests/Zend/Di/TestAsset/InspectedClass.php | 11 + tests/Zend/Di/TestAsset/OptionalArg.php | 15 + tests/Zend/Di/TestAsset/StaticFactory.php | 11 + tests/Zend/Di/TestAsset/Struct.php | 11 + tests/Zend/Di/_files/config.ini | 27 ++ tests/Zend/Di/_files/config.json | 54 +++ tests/Zend/Di/_files/config.xml | 53 +++ tests/Zend/Di/_files/config.yml | 42 +++ 45 files changed, 3215 insertions(+) create mode 100644 library/Zend/Di/Configuration.php create mode 100644 library/Zend/Di/ContainerBuilder.php create mode 100644 library/Zend/Di/Definition.php create mode 100644 library/Zend/Di/DependencyDefinition.php create mode 100644 library/Zend/Di/DependencyEnabled.php create mode 100644 library/Zend/Di/DependencyInjection.php create mode 100644 library/Zend/Di/DependencyInjectionContainer.php create mode 100644 library/Zend/Di/DependencyInjector.php create mode 100644 library/Zend/Di/DependencyReference.php create mode 100644 library/Zend/Di/Exception.php create mode 100644 library/Zend/Di/Exception/ClassNotFoundException.php create mode 100644 library/Zend/Di/Exception/InvalidArgumentException.php create mode 100644 library/Zend/Di/Exception/InvalidCallbackException.php create mode 100644 library/Zend/Di/Exception/InvalidParamNameException.php create mode 100644 library/Zend/Di/Exception/InvalidPositionException.php create mode 100644 library/Zend/Di/Exception/RuntimeException.php create mode 100644 library/Zend/Di/Exception/UndefinedReferenceException.php create mode 100644 library/Zend/Di/InjectibleMethod.php create mode 100644 library/Zend/Di/InjectibleMethods.php create mode 100644 library/Zend/Di/Method.php create mode 100644 library/Zend/Di/Methods.php create mode 100644 library/Zend/Di/Reference.php create mode 100644 library/Zend/Di/ServiceLocation.php create mode 100644 library/Zend/Di/ServiceLocator.php create mode 100644 tests/Zend/Di/ConfigurationTest.php create mode 100644 tests/Zend/Di/ContainerBuilderTest.php create mode 100644 tests/Zend/Di/DefinitionTest.php create mode 100644 tests/Zend/Di/DependencyInjectionContainerTest.php create mode 100644 tests/Zend/Di/DependencyInjectorTest.php create mode 100644 tests/Zend/Di/MethodTest.php create mode 100644 tests/Zend/Di/MethodsTest.php create mode 100644 tests/Zend/Di/ReferenceTest.php create mode 100644 tests/Zend/Di/ServiceLocatorTest.php create mode 100644 tests/Zend/Di/TestAsset/ComposedClass.php create mode 100644 tests/Zend/Di/TestAsset/ContainerExtension.php create mode 100644 tests/Zend/Di/TestAsset/DummyParams.php create mode 100644 tests/Zend/Di/TestAsset/InjectedMethod.php create mode 100644 tests/Zend/Di/TestAsset/InspectedClass.php create mode 100644 tests/Zend/Di/TestAsset/OptionalArg.php create mode 100644 tests/Zend/Di/TestAsset/StaticFactory.php create mode 100644 tests/Zend/Di/TestAsset/Struct.php create mode 100644 tests/Zend/Di/_files/config.ini create mode 100644 tests/Zend/Di/_files/config.json create mode 100644 tests/Zend/Di/_files/config.xml create mode 100644 tests/Zend/Di/_files/config.yml diff --git a/library/Zend/Di/Configuration.php b/library/Zend/Di/Configuration.php new file mode 100644 index 00000000000..9e993ba4668 --- /dev/null +++ b/library/Zend/Di/Configuration.php @@ -0,0 +1,212 @@ +injector = $injector; + } + + /** + * Update injector based on array configuration + * + * @param array $config + * @return void + */ + public function fromArray(array $config) + { + if (isset($config['definitions']) && is_array($config['definitions'])) { + $this->buildDefinitions($config['definitions']); + } + if (isset($config['aliases']) && is_array($config['aliases'])) { + $this->buildAliases($config['aliases']); + } + } + + /** + * Load definitions from a configuration object + * + * Accepts an array, an object with a toArray() method, or a Traversable + * object. + * + * @param array|object $config + * @return void + */ + public function fromConfig($config) + { + if (!is_object($config)) { + if (is_array($config)) { + return $this->fromArray($config); + } + throw new Exception\InvalidArgumentException(sprintf( + 'Configuration must be provided as either an array or Traversable object; "%s" was provided', + gettype($config) + )); + } + if (method_exists($config, 'toArray')) { + return $this->fromArray($config->toArray()); + } + if (!$config instanceof \Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Configuration must be provided as either an array or Traversable object; "%s" was provided', + get_class($config) + )); + } + $array = array(); + foreach ($config as $key => $value) { + $array[$key] = $value; + } + return $this->fromArray($array); + } + + /** + * Create definitions and inject into dependency injector + * + * @param array $definitions + * @return void + */ + protected function buildDefinitions(array $definitions) + { + foreach ($definitions as $definition) { + $this->buildDefinition($definition); + } + } + + /** + * Build a definition to add to the injector + * + * @param array $values + * @return void + */ + protected function buildDefinition(array $values) + { + if (!isset($values['class']) + || !is_string($values['class']) + || empty($values['class']) + ) { + throw new Exception\InvalidArgumentException(sprintf( + 'Cannot create definition; provided definition contains no class key (%s)', + var_export($values, 1) + )); + } + + $definition = new Definition($values['class']); + + foreach ($values as $key => $value) { + switch (strtolower($key)) { + case 'class': + break; + case 'constructor_callback': + $callback = $value; + if (is_array($value) + && (isset($value['class']) && isset($value['method'])) + ) { + $callback = array($value['class'], $value['method']); + } + $definition->setConstructorCallback($callback); + break; + case 'params': + if (!is_array($value)) { + break; + } + $params = $this->resolveReferences($value); + $definition->setParams($params); + break; + case 'param_map': + $definition->setParamMap($value); + break; + case 'tags': + $definition->addTags($value); + break; + case 'shared': + $definition->setShared((bool) $value); + break; + case 'methods': + $this->buildMethods($definition, $value); + break; + default: + // ignore all other options + break; + } + } + + $this->injector->setDefinition($definition); + } + + /** + * Build injectible methods for a definition + * + * @param DependencyDefinition $definition + * @param array $methods + * @return void + */ + protected function buildMethods(DependencyDefinition $definition, array $methods) + { + foreach ($methods as $methodDefinition) { + if (!is_array($methodDefinition)) { + continue; + } + if (!isset($methodDefinition['name'])) { + continue; + } + $method = $methodDefinition['name']; + $args = array(); + if (isset($methodDefinition['args']) && is_array($methodDefinition['args'])) { + $args = $this->resolveReferences($methodDefinition['args']); + } + $definition->addMethodCall($method, $args); + } + } + + /** + * Resolve parameters that are references + * + * If a parameter value is an array containing a key "__reference", replace + * it with a Reference object that is seeded with the value of that key. + * + * @param array $params + * @return array + */ + protected function resolveReferences(array $params) + { + foreach ($params as $key => $value) { + if (!is_array($value)) { + continue; + } + if (!isset($value['__reference'])) { + continue; + } + $params[$key] = new Reference($value['__reference']); + } + return $params; + } + + /** + * Build aliases from provided configuration + * + * $aliases should be array of $alias => $target pairs. + * + * @param array $aliases + * @return void + */ + protected function buildAliases(array $aliases) + { + foreach ($aliases as $alias => $target) { + $this->injector->setAlias($alias, $target); + } + } +} diff --git a/library/Zend/Di/ContainerBuilder.php b/library/Zend/Di/ContainerBuilder.php new file mode 100644 index 00000000000..014096d637c --- /dev/null +++ b/library/Zend/Di/ContainerBuilder.php @@ -0,0 +1,310 @@ +injector = $injector; + } + + /** + * Set the class name for the generated service locator container + * + * @param string $name + * @return ContainerBuilder + */ + public function setContainerClass($name) + { + $this->containerClass = $name; + return $this; + } + + /** + * Set the namespace to use for the generated class file + * + * @param string $namespace + * @return ContainerBuilder + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * Construct, configure, and return a PHP classfile code generation object + * + * Creates a Zend\CodeGenerator\Php\PhpFile object that has + * created the specified class and service locator methods. + * + * @param null|string $filename + * @return CodeGen\PhpFile + */ + public function getCodeGenerator($filename = null) + { + $indent = ' '; + $aliases = $this->reduceAliases($this->injector->getAliases()); + $caseStatements = array(); + $getters = array(); + + foreach ($this->injector->getDefinitions() as $definition) { + $name = $definition->getClass(); + $getter = $this->normalizeAlias($name); + $params = $definition->getParams(); + + // Build parameter list for instantiation + foreach ($params as $key => $param) { + if (null === $param || is_scalar($param) || is_array($param)) { + $string = var_export($param, 1); + if (strstr($string, '::__set_state(')) { + throw new Exception\RuntimeException('Arguments in definitions may not contain objects'); + } + $params[$key] = $string; + } elseif ($param instanceof DependencyReference) { + $params[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getServiceName())); + } else { + $message = sprintf('Unable to use object arguments when building containers. Encountered with "%s", parameter of type "%s"', $name, get_class($param)); + throw new Exception\RuntimeException($message); + } + } + + // Strip null arguments from the end of the params list + $reverseParams = array_reverse($params, true); + foreach ($reverseParams as $key => $param) { + if ('NULL' === $param) { + unset($params[$key]); + continue; + } + break; + } + + // Create instantiation code + $creation = ''; + if ($definition->hasConstructorCallback()) { + // Constructor callback + $callback = var_export($definition->getConstructorCallback(), 1); + if (strstr($callback, '::__set_state(')) { + throw new Exception\RuntimeException('Unable to build containers that use callbacks requiring object instances'); + } + if (count($params)) { + $creation = sprintf('$object = call_user_func(%s, %s);', $callback, implode(', ', $params)); + } else { + $creation = sprintf('$object = call_user_func(%s);', $callback); + } + } else { + // Normal instantiation + $className = '\\' . ltrim($name, '\\'); + $creation = sprintf('$object = new %s(%s);', $className, implode(', ', $params)); + } + + // Create method call code + $methods = ''; + foreach ($definition->getMethodCalls() as $method) { + $methodName = $method->getName(); + $methodParams = $method->getArgs(); + + // Create method parameter representation + foreach ($methodParams as $key => $param) { + if (null === $param || is_scalar($param) || is_array($param)) { + $string = var_export($param, 1); + if (strstr($string, '::__set_state(')) { + throw new Exception\RuntimeException('Arguments in definitions may not contain objects'); + } + $methodParams[$key] = $string; + } elseif ($param instanceof DependencyReference) { + $methodParams[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getServiceName())); + } else { + $message = sprintf('Unable to use object arguments when generating method calls. Encountered with class "%s", method "%s", parameter of type "%s"', $name, $methodName, get_class($param)); + throw new Exception\RuntimeException($message); + } + } + + // Strip null arguments from the end of the params list + $reverseParams = array_reverse($methodParams, true); + foreach ($reverseParams as $key => $param) { + if ('NULL' === $param) { + unset($methodParams[$key]); + continue; + } + break; + } + + $methods .= sprintf("\$object->%s(%s);\n", $methodName, implode(', ', $methodParams)); + } + + // Generate caching statement + $storage = ''; + if ($definition->isShared()) { + $storage = sprintf("\$this->services['%s'] = \$object;\n", $name); + } + + // Start creating getter + $getterBody = ''; + + // Create fetch of stored service + if ($definition->isShared()) { + $getterBody .= sprintf("if (isset(\$this->services['%s'])) {\n", $name); + $getterBody .= sprintf("%sreturn \$this->services['%s'];\n}\n\n", $indent, $name); + } + + // Creation and method calls + $getterBody .= sprintf("%s\n", $creation); + $getterBody .= $methods; + + // Stored service + $getterBody .= $storage; + + // End getter body + $getterBody .= "return \$object;\n"; + + $getterDef = new CodeGen\PhpMethod(); + $getterDef->setName($getter) + ->setBody($getterBody); + $getters[] = $getterDef; + + // Get cases for case statements + $cases = array($name); + if (isset($aliases[$name])) { + $cases = array_merge($aliases[$name], $cases); + } + + // Build case statement and store + $statement = ''; + foreach ($cases as $value) { + $statement .= sprintf("%scase '%s':\n", $indent, $value); + } + $statement .= sprintf("%sreturn \$this->%s();\n", str_repeat($indent, 2), $getter); + + $caseStatements[] = $statement; + } + + // Build switch statement + $switch = sprintf("switch (%s) {\n%s\n", '$name', implode("\n", $caseStatements)); + $switch .= sprintf("%sdefault:\n%sreturn parent::get(%s, %s);\n", $indent, str_repeat($indent, 2), '$name', '$params'); + $switch .= "}\n\n"; + + // Build get() method + $nameParam = new CodeGen\PhpParameter(); + $nameParam->setName('name'); + $defaultParams = new CodeGen\PhpParameterDefaultValue(); + $defaultParams->setValue(array()); + $paramsParam = new CodeGen\PhpParameter(); + $paramsParam->setName('params') + ->setType('array') + ->setDefaultValue($defaultParams); + + $get = new CodeGen\PhpMethod(); + $get->setName('get'); + $get->setParameters(array( + $nameParam, + $paramsParam, + )); + $get->setBody($switch); + + // Create getters for aliases + $aliasMethods = array(); + foreach ($aliases as $class => $classAliases) { + foreach ($classAliases as $alias) { + $aliasMethods[] = $this->getCodeGenMethodFromAlias($alias, $class); + } + } + + // Create class code generation object + $container = new CodeGen\PhpClass(); + $container->setName($this->containerClass) + ->setExtendedClass('DependencyInjectionContainer') + ->setMethod($get) + ->setMethods($getters) + ->setMethods($aliasMethods); + + // Create PHP file code generation object + $classFile = new CodeGen\PhpFile(); + $classFile->setUse('Zend\Di\DependencyInjectionContainer') + ->setClass($container); + + if (null !== $this->namespace) { + $classFile->setNamespace($this->namespace); + } + + if (null !== $filename) { + $classFile->setFilename($filename); + } + + return $classFile; + } + + /** + * Reduces aliases + * + * Takes alias list and reduces it to a 2-dimensional array of + * class names pointing to an array of aliases that resolve to + * it. + * + * @param array $aliasList + * @return array + */ + protected function reduceAliases(array $aliasList) + { + $reduced = array(); + $aliases = array_keys($aliasList); + foreach ($aliasList as $alias => $service) + { + if (in_array($service, $aliases)) { + do { + $service = $aliasList[$service]; + } while (in_array($service, $aliases)); + } + if (!isset($reduced[$service])) { + $reduced[$service] = array(); + } + $reduced[$service][] = $alias; + } + return $reduced; + } + + /** + * Create a PhpMethod code generation object named after a given alias + * + * @param string $alias + * @param class $class Class to which alias refers + * @return CodeGen\PhpMethod + */ + protected function getCodeGenMethodFromAlias($alias, $class) + { + $alias = $this->normalizeAlias($alias); + $method = new CodeGen\PhpMethod(); + $method->setName($alias) + ->setBody(sprintf('return $this->get(\'%s\');', $class)); + return $method; + } + + /** + * Normalize an alias to a getter method name + * + * @param string $alias + * @return string + */ + protected function normalizeAlias($alias) + { + $normalized = preg_replace('/[^a-zA-Z0-9]/', ' ', $alias); + $normalized = 'get' . str_replace(' ', '', ucwords($normalized)); + return $normalized; + } +} diff --git a/library/Zend/Di/Definition.php b/library/Zend/Di/Definition.php new file mode 100644 index 00000000000..35bfa4c2c7b --- /dev/null +++ b/library/Zend/Di/Definition.php @@ -0,0 +1,333 @@ +className = $className; + $this->injectibleMethods = new Methods(); + } + + /** + * Get the defined class name + * + * @return string + */ + public function getClass() + { + return $this->className; + } + + /** + * Provide a callback to use in order to get an instance + * + * @param callback $callback + * @return Definition + */ + public function setConstructorCallback($callback) + { + $this->constructorCallback = $callback; + return $this; + } + + /** + * Retrieve the constructor callback, if any + * + * @return false|callback + */ + public function getConstructorCallback() + { + return $this->constructorCallback; + } + + /** + * Do we define a constructor callback? + * + * @return bool + */ + public function hasConstructorCallback() + { + if (false === $this->constructorCallback) { + return false; + } + + return true; + } + + /** + * Set a constructor parameter + * + * @param string $name + * @param mixed $value + * @return Definition + */ + public function setParam($name, $value) + { + $this->constructorParams[$name] = $value; + $this->orderedConstructorParams = null; + return $this; + } + + /** + * Set all parameters at once + * + * @param array $params + * @return Definition + */ + public function setParams(array $params) + { + $this->constructorParams = $params; + $this->orderedConstructorParams = null; + return $this; + } + + /** + * Define the constructor parameter map + * + * Maps parameter names to position in order to specify argument order. + * + * @param array $map Map of name => position pairs for constructor arguments + * @return Definition + */ + public function setParamMap(array $map) + { + foreach ($map as $name => $position) { + if (!is_int($position) && !is_numeric($position)) { + throw new Exception\InvalidPositionException(); + } + if (!is_string($name) || empty($name)) { + throw new Exception\InvalidParamNameException(); + } + } + $positions = array_values($map); + sort($positions); + if (!empty($positions) && ($positions != range(0, count($positions) - 1))) { + throw new Exception\InvalidPositionException('Positions are non-sequential'); + } + $this->constructorParamMap = $map; + $this->orderedConstructorParams = null; + return $this; + } + + /** + * Retrieve constructor parameters + * + * Returns the constructor parameters in the order in which they should be + * passed to the constructor, as an indexed array. + * + * @return array + */ + public function getParams() + { + if (null !== $this->orderedConstructorParams) { + return $this->orderedConstructorParams; + } + + if (null === $this->constructorParamMap) { + $this->buildConstructorParamMapFromReflection(); + } + $map = $this->constructorParamMap; + + // Sort map, and flip such that positions become keys + asort($map); + $map = array_flip($map); + + $params = array(); + foreach ($map as $key) { + $value = isset($this->constructorParams[$key]) ? $this->constructorParams[$key] : null; + $params[] = $value; + } + + $this->orderedConstructorParams = $params; + return $params; + } + + /** + * Should the container return the same or different instances? + * + * @param bool $flag + * @return Definition + */ + public function setShared($flag = true) + { + $this->shareInstances = (bool) $flag; + return $this; + } + + /** + * Should the container return the same or different instances? + * + * @return bool + */ + public function isShared() + { + return $this->shareInstances; + } + + /** + * Add a tag + * + * Tags may be used by container-defined classes to retrieve dependencies, + * plugins, helpers, etc. + * + * @param string $tag + * @return Definition + */ + public function addTag($tag) + { + if (!is_string($tag) || empty($tag)) { + throw new Exception\InvalidArgumentException('Tag must be a string and non-empty'); + } + if (!in_array($tag, $this->tags)) { + $this->tags[] = $tag; + } + return $this; + } + + /** + * Add many tags at once + * + * @param array $tags + * @return Definition + */ + public function addTags(array $tags) + { + foreach ($tags as $tag) { + $this->addTag($tag); + } + return $this; + } + + /** + * Retrieve all tags associated with this class + * + * @return array + */ + public function getTags() + { + return $this->tags; + } + + /** + * Is the given tag associated with this definition + * + * @param string $tag + * @return bool + */ + public function hasTag($tag) + { + return (in_array($tag, $this->tags)); + } + + /** + * Add a method to be called and injected + * + * @param string $name + * @param array $args + * @return Definition + */ + public function addMethodCall($name, array $args) + { + $method = new Method($name, $args); + $this->injectibleMethods->insert($method); + return $this; + } + + /** + * Get collection of injectible methods + * + * @return InjectibleMethods + */ + public function getMethodCalls() + { + return $this->injectibleMethods; + } + + /** + * Build the constructor parameter map from Reflection + * + * @return void + */ + protected function buildConstructorParamMapFromReflection() + { + $class = new ReflectionClass($this->getClass()); + if (!$class->hasMethod('__construct')) { + $this->setParamMap(array()); + return; + } + $constructor = $class->getMethod('__construct'); + $parameters = $constructor->getParameters(); + $params = array(); + foreach ($parameters as $param) { + $params[$param->getName()] = $param->getPosition(); + } + $this->setParamMap($params); + } +} diff --git a/library/Zend/Di/DependencyDefinition.php b/library/Zend/Di/DependencyDefinition.php new file mode 100644 index 00000000000..d8b094c64ff --- /dev/null +++ b/library/Zend/Di/DependencyDefinition.php @@ -0,0 +1,35 @@ + position pairs for constructor arguments + */ + public function setParamMap(array $map); + public function getParams(); + + public function setShared($flag = true); + public function isShared(); + + public function addTag($tag); + public function addTags(array $tags); + public function getTags(); + public function hasTag($tag); + + public function addMethodCall($name, array $args); + /** + * @return InjectibleMethods + */ + public function getMethodCalls(); +} diff --git a/library/Zend/Di/DependencyEnabled.php b/library/Zend/Di/DependencyEnabled.php new file mode 100644 index 00000000000..a13bb0ec561 --- /dev/null +++ b/library/Zend/Di/DependencyEnabled.php @@ -0,0 +1,8 @@ +injector = $di; + return $this; + } + + /** + * Get DI manager for this service locator + * + * If none has been injected, injects an instance of DependencyInjector. + * + * @return DependencyInjection + */ + public function getInjector() + { + if (null === $this->injector) { + $this->setInjector(new DependencyInjector()); + } + return $this->injector; + } + + /** + * Retrieve a registered service + * + * Attempts to retrieve a registered service. If none matching is found, it + * then looks in the dependency injector to see if it can find it; if so, + * it returns it. + * + * @param string $name + * @param array $params + * @return mixed + */ + public function get($name, array $params = array()) + { + $service = parent::get($name, $params); + if (null !== $service) { + return $service; + } + + return $this->getInjector()->get($name, $params); + } +} diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php new file mode 100644 index 00000000000..f30e458be3e --- /dev/null +++ b/library/Zend/Di/DependencyInjector.php @@ -0,0 +1,298 @@ +instances[$name])) { + return $this->instances[$name]; + } + + return $this->newInstance($name, $params); + } + + /** + * Retrieve a new instance of a class + * + * Forces retrieval of a discrete instance of the given class, using the + * constructor parameters provided. + * + * @param mixed $name Class name or service alias + * @param array $params Parameters to pass to the constructor + * @return object|null + */ + public function newInstance($name, array $params = array()) + { + // Class name provided + if (isset($this->definitions[$name])) { + $instance = $this->getInstanceFromDefinition($this->definitions[$name], $params); + if ($this->definitions[$name]->isShared()) { + $this->instances[$name] = $instance; + } + return $instance; + } + + // Alias resolved to definition + if (false !== $definition = $this->getDefinitionFromAlias($name)) { + $class = $definition->getClass(); + if (isset($this->instances[$class])) { + return $this->instances[$class]; + } + $instance = $this->getInstanceFromDefinition($definition, $params); + if ($definition->isShared()) { + $this->instances[$name] = $instance; + $this->instances[$class] = $instance; + } + return $instance; + } + + // Test if class exists, and return instance if possible + if (!class_exists($name)) { + return null; + } + $instance = $this->getInstanceFromClassName($name, $params); + return $instance; + } + + /** + * Set many definitions at once + * + * String keys will be used as the $serviceName argument to + * {@link setDefinition()}. + * + * @param array|Traversable $definitions Iterable Definition objects + * @return DependencyInjector + */ + public function setDefinitions($definitions) + { + foreach ($definitions as $name => $definition) { + if (!is_string($name) || is_numeric($name) || empty($name)) { + $name = null; + } + $this->setDefinition($definition, $name); + } + return $this; + } + + /** + * Add a definition, optionally with a service name alias + * + * @param DependencyDefinition $definition + * @param string $serviceName + * @return DependencyInjector + */ + public function setDefinition(DependencyDefinition $definition, $serviceName = null) + { + $className = $definition->getClass(); + $this->definitions[$className] = $definition; + if (null !== $serviceName && !empty($serviceName)) { + $this->aliases[$serviceName] = $className; + } + return $this; + } + + /** + * Alias a given service/class name so that it may be referenced by another name + * + * @param string $alias + * @param string $serviceName Class name or service/alias name + * @return DependencyInjector + */ + public function setAlias($alias, $serviceName) + { + $this->aliases[$alias] = $serviceName; + return $this; + } + + /** + * Retrieve aggregated definitions + * + * @return array + */ + public function getDefinitions() + { + return $this->definitions; + } + + /** + * Retrieve defined aliases + * + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Get an object instance based on a Definition object + * + * @param DependencyDefinition $definition + * @param array $params + * @return object + */ + protected function getInstanceFromDefinition(DependencyDefinition $definition, array $params) + { + $class = $definition->getClass(); + $params = array_merge($definition->getParams(), $params); + + if ($definition->hasConstructorCallback()) { + $object = $this->getInstanceFromCallback($definition->getConstructorCallback(), $params); + } else { + $object = $this->getInstanceFromClassName($class, $params); + } + $this->injectMethods($object, $definition); + return $object; + } + + /** + * Resolve a Definition class based on the alias provided + * + * @param string $name + * @return false|DependencyDefinition + */ + protected function getDefinitionFromAlias($name) + { + if (!isset($this->aliases[$name])) { + return false; + } + + $service = $this->aliases[$name]; + if (!isset($this->definitions[$service])) { + return $this->getDefinitionFromAlias($service); + } + + return $this->definitions[$service]; + } + + /** + * Retrieve a class instance based on class name + * + * Any parameters provided will be used as constructor arguments. If any + * given parameter is a DependencyReference object, it will be fetched + * from the container so that the instance may be injected. + * + * @param string $class + * @param array $params + * @return object + */ + protected function getInstanceFromClassName($class, array $params) + { + // Hack to avoid Reflection in most common use cases + switch (count($params)) { + case 0: + return new $class(); + case 1: + $param = array_shift($params); + if (null === $param) { + return new $class(); + } + if ($param instanceof DependencyReference) { + $param = $this->get($param->getServiceName()); + } + return new $class($param); + case 2: + $param1 = array_shift($params); + if ($param1 instanceof DependencyReference) { + $param1 = $this->get($param1->getServiceName()); + } + $param2 = array_shift($params); + if ($param2 instanceof DependencyReference) { + $param2 = $this->get($param2->getServiceName()); + } + return new $class($param1, $param2); + default: + $params = $this->resolveReferences($params); + $r = new ReflectionClass($class); + return $r->newInstanceArgs($params); + } + } + + /** + * Get an object instance from the defined callback + * + * @param callback $callback + * @param array $params + * @return object + * @throws Exception\InvalidCallbackException + */ + protected function getInstanceFromCallback($callback, array $params) + { + if (!is_callable($callback)) { + throw new Exception\InvalidCallbackException('An invalid constructor callback was provided'); + } + $params = $this->resolveReferences($params); + return call_user_func_array($callback, $params); + } + + /** + * Resolve parameters referencing other services + * + * @param array $params + * @return array + */ + protected function resolveReferences(array $params) + { + foreach ($params as $key => $value) { + if ($value instanceof DependencyReference) { + $params[$key] = $this->get($value->getServiceName()); + } + } + return $params; + } + + /** + * Call setter methods in order to inject dependencies + * + * @param object $object + * @param DependencyDefinition $definition + * @return void + */ + protected function injectMethods($object, DependencyDefinition $definition) + { + foreach ($definition->getMethodCalls() as $name => $info) + { + if (!method_exists($object, $name)) { + continue; + } + + $params = $info->getArgs(); + foreach ($params as $key => $param) { + if ($param instanceof DependencyReference) { + $params[$key] = $this->get($param->getServiceName()); + } + } + call_user_func_array(array($object, $name), $params); + } + } +} diff --git a/library/Zend/Di/DependencyReference.php b/library/Zend/Di/DependencyReference.php new file mode 100644 index 00000000000..be0e72d0a5c --- /dev/null +++ b/library/Zend/Di/DependencyReference.php @@ -0,0 +1,8 @@ +name = $name; + $this->args = $args; + } + + /** + * Retrieve the method name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Retrieve the arguments to pass to the method + * + * @return array + */ + public function getArgs() + { + return $this->args; + } +} diff --git a/library/Zend/Di/Methods.php b/library/Zend/Di/Methods.php new file mode 100644 index 00000000000..4404cddf5b3 --- /dev/null +++ b/library/Zend/Di/Methods.php @@ -0,0 +1,75 @@ +methods[] = $method; + } + + /** + * Return the current method object + * + * @return InjectibleMethod + */ + public function current() + { + return current($this->methods); + } + + /** + * Return the current method's name + * + * @return string + */ + public function key() + { + $method = $this->current(); + return $method->getName(); + } + + /** + * Iterator: Move to the next item in the list + * + * @return void + */ + public function next() + { + return next($this->methods); + } + + /** + * Iterator: Reset the pointer + * + * @return void + */ + public function rewind() + { + return reset($this->methods); + } + + /** + * Iterator: Is the current index valid? + * + * @return bool + */ + public function valid() + { + return (current($this->methods) !== false); + } +} diff --git a/library/Zend/Di/Reference.php b/library/Zend/Di/Reference.php new file mode 100644 index 00000000000..ca9d13d1355 --- /dev/null +++ b/library/Zend/Di/Reference.php @@ -0,0 +1,41 @@ +name = $serviceName; + } + + /** + * Retrieve service name + * + * @return string + */ + public function getServiceName() + { + return $this->name; + } +} diff --git a/library/Zend/Di/ServiceLocation.php b/library/Zend/Di/ServiceLocation.php new file mode 100644 index 00000000000..ee0cd9ded5a --- /dev/null +++ b/library/Zend/Di/ServiceLocation.php @@ -0,0 +1,8 @@ + + * protected $map = array('foo' => 'getFoo'); + * + * + * When encountered, the return value of that method will be used. + * + * Methods mapped in this way may expect a single, array argument, the + * $params passed to {@link get()}, if any. + * + * @var array + */ + protected $map = array(); + + /** + * Registered services and cached values + * + * @var array + */ + protected $services = array(); + + /** + * Register a service with the locator + * + * @param string $name + * @param mixed $service + * @return ServiceLocator + */ + public function set($name, $service) + { + $this->services[$name] = $service; + return $this; + } + + /** + * Retrieve a registered service + * + * Tests first if a value is registered for the service, and, if so, + * returns it. + * + * If the value returned is a non-object callback or closure, the return + * value is retrieved, stored, and returned. Parameters passed to the method + * are passed to the callback, but only on the first retrieval. + * + * If the service requested matches a method in the method map, the return + * value of that method is returned. Parameters are passed to the matching + * method. + * + * @param string $name + * @param array $params + * @return mixed + */ + public function get($name, array $params = array()) + { + if (!isset($this->services[$name])) { + if (!isset($this->map[$name])) { + return null; + } + $method = $this->map[$name]; + return $this->$method($params); + } + + $service = $this->services[$name]; + if ($service instanceof \Closure + || (!is_object($service) && is_callable($service)) + ) { + $this->services[$name] = $service = call_user_func_array($service, $params); + } + + return $service; + } +} diff --git a/tests/Zend/Di/ConfigurationTest.php b/tests/Zend/Di/ConfigurationTest.php new file mode 100644 index 00000000000..c96e562934f --- /dev/null +++ b/tests/Zend/Di/ConfigurationTest.php @@ -0,0 +1,180 @@ +getConfig(); + $di = new DependencyInjector(); + $dibuilder = new Configuration($di); + $dibuilder->fromArray($config); + $this->assertObjectGraph($di); + } + + public function testCanCreateObjectGraphFromZendConfig() + { + $config = new Config($this->getConfig()); + $di = new DependencyInjector(); + $dibuilder = new Configuration($di); + $dibuilder->fromConfig($config); + $this->assertObjectGraph($di); + } + + public function testCanCreateObjectGraphFromIniConfig() + { + $config = new IniConfig(__DIR__ . '/_files/config.ini', 'testing'); + $di = new DependencyInjector(); + $dibuilder = new Configuration($di); + $dibuilder->fromConfig($config); + $this->assertObjectGraph($di); + } + + public function testCanCreateObjectGraphFromXmlConfig() + { + $config = new XmlConfig(__DIR__ . '/_files/config.xml', 'testing'); + $di = new DependencyInjector(); + $dibuilder = new Configuration($di); + $dibuilder->fromConfig($config); + $this->assertObjectGraph($di); + } + + public function testCanCreateObjectGraphFromYamlConfig() + { + $config = new YamlConfig(__DIR__ . '/_files/config.yml', 'testing'); + $di = new DependencyInjector(); + $dibuilder = new Configuration($di); + $dibuilder->fromConfig($config); + $this->assertObjectGraph($di); + } + + public function testCanCreateObjectGraphFromJsonConfig() + { + $config = new JsonConfig(__DIR__ . '/_files/config.json', 'testing'); + $di = new DependencyInjector(); + $dibuilder = new Configuration($di); + $dibuilder->fromConfig($config); + $this->assertObjectGraph($di); + } + + public function assertObjectGraph($di) + { + $inspected = $di->get('inspected'); + $injected = $di->get('injected'); + $struct = $di->get('struct'); + $params = $di->get('params'); + + $this->assertInstanceOf('ZendTest\Di\TestAsset\InspectedClass', $inspected); + $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectedMethod', $injected); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $struct); + $this->assertInstanceOf('ZendTest\Di\TestAsset\DummyParams', $params); + + $this->assertEquals('FOO', $inspected->foo); + $this->assertEquals('BAZ', $inspected->baz); + + $this->assertEquals(array('params' => array('param1' => 'foo', 'param2' => 'bar', 'foo' => 'bar')), (array) $params); + + $this->assertEquals(array('param1' => 'foo', 'param2' => 'bar'), (array) $struct); + $this->assertSame($params, $injected->object, sprintf('Params: %s; Injected: %s', var_export($params, 1), var_export($injected, 1))); + } + + public function getConfig() + { + return array( + 'definitions' => array( + array( + 'class' => 'ZendTest\Di\TestAsset\Struct', + 'params' => array( + 'param1' => 'foo', + 'param2' => 'bar', + ), + 'param_map' => array( + 'param1' => 0, + 'param2' => 1, + ), + ), + array( + 'class' => 'ZendTest\Di\TestAsset\DummyParams', + 'constructor_callback' => array( + 'class' => 'ZendTest\Di\TestAsset\StaticFactory', + 'method' => 'factory', + ), + 'params' => array( + 'struct' => array('__reference' => 'struct'), + 'params' => array('foo' => 'bar'), + ), + 'param_map' => array( + 'struct' => 0, + 'params' => 1, + ), + ), + array( + 'class' => 'ZendTest\Di\TestAsset\InjectedMethod', + 'methods' => array( + array( + 'name' => 'setObject', + 'args' => array( + array('__reference' => 'params'), + ), + ), + ), + ), + array( + 'class' => 'ZendTest\Di\TestAsset\InspectedClass', + 'params' => array( + 'baz' => 'BAZ', + 'foo' => 'FOO', + ), + ), + ), + 'aliases' => array( + 'struct' => 'ZendTest\Di\TestAsset\Struct', + 'params' => 'ZendTest\Di\TestAsset\DummyParams', + 'injected' => 'ZendTest\Di\TestAsset\InjectedMethod', + 'inspected' => 'ZendTest\Di\TestAsset\InspectedClass', + ), + ); + /* + return array( + 'definitions' => array( + array( + 'class' => 'className', + 'constructor_callback' => false, + // or string, or array; if array, 'class' and 'method' + // strings + 'params' => array( + 'name' => 'value', + // if value is an array, look for '__reference' key, + // and, if found, create a Reference object + ), + 'param_map' => array( + ), + 'tags' => array(), + 'shared' => true, + 'methods' => array( + array( + 'name' => 'method_name', + 'args' => array( /* ... * / ), + // if value is an array, look for '__reference' + // key, and, if found, create a Reference object + ), + ), + ), + ), + 'aliases' => array( + 'alias' => 'target', + ), + ); + */ + } +} diff --git a/tests/Zend/Di/ContainerBuilderTest.php b/tests/Zend/Di/ContainerBuilderTest.php new file mode 100644 index 00000000000..229956a3d03 --- /dev/null +++ b/tests/Zend/Di/ContainerBuilderTest.php @@ -0,0 +1,263 @@ +tmpFile = false; + $this->di = new DependencyInjector; + } + + public function tearDown() + { + if ($this->tmpFile) { + unlink($this->tmpFile); + $this->tmpFile = false; + } + } + + public function getTmpFile() + { + $this->tmpFile = tempnam(sys_get_temp_dir(), 'zdi'); + return $this->tmpFile; + } + + public function createDefinitions() + { + $inspect = new Definition('ZendTest\Di\TestAsset\InspectedClass'); + $inspect->setParam('foo', new Reference('composed')) + ->setParam('baz', 'BAZ'); + $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setParam('param1', 'foo') + ->setParam('param2', new Reference('inspect')); + $this->di->setDefinition($composed, 'composed') + ->setDefinition($inspect, 'inspect') + ->setDefinition($struct, 'struct'); + } + + public function buildContainerClass($name = 'Application') + { + $this->createDefinitions(); + $builder = new ContainerBuilder($this->di); + $builder->setContainerClass($name); + $builder->getCodeGenerator($this->getTmpFile())->write(); + $this->assertFileExists($this->tmpFile); + } + + public function testCreatesContainerClassFromConfiguredDependencyInjector() + { + $this->buildContainerClass(); + + $tokens = token_get_all(file_get_contents($this->tmpFile)); + $count = count($tokens); + $found = false; + $value = false; + for ($i = 0; $i < $count; $i++) { + $token = $tokens[$i]; + if (is_string($token)) { + continue; + } + if ("T_CLASS" == token_name($token[0])) { + $found = true; + $value = false; + do { + $i++; + $token = $tokens[$i]; + if (is_string($token)) { + $id = null; + } else { + list($id, $value) = $token; + } + } while (($i < $count) && (token_name($id) != 'T_STRING')); + break; + } + } + $this->assertTrue($found, "Class token not found"); + $this->assertContains('Application', $value); + } + + public function testCreatesContainerClassWithCasesForEachService() + { + $this->buildContainerClass(); + + $tokens = token_get_all(file_get_contents($this->tmpFile)); + $count = count($tokens); + $services = array(); + for ($i = 0; $i < $count; $i++) { + $token = $tokens[$i]; + if (is_string($token)) { + continue; + } + if ('T_CASE' == token_name($token[0])) { + do { + $i++; + if ($i >= $count) { + break; + } + $token = $tokens[$i]; + if (is_string($token)) { + $id = $token; + } else { + $id = $token[0]; + } + } while (($i < $count) && ($id != T_CONSTANT_ENCAPSED_STRING)); + if (is_array($token)) { + $services[] = preg_replace('/\\\'/', '', $token[1]); + } + } + } + $expected = array( + 'composed', + 'ZendTest\Di\TestAsset\ComposedClass', + 'inspect', + 'ZendTest\Di\TestAsset\InspectedClass', + 'struct', + 'ZendTest\Di\TestAsset\Struct', + ); + $this->assertEquals(count($expected), count($services), var_export($services, 1)); + foreach ($expected as $service) { + $this->assertContains($service, $services); + } + } + + public function testCreatesContainerClassWithMethodsForEachServiceAndAlias() + { + $this->buildContainerClass(); + $tokens = token_get_all(file_get_contents($this->tmpFile)); + $count = count($tokens); + $methods = array(); + for ($i = 0; $i < $count; $i++) { + $token = $tokens[$i]; + if (is_string($token)) { + continue; + } + if ("T_FUNCTION" == token_name($token[0])) { + $value = false; + do { + $i++; + $token = $tokens[$i]; + if (is_string($token)) { + $id = null; + } else { + list($id, $value) = $token; + } + } while (($i < $count) && (token_name($id) != 'T_STRING')); + if ($value) { + $methods[] = $value; + } + } + } + $expected = array( + 'get', + 'getZendTestDiTestAssetComposedClass', + 'getComposed', + 'getZendTestDiTestAssetInspectedClass', + 'getInspect', + 'getZendTestDiTestAssetStruct', + 'getStruct', + ); + $this->assertEquals(count($expected), count($methods), var_export($methods, 1)); + foreach ($expected as $method) { + $this->assertContains($method, $methods); + } + } + + public function testAllowsRetrievingClassFileCodeGenerationObject() + { + $this->createDefinitions(); + $builder = new ContainerBuilder($this->di); + $builder->setContainerClass('Application'); + $codegen = $builder->getCodeGenerator(); + $this->assertInstanceOf('Zend\CodeGenerator\Php\PhpFile', $codegen); + } + + public function testCanSpecifyNamespaceForGeneratedPhpClassfile() + { + $this->createDefinitions(); + $builder = new ContainerBuilder($this->di); + $builder->setContainerClass('Context') + ->setNamespace('Application'); + $codegen = $builder->getCodeGenerator(); + $this->assertEquals('Application', $codegen->getNamespace()); + } + + /** + * @group nullargs + */ + public function testNullAsOnlyArgumentResultsInEmptyParameterList() + { + $def = new Definition('ZendTest\Di\TestAsset\OptionalArg'); + $def->setParamMap(array('param' => 0)) + ->setParam('param', null); + $this->di->setDefinition($def, 'optional'); + + $builder = new ContainerBuilder($this->di); + $builder->setContainerClass('Container'); + $codeGen = $builder->getCodeGenerator(); + $classBody = $codeGen->generate(); + $this->assertNotContains('NULL)', $classBody, $classBody); + } + + /** + * @group nullargs + */ + public function testNullAsLastArgumentsResultsInTruncatedParameterList() + { + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $dummy = new Definition('ZendTest\Di\TestAsset\DummyParams'); + $dummy->setConstructorCallback(array('ZendTest\Di\TestAsset\StaticFactory', 'factory')) + ->setParam('struct', new Reference('struct')) + ->setParam('params', null) + ->setParamMap(array('struct' => 0, 'params' => 1)); + $this->di->setDefinition($struct, 'struct') + ->setDefinition($dummy, 'dummy'); + + $builder = new ContainerBuilder($this->di); + $builder->setContainerClass('Container'); + $codeGen = $builder->getCodeGenerator(); + $classBody = $codeGen->generate(); + $this->assertNotContains('NULL)', $classBody, $classBody); + } + + /** + * @group nullargs + */ + public function testNullArgumentsResultInEmptyMethodParameterList() + { + $def = new Definition('ZendTest\Di\TestAsset\OptionalArg'); + $def->setParamMap(array('param' => 0)) + ->addMethodCall('inject', array(null, null)); + $this->di->setDefinition($def, 'optional'); + + $builder = new ContainerBuilder($this->di); + $builder->setContainerClass('Container'); + $codeGen = $builder->getCodeGenerator(); + $classBody = $codeGen->generate(); + $this->assertNotContains('NULL)', $classBody, $classBody); + } + + public function testClassNamesInstantiatedDirectlyShouldBeFullyQualified() + { + $this->createDefinitions(); + $builder = new ContainerBuilder($this->di); + $builder->setContainerClass('Context') + ->setNamespace('Application'); + $content = $builder->getCodeGenerator()->generate(); + $count = substr_count($content, '\ZendTest\Di\TestAsset\\'); + $this->assertEquals(3, $count, $content); + $this->assertNotContains('\\\\', $content); + } +} diff --git a/tests/Zend/Di/DefinitionTest.php b/tests/Zend/Di/DefinitionTest.php new file mode 100644 index 00000000000..761e813b23f --- /dev/null +++ b/tests/Zend/Di/DefinitionTest.php @@ -0,0 +1,206 @@ +definition = new Definition(__CLASS__); + } + + public function testCanRetrieveConfiguredClassName() + { + $this->assertEquals(__CLASS__, $this->definition->getClass()); + } + + public function testParamsAreEmptyByDefault() + { + foreach ($this->definition->getParams() as $param) { + $this->assertNull($param); + } + } + + public function testParamsAreReturnedInConstructorOrderWhenNoParamMapProvided() + { + $def = new Definition('ZendTest\Di\TestAsset\InspectedClass'); + $def->setParam('baz', 'BAZ'); + $def->setParam('foo', 'FOO'); + $expected = array( + 'FOO', + 'BAZ', + ); + $this->assertEquals($expected, $def->getParams()); + } + + public function testParamsAreReturnedInParamMapOrderIfSpecified() + { + $def = new Definition('ZendTest\Di\TestAsset\InspectedClass'); + $def->setParam('baz', 'BAZ'); + $def->setParam('foo', 'FOO'); + $def->setParamMap(array( + 'baz' => 0, + 'foo' => 1, + )); + $expected = array( + 'BAZ', + 'FOO', + ); + $this->assertEquals($expected, $def->getParams()); + } + + public function testSpecifyingAParamMultipleTimesOverwrites() + { + $def = new Definition('ZendTest\Di\TestAsset\InspectedClass'); + $def->setParam('baz', 'BAZ'); + $def->setParam('foo', 'FOO'); + $def->setParam('baz', 'baz'); + $def->setParam('foo', 'foo'); + $def->setParamMap(array( + 'baz' => 0, + 'foo' => 1, + )); + $expected = array( + 'baz', + 'foo', + ); + $this->assertEquals($expected, $def->getParams()); + } + + public function testCanSpecifyManyParamsAtOnce() + { + $params = array( + 'foo' => 'FOO', + 'bar' => 'BAR', + ); + $map = array('foo' => 0, 'bar' => 1); + $this->definition->setParams($params) + ->setParamMap($map); + $this->assertEquals(array_values($params), $this->definition->getParams()); + } + + public function testSettingParamMapWithNonNumericPositionsRaisesException() + { + $this->setExpectedException('Zend\Di\Exception\InvalidPositionException'); + $this->definition->setParamMap(array( + 'foo' => 0, + 'bar' => 'bar', + 'baz' => 2, + )); + } + + public function testSettingParamMapWithNonStringNameRaisesException() + { + $this->setExpectedException('Zend\Di\Exception\InvalidParamNameException'); + $this->definition->setParamMap(array( + 'foo' => 0, + 1 => 1, + 'baz' => 2, + )); + } + + public function testSettingParamMapWithInvalidPositionsRaisesException() + { + $this->setExpectedException('Zend\Di\Exception\InvalidPositionException', 'non-sequential'); + $this->definition->setParamMap(array( + 'foo' => 0, + 'bar' => 3, + 'baz' => 2, + )); + } + + public function testSharedByDefault() + { + $this->assertTrue($this->definition->isShared()); + } + + public function testCanOverrideSharedFlag() + { + $this->definition->setShared(false); + $this->assertFalse($this->definition->isShared()); + } + + public function testAddingMethodCallsAggregates() + { + $this->definition->addMethodCall('foo', array()); + $this->definition->addMethodCall('bar', array('bar')); + $methods = $this->definition->getMethodCalls(); + $this->assertInstanceOf('Zend\Di\InjectibleMethods', $methods); + foreach ($methods as $name => $method) { + switch ($name) { + case 'foo': + $this->assertSame(array(), $method->getArgs()); + break; + case 'bar': + $this->assertSame(array('bar'), $method->getArgs()); + break; + default: + $this->fail('Unexpected method encountered'); + } + } + } + + public function testCanAddSingleTags() + { + $this->definition->addTag('foo'); + $this->assertTrue($this->definition->hasTag('foo')); + } + + /** + * @dataProvider invalidTags + */ + public function testPassingInvalidTagRaisesException($tag) + { + $this->setExpectedException('Zend\Di\Exception\InvalidArgumentException', 'Tag'); + $this->definition->addTag($tag); + } + + public function invalidTags() + { + return array( + array(1), + array(1.0), + array(false), + array(new \stdClass), + array(array()), + ); + } + + public function testHasTagReturnsFalseWhenTagNotPresent() + { + $this->assertFalse($this->definition->hasTag('foo')); + } + + public function testCanAddManyTagsAtOnce() + { + $tags = array( + 'foo', + 'bar', + 'baz', + ); + $this->definition->addTags($tags); + $this->assertEquals($tags, $this->definition->getTags()); + } + + public function testNoConstructorCallbackByDefault() + { + $this->assertFalse($this->definition->hasConstructorCallback()); + } + + public function testReturnsTrueForHasConstructorCallbackWhenOneProvided() + { + $callback = function () {}; + $this->definition->setConstructorCallback($callback); + $this->assertTrue($this->definition->hasConstructorCallback()); + } + + public function testCanSetConstructorCallback() + { + $callback = function () {}; + $this->definition->setConstructorCallback($callback); + $this->assertSame($callback, $this->definition->getConstructorCallback()); + } +} diff --git a/tests/Zend/Di/DependencyInjectionContainerTest.php b/tests/Zend/Di/DependencyInjectionContainerTest.php new file mode 100644 index 00000000000..ea6dea55d53 --- /dev/null +++ b/tests/Zend/Di/DependencyInjectionContainerTest.php @@ -0,0 +1,57 @@ +di = new DependencyInjectionContainer(); + } + + public function testComposesDependencyInjectorInstanceByDefault() + { + $this->assertInstanceOf('Zend\Di\DependencyInjector', $this->di->getInjector()); + } + + public function testCanComposeInCustomDiInstance() + { + $di = new DependencyInjector(); + $this->di->setInjector($di); + $this->assertSame($di, $this->di->getInjector()); + } + + public function testGetFallsBacktoInjectorWhenUnableToFindService() + { + $di = $this->di->getInjector(); + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $di->setDefinition($def, 'struct'); + + $test = $this->di->get('struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + $this->assertEquals('foo', $test->param1); + $this->assertEquals('bar', $test->param2); + } + + public function testRegisteredServicesArePreferredOverInjectorProvidedServices() + { + $di = $this->di->getInjector(); + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $di->setDefinition($def, 'struct'); + + $explicit = new TestAsset\Struct('FOO', 'BAR'); + $this->di->set('struct', $explicit); + + $test = $this->di->get('struct'); + $this->assertSame($explicit, $test); + } +} diff --git a/tests/Zend/Di/DependencyInjectorTest.php b/tests/Zend/Di/DependencyInjectorTest.php new file mode 100644 index 00000000000..fabb08930bb --- /dev/null +++ b/tests/Zend/Di/DependencyInjectorTest.php @@ -0,0 +1,348 @@ +di = new DependencyInjector; + } + + public function testPassingInvalidDefinitionRaisesException() + { + $definitions = array('foo'); + $this->setExpectedException('PHPUnit_Framework_Error'); + $this->di->setDefinitions($definitions); + } + + public function testGetRetrievesObjectWithMatchingClassDefinition() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def); + $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + $this->assertEquals('foo', $test->param1); + $this->assertEquals('bar', $test->param2); + } + + public function testGetRetrievesSameInstanceOnSubsequentCalls() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def); + $first = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $second = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $first); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $second); + $this->assertSame($first, $second); + } + + public function testGetCanRetrieveByProvidedServiceName() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def, 'struct'); + $test = $this->di->get('struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + $this->assertEquals('foo', $test->param1); + $this->assertEquals('bar', $test->param2); + } + + public function testGetCanRetrieveByClassNameWhenServiceNameIsAlsoProvided() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def, 'struct'); + $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + $this->assertEquals('foo', $test->param1); + $this->assertEquals('bar', $test->param2); + } + + public function testGetReturnsNewInstanceIfDefinitionSharedFlagIsFalse() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar') + ->setShared(false); + $this->di->setDefinition($def); + $first = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $second = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $first); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $second); + $this->assertNotSame($first, $second); + } + + public function testNewInstanceForcesNewObjectInstanceEvenWhenSharedFlagIsTrue() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar') + ->setShared(true); + $this->di->setDefinition($def); + $first = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $second = $this->di->newInstance('ZendTest\Di\TestAsset\Struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $first); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $second); + $this->assertNotSame($first, $second); + } + + public function testGetNewInstanceByServiceName() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def, 'struct'); + $test = $this->di->newInstance('struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + } + + public function testGetNewInstanceByAlias() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def); + $this->di->setAlias('struct', 'ZendTest\Di\TestAsset\Struct'); + + $test = $this->di->newInstance('struct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + } + + public function testCanAliasToServiceName() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def, 'struct'); + $this->di->setAlias('mystruct', 'struct'); + + $test = $this->di->newInstance('mystruct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + } + + public function testCanApplyMultipleAliasesPerDefinition() + { + $def = new Definition('ZendTest\Di\TestAsset\Struct'); + $def->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $this->di->setDefinition($def); + $this->di->setAlias('mystruct', 'ZendTest\Di\TestAsset\Struct'); + $this->di->setAlias('struct', 'ZendTest\Di\TestAsset\Struct'); + + $test1 = $this->di->newInstance('struct'); + $test2 = $this->di->newInstance('mystruct'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test1); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test2); + $this->assertSame($test1, $test2); + } + + public function testGetReturnsNullIfNoMatchingClassOrDefinitionFound() + { + $classes = get_declared_classes(); + $class = array_pop($classes) . uniqid(); + while (in_array($class, $classes)) { + $class .= uniqid(); + } + + $this->assertNull($this->di->get($class)); + } + + public function testNewInstanceReturnsNullIfNoMatchingClassOrDefinitionFound() + { + $classes = get_declared_classes(); + $class = array_pop($classes) . uniqid(); + while (in_array($class, $classes)) { + $class .= uniqid(); + } + + $this->assertNull($this->di->newInstance($class)); + } + + public function testUnmatchedReferenceInDefinitionParametersResultsInNullInjection() + { + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setParam('param1', 'foo') + ->setParam('param2', new Reference('voodoo')); + $this->di->setDefinition($struct); + $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $this->assertNull($test->param2); + } + + public function testReferenceInDefinitionParametersCausesInjection() + { + $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setParam('param1', 'foo') + ->setParam('param2', new Reference('ZendTest\Di\TestAsset\ComposedClass')); + $this->di->setDefinition($composed) + ->setDefinition($struct); + + $diStruct = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $diCompose = $this->di->get('ZendTest\Di\TestAsset\ComposedClass'); + $this->assertSame($diCompose, $diStruct->param2); + } + + public function testReferenceToServiceNameInDefinitionParametersCausesInjection() + { + $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setParam('param1', 'foo') + ->setParam('param2', new Reference('composed')); + $this->di->setDefinition($composed, 'composed') + ->setDefinition($struct); + + $diStruct = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $diCompose = $this->di->get('ZendTest\Di\TestAsset\ComposedClass'); + $this->assertSame($diCompose, $diStruct->param2); + } + + public function testCanInjectNestedItems() + { + $inspect = new Definition('ZendTest\Di\TestAsset\InspectedClass'); + $inspect->setParam('foo', new Reference('composed')) + ->setParam('baz', 'BAZ'); + $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setParam('param1', 'foo') + ->setParam('param2', new Reference('inspect')); + $this->di->setDefinition($composed, 'composed') + ->setDefinition($inspect, 'inspect') + ->setDefinition($struct, 'struct'); + + $diStruct = $this->di->get('struct'); + $diInspect = $this->di->get('inspect'); + $diCompose = $this->di->get('composed'); + $this->assertSame($diCompose, $diInspect->foo); + $this->assertSame($diInspect, $diStruct->param2); + } + + public function testLastDefinitionOfSameClassNameWins() + { + $struct1 = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct1->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $struct2 = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct2->setParam('param1', 'FOO') + ->setParam('param2', 'BAR'); + $this->di->setDefinition($struct1) + ->setDefinition($struct2); + $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); + $this->assertEquals('FOO', $test->param1); + $this->assertEquals('BAR', $test->param2); + } + + public function testLastDefinitionOfSameClassNameWinsEvenWhenAddedWithDifferentServiceNames() + { + $struct1 = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct1->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $struct2 = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct2->setParam('param1', 'FOO') + ->setParam('param2', 'BAR'); + $this->di->setDefinition($struct1, 'struct1') + ->setDefinition($struct2, 'struct2'); + $test = $this->di->get('struct1'); + $this->assertEquals('FOO', $test->param1); + $this->assertEquals('BAR', $test->param2); + } + + public function testCanInjectSpecificMethods() + { + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setParam('param1', 'foo') + ->setParam('param2', 'bar'); + $def = new Definition('ZendTest\Di\TestAsset\InjectedMethod'); + $def->addMethodCall('setObject', array(new Reference('struct'))); + $this->di->setDefinition($def) + ->setDefinition($struct, 'struct'); + + $test = $this->di->get('ZendTest\Di\TestAsset\InjectedMethod'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectedMethod', $test); + $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test->object); + $this->assertSame($test->object, $this->di->get('struct')); + } + + /** + * @dataProvider constructorCallbacks + */ + public function testUsesConstructorCallbackIfDefinedInDefinition($callback) + { + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setConstructorCallback($callback); + $struct->setParam('params', array('foo' => 'bar')); + $struct->setParamMap(array('params' => 0)); + $this->di->setDefinition($struct, 'struct'); + $test = $this->di->get('struct'); + $this->assertInstanceOf('stdClass', $test); + $this->assertNotInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + $this->assertEquals('bar', $test->foo); + } + + public function constructorCallbacks() + { + return array( + array(__CLASS__ . '::structFactory'), + array(array($this, 'structFactory')), + array(function (array $params) { + $o = (object) $params; + return $o; + }), + ); + } + + public static function structFactory(array $params) + { + $o = (object) $params; + return $o; + } + + public function testRaisesExceptionForInvalidConstructorCallback() + { + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setConstructorCallback(array('foo' => 'bar')); + $struct->setParam('params', array('foo' => 'bar')); + $struct->setParamMap(array('params' => 0)); + $this->di->setDefinition($struct, 'struct'); + + $this->setExpectedException('Zend\Di\Exception\InvalidCallbackException'); + $test = $this->di->get('struct'); + } + + public function testConstructorCallbackAllowsPassingReferences() + { + $struct = new Definition('ZendTest\Di\TestAsset\Struct'); + $struct->setConstructorCallback(function ($params) { + $o = new \stdClass; + $o->params = $params; + return $o; + }); + $struct->setParam('params', new Reference('params')); + $struct->setParamMap(array('params' => 0)); + $this->di->setDefinition($struct, 'struct'); + + $params = new Definition('ZendTest\Di\TestAsset\DummyParams'); + $params->setParam('params', array('bar' => 'baz')) + ->setParamMap(array('params' => 0)); + $this->di->setDefinition($params, 'params'); + + $testStruct = $this->di->get('struct'); + $testParams = $this->di->get('params'); + $this->assertSame($testParams, $testStruct->params, sprintf('Params: %s; struct: %s', var_export($testParams, 1), var_export($testStruct, 1))); + } + + /** + * @todo tests for recursive DI calls + */ +} diff --git a/tests/Zend/Di/MethodTest.php b/tests/Zend/Di/MethodTest.php new file mode 100644 index 00000000000..016b5e29928 --- /dev/null +++ b/tests/Zend/Di/MethodTest.php @@ -0,0 +1,32 @@ +assertEquals($name, $method->getName()); + } + + public function testMethodReturnsArgsPassedToConstructor() + { + $name = uniqid(); + $args = array( + 'foo', + new \stdClass, + true, + null, + 0, + 0.00, + array('bar'), + ); + $method = new Method($name, $args); + $this->assertEquals($args, $method->getArgs()); + } +} diff --git a/tests/Zend/Di/MethodsTest.php b/tests/Zend/Di/MethodsTest.php new file mode 100644 index 00000000000..0a202e0e0f8 --- /dev/null +++ b/tests/Zend/Di/MethodsTest.php @@ -0,0 +1,61 @@ +methods = new Methods(); + } + + public function testInsertAcceptsMethodObject() + { + $this->methods->insert(new Method('foo', array())); + // No test; simply ensuring no exceptions/errors + } + + public function invalidMethodArguments() + { + return array( + array(null), + array(true), + array(false), + array(1), + array(1.0), + array(array()), + array(new \stdClass()), + ); + } + + /** + * @dataProvider invalidMethodArguments + */ + public function testInsertRaisesExceptionIfInvalidArgumentProvidedForMethodObject($arg) + { + $this->setExpectedException('PHPUnit_Framework_Error'); + $this->methods->insert($arg); + } + + public function testIterationReturnsMethodNameForKey() + { + $method = new Method('foo', array()); + $this->methods->insert($method); + foreach ($this->methods as $key => $value) { + } + $this->assertEquals('foo', $key); + } + + public function testIterationReturnsMethodObjectForValue() + { + $method = new Method('foo', array()); + $this->methods->insert($method); + foreach ($this->methods as $key => $value) { + } + $this->assertEquals($method, $value); + } +} diff --git a/tests/Zend/Di/ReferenceTest.php b/tests/Zend/Di/ReferenceTest.php new file mode 100644 index 00000000000..3f45d64b22e --- /dev/null +++ b/tests/Zend/Di/ReferenceTest.php @@ -0,0 +1,16 @@ +assertEquals($name, $ref->getServiceName()); + } +} diff --git a/tests/Zend/Di/ServiceLocatorTest.php b/tests/Zend/Di/ServiceLocatorTest.php new file mode 100644 index 00000000000..8631c766d27 --- /dev/null +++ b/tests/Zend/Di/ServiceLocatorTest.php @@ -0,0 +1,102 @@ +services = new ServiceLocator(); + } + + public function testRetrievingUnknownServiceResultsInNullValue() + { + $this->assertNull($this->services->get('foo')); + } + + public function testCanRetrievePreviouslyRegisteredServices() + { + $s = new \stdClass; + $this->services->set('foo', $s); + $test = $this->services->get('foo'); + $this->assertSame($s, $test); + } + + public function testRegisteringAServiceUnderAnExistingNameOverwrites() + { + $s = new \stdClass(); + $t = new \stdClass(); + $this->services->set('foo', $s); + $this->services->set('foo', $t); + $test = $this->services->get('foo'); + $this->assertSame($t, $test); + } + + public function testRetrievingAServiceMultipleTimesReturnsSameInstance() + { + $s = new \stdClass(); + $this->services->set('foo', $s); + $test1 = $this->services->get('foo'); + $test2 = $this->services->get('foo'); + $this->assertSame($s, $test1); + $this->assertSame($s, $test2); + $this->assertSame($test1, $test2); + } + + public function testRegisteringCallbacksReturnsReturnValueWhenServiceRequested() + { + $this->services->set('foo', function() { + $object = new \stdClass(); + $object->foo = 'FOO'; + return $object; + }); + $test = $this->services->get('foo'); + $this->assertInstanceOf('stdClass', $test); + $this->assertEquals('FOO', $test->foo); + } + + public function testReturnValueOfCallbackIsCachedBetweenRequestsToService() + { + $this->services->set('foo', function() { + $object = new \stdClass(); + $object->foo = 'FOO'; + return $object; + }); + $test1 = $this->services->get('foo'); + $test2 = $this->services->get('foo'); + $this->assertEquals('FOO', $test1->foo); + $this->assertSame($test1, $test2); + } + + public function testParametersArePassedToCallbacks() + { + $this->services->set('foo', function() { + $object = new \stdClass(); + $object->params = func_get_args(); + return $object; + }); + + $params = array('foo', 'bar'); + $test = $this->services->get('foo', $params); + $this->assertEquals($params, $test->params); + } + + public function testGetProxiesToMappedMethods() + { + $sc = new TestAsset\ContainerExtension(); + $sc->foo = 'FOO'; + $this->assertEquals('FOO', $sc->get('foo')); + } + + public function testProxiedMethodsReceiveParametersPassedToGet() + { + $sc = new TestAsset\ContainerExtension(); + $params = array('foo' => 'FOO'); + $test = $sc->get('params', $params); + $this->assertEquals($params, $test); + $this->assertEquals($params, $sc->params); + } +} diff --git a/tests/Zend/Di/TestAsset/ComposedClass.php b/tests/Zend/Di/TestAsset/ComposedClass.php new file mode 100644 index 00000000000..f0d97aadfbb --- /dev/null +++ b/tests/Zend/Di/TestAsset/ComposedClass.php @@ -0,0 +1,6 @@ + 'getFoo', + 'params' => 'getParams', + ); + + public function getFoo() + { + return $this->foo; + } + + public function getParams(array $params) + { + $this->params = $params; + return $this->params; + } +} diff --git a/tests/Zend/Di/TestAsset/DummyParams.php b/tests/Zend/Di/TestAsset/DummyParams.php new file mode 100644 index 00000000000..362cd6cc87d --- /dev/null +++ b/tests/Zend/Di/TestAsset/DummyParams.php @@ -0,0 +1,12 @@ +params = $params; + } +} diff --git a/tests/Zend/Di/TestAsset/InjectedMethod.php b/tests/Zend/Di/TestAsset/InjectedMethod.php new file mode 100644 index 00000000000..60a1c6e6d0e --- /dev/null +++ b/tests/Zend/Di/TestAsset/InjectedMethod.php @@ -0,0 +1,10 @@ +object = $o; + } +} diff --git a/tests/Zend/Di/TestAsset/InspectedClass.php b/tests/Zend/Di/TestAsset/InspectedClass.php new file mode 100644 index 00000000000..c00d5e94000 --- /dev/null +++ b/tests/Zend/Di/TestAsset/InspectedClass.php @@ -0,0 +1,11 @@ +foo = $foo; + $this->baz = $baz; + } +} diff --git a/tests/Zend/Di/TestAsset/OptionalArg.php b/tests/Zend/Di/TestAsset/OptionalArg.php new file mode 100644 index 00000000000..35beb2451bc --- /dev/null +++ b/tests/Zend/Di/TestAsset/OptionalArg.php @@ -0,0 +1,15 @@ +param = $param; + } + + public function inject($param1 = null, $param2 = null) + { + } +} + diff --git a/tests/Zend/Di/TestAsset/StaticFactory.php b/tests/Zend/Di/TestAsset/StaticFactory.php new file mode 100644 index 00000000000..b2af52c44a3 --- /dev/null +++ b/tests/Zend/Di/TestAsset/StaticFactory.php @@ -0,0 +1,11 @@ +param1 = $param1; + $this->param2 = $param2; + } +} diff --git a/tests/Zend/Di/_files/config.ini b/tests/Zend/Di/_files/config.ini new file mode 100644 index 00000000000..7d45d42d763 --- /dev/null +++ b/tests/Zend/Di/_files/config.ini @@ -0,0 +1,27 @@ +[testing] +definitions.struct.class = "ZendTest\Di\TestAsset\Struct" +definitions.struct.params.param1 = "foo" +definitions.struct.params.param2 = "bar" +definitions.struct.param_map.param1 = 0 +definitions.struct.param_map.param2 = 1 + +definitions.params.class = "ZendTest\Di\TestAsset\DummyParams" +definitions.params.constructor_callback.class = "ZendTest\Di\TestAsset\StaticFactory" +definitions.params.constructor_callback.method = "factory" +definitions.params.params.struct.__reference = "struct" +definitions.params.params.params.foo = "bar" +definitions.params.param_map.struct = 0 +definitions.params.param_map.params = 1 + +definitions.injected.class = "ZendTest\Di\TestAsset\InjectedMethod" +definitions.injected.methods.set_object.name = "setObject" +definitions.injected.methods.set_object.args.1.__reference = "params" + +definitions.inspected.class = "ZendTest\Di\TestAsset\InspectedClass" +definitions.inspected.params.baz = "BAZ" +definitions.inspected.params.foo = "FOO" + +aliases.struct = "ZendTest\Di\TestAsset\Struct" +aliases.params = "ZendTest\Di\TestAsset\DummyParams" +aliases.injected = "ZendTest\Di\TestAsset\InjectedMethod" +aliases.inspected = "ZendTest\Di\TestAsset\InspectedClass" diff --git a/tests/Zend/Di/_files/config.json b/tests/Zend/Di/_files/config.json new file mode 100644 index 00000000000..8d881e40ad8 --- /dev/null +++ b/tests/Zend/Di/_files/config.json @@ -0,0 +1,54 @@ +{ + "testing": { + "definitions": [ + { + "class": "ZendTest\\Di\\TestAsset\\Struct", + "params": { + "param1": "foo", + "param2": "bar" + }, + "param_map": { + "param1": 0, + "param2": 1 + } + }, + { + "class": "ZendTest\\Di\\TestAsset\\DummyParams", + "constructor_callback": { + "class": "ZendTest\\Di\\TestAsset\\StaticFactory", + "method": "factory" + }, + "params": { + "struct": { "__reference": "struct" }, + "params": {"foo": "bar"} + }, + "param_map": { + "struct": 0, + "params": 1 + } + }, + { + "class": "ZendTest\\Di\\TestAsset\\InjectedMethod", + "methods": [ + { + "name": "setObject", + "args": [ { "__reference": "params" } ] + } + ] + }, + { + "class": "ZendTest\\Di\\TestAsset\\InspectedClass", + "params": { + "baz": "BAZ", + "foo": "FOO" + } + } + ], + "aliases": { + "struct": "ZendTest\\Di\\TestAsset\\Struct", + "params": "ZendTest\\Di\\TestAsset\\DummyParams", + "injected": "ZendTest\\Di\\TestAsset\\InjectedMethod", + "inspected": "ZendTest\\Di\\TestAsset\\InspectedClass" + } + } +} diff --git a/tests/Zend/Di/_files/config.xml b/tests/Zend/Di/_files/config.xml new file mode 100644 index 00000000000..0a61c5c04ed --- /dev/null +++ b/tests/Zend/Di/_files/config.xml @@ -0,0 +1,53 @@ + + + + + ZendTest\Di\TestAsset\Struct + + foo + bar + + + 0 + 1 + + + + ZendTest\Di\TestAsset\DummyParams + + ZendTest\Di\TestAsset\StaticFactory + factory + + + <__reference>struct + bar + + + 0 + 1 + + + + ZendTest\Di\TestAsset\InjectedMethod + + + setObject + <__reference>params + + + + + ZendTest\Di\TestAsset\InspectedClass + + BAZ + FOO + + + + ZendTest\Di\TestAsset\Struct + ZendTest\Di\TestAsset\DummyParams + ZendTest\Di\TestAsset\InjectedMethod + ZendTest\Di\TestAsset\InspectedClass + + + diff --git a/tests/Zend/Di/_files/config.yml b/tests/Zend/Di/_files/config.yml new file mode 100644 index 00000000000..bb61128ef7a --- /dev/null +++ b/tests/Zend/Di/_files/config.yml @@ -0,0 +1,42 @@ +testing: + definitions: + - + class: ZendTest\Di\TestAsset\Struct + params: + param1: foo + param2: bar + param_map: + param1: 0 + param2: 1 + - + class: ZendTest\Di\TestAsset\DummyParams + constructor_callback: + class: ZendTest\Di\TestAsset\StaticFactory + method: factory + params: + struct: + __reference: struct + params: + foo: bar + param_map: + struct: 0 + params: 1 + - + class: ZendTest\Di\TestAsset\InjectedMethod + methods: + setObject: + name: setObject + args: + - + __reference: params + - + class: ZendTest\Di\TestAsset\InspectedClass + params: + baz: BAZ + foo: FOO + aliases: + struct: ZendTest\Di\TestAsset\Struct + params: ZendTest\Di\TestAsset\DummyParams + injected: ZendTest\Di\TestAsset\InjectedMethod + inspected: ZendTest\Di\TestAsset\InspectedClass + From c0d7ac558a501a38038573cd6ddc999ad3398617 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 16 May 2011 15:57:50 -0500 Subject: [PATCH 23/45] Remove "tag" feature - Not necessary due to nature of aliasing - Fixed issue in test autoloader; should not autoload PHPUnit under 3.5+, as it has its own autoloader --- library/Zend/Di/Configuration.php | 3 - library/Zend/Di/Definition.php | 62 ------------------- library/Zend/Di/DependencyDefinition.php | 5 -- .../Exception/InvalidParamNameException.php | 3 +- .../Di/Exception/InvalidPositionException.php | 3 +- tests/Zend/Di/DefinitionTest.php | 42 ------------- tests/_autoload.php | 2 - 7 files changed, 2 insertions(+), 118 deletions(-) diff --git a/library/Zend/Di/Configuration.php b/library/Zend/Di/Configuration.php index 9e993ba4668..2e8c4d57cbc 100644 --- a/library/Zend/Di/Configuration.php +++ b/library/Zend/Di/Configuration.php @@ -129,9 +129,6 @@ protected function buildDefinition(array $values) case 'param_map': $definition->setParamMap($value); break; - case 'tags': - $definition->addTags($value); - break; case 'shared': $definition->setShared((bool) $value); break; diff --git a/library/Zend/Di/Definition.php b/library/Zend/Di/Definition.php index 35bfa4c2c7b..d3ef4fd58b3 100644 --- a/library/Zend/Di/Definition.php +++ b/library/Zend/Di/Definition.php @@ -56,13 +56,6 @@ class Definition implements DependencyDefinition */ protected $shareInstances = true; - /** - * Tags by which this definition may be alternately retrieved - * - * @var array - */ - protected $tags = array(); - /** * Create a definition for the given class name * @@ -231,61 +224,6 @@ public function isShared() return $this->shareInstances; } - /** - * Add a tag - * - * Tags may be used by container-defined classes to retrieve dependencies, - * plugins, helpers, etc. - * - * @param string $tag - * @return Definition - */ - public function addTag($tag) - { - if (!is_string($tag) || empty($tag)) { - throw new Exception\InvalidArgumentException('Tag must be a string and non-empty'); - } - if (!in_array($tag, $this->tags)) { - $this->tags[] = $tag; - } - return $this; - } - - /** - * Add many tags at once - * - * @param array $tags - * @return Definition - */ - public function addTags(array $tags) - { - foreach ($tags as $tag) { - $this->addTag($tag); - } - return $this; - } - - /** - * Retrieve all tags associated with this class - * - * @return array - */ - public function getTags() - { - return $this->tags; - } - - /** - * Is the given tag associated with this definition - * - * @param string $tag - * @return bool - */ - public function hasTag($tag) - { - return (in_array($tag, $this->tags)); - } - /** * Add a method to be called and injected * diff --git a/library/Zend/Di/DependencyDefinition.php b/library/Zend/Di/DependencyDefinition.php index d8b094c64ff..191a779df36 100644 --- a/library/Zend/Di/DependencyDefinition.php +++ b/library/Zend/Di/DependencyDefinition.php @@ -22,11 +22,6 @@ public function getParams(); public function setShared($flag = true); public function isShared(); - public function addTag($tag); - public function addTags(array $tags); - public function getTags(); - public function hasTag($tag); - public function addMethodCall($name, array $args); /** * @return InjectibleMethods diff --git a/library/Zend/Di/Exception/InvalidParamNameException.php b/library/Zend/Di/Exception/InvalidParamNameException.php index dac202185f8..a9958e2cf5b 100644 --- a/library/Zend/Di/Exception/InvalidParamNameException.php +++ b/library/Zend/Di/Exception/InvalidParamNameException.php @@ -1,8 +1,7 @@ definition->addTag('foo'); - $this->assertTrue($this->definition->hasTag('foo')); - } - - /** - * @dataProvider invalidTags - */ - public function testPassingInvalidTagRaisesException($tag) - { - $this->setExpectedException('Zend\Di\Exception\InvalidArgumentException', 'Tag'); - $this->definition->addTag($tag); - } - - public function invalidTags() - { - return array( - array(1), - array(1.0), - array(false), - array(new \stdClass), - array(array()), - ); - } - - public function testHasTagReturnsFalseWhenTagNotPresent() - { - $this->assertFalse($this->definition->hasTag('foo')); - } - - public function testCanAddManyTagsAtOnce() - { - $tags = array( - 'foo', - 'bar', - 'baz', - ); - $this->definition->addTags($tags); - $this->assertEquals($tags, $this->definition->getTags()); - } - public function testNoConstructorCallbackByDefault() { $this->assertFalse($this->definition->hasConstructorCallback()); diff --git a/tests/_autoload.php b/tests/_autoload.php index 6a497bc9715..789ce874292 100644 --- a/tests/_autoload.php +++ b/tests/_autoload.php @@ -39,8 +39,6 @@ function ZendTest_Autoloader($class) $ns = array_shift($segments); switch ($ns) { - case 'PHPUnit': - return include_once str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; case 'Zend': $file = dirname(__DIR__) . '/library/Zend/'; break; From 438944df8101a07ceb36669878931c6da132c353 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 17 May 2011 09:06:09 -0500 Subject: [PATCH 24/45] Merged introspection features into Definition class - Added setClass() and toArray() to DependencyDefinition interface - Added implementations for above to Definition class, along with tests --- library/Zend/Di/Definition.php | 48 +++++++++++++++++++++++- library/Zend/Di/DependencyDefinition.php | 8 ++++ tests/Zend/Di/DefinitionTest.php | 41 ++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/library/Zend/Di/Definition.php b/library/Zend/Di/Definition.php index d3ef4fd58b3..aa1f0dd8a34 100644 --- a/library/Zend/Di/Definition.php +++ b/library/Zend/Di/Definition.php @@ -64,7 +64,7 @@ class Definition implements DependencyDefinition */ public function __construct($className) { - $this->className = $className; + $this->setClass($className); $this->injectibleMethods = new Methods(); } @@ -78,6 +78,18 @@ public function getClass() return $this->className; } + /** + * Set the class name the definition describes + * + * @param string $name + * @return Definition + */ + public function setClass($name) + { + $this->className = $name; + return $this; + } + /** * Provide a callback to use in order to get an instance * @@ -248,6 +260,40 @@ public function getMethodCalls() return $this->injectibleMethods; } + public function toArray() + { + $params = array(); + foreach ($this->getParams() as $name => $value) { + if ($value instanceof Reference) { + $value = array('__reference' => $value->getServiceName()); + } + $params[$name] = $value; + } + + $methods = array(); + foreach ($this->getMethodCalls() as $method) { + $args = array(); + foreach ($method->getArgs() as $key => $arg) { + if ($arg instanceof Reference) { + $args[$key] = array('__reference' => $arg->getServiceName()); + } else { + $args[$key] = $arg; + } + } + $methods[] = array( + 'name' => $method->getName(), + 'args' => $args, + ); + } + + return array( + 'class' => $this->getClass(), + 'methods' => $methods, + 'param_map' => ($this->constructorParamMap) ?: array(), + 'params' => $params, + ); + } + /** * Build the constructor parameter map from Reflection * diff --git a/library/Zend/Di/DependencyDefinition.php b/library/Zend/Di/DependencyDefinition.php index 191a779df36..5f3fd876fee 100644 --- a/library/Zend/Di/DependencyDefinition.php +++ b/library/Zend/Di/DependencyDefinition.php @@ -6,6 +6,7 @@ interface DependencyDefinition public function __construct($className); public function getClass(); + public function setClass($name); public function setConstructorCallback($callback); public function getConstructorCallback(); @@ -27,4 +28,11 @@ public function addMethodCall($name, array $args); * @return InjectibleMethods */ public function getMethodCalls(); + + /** + * Serialization + * + * @return array + */ + public function toArray(); } diff --git a/tests/Zend/Di/DefinitionTest.php b/tests/Zend/Di/DefinitionTest.php index a1c8e280f13..18bf166b628 100644 --- a/tests/Zend/Di/DefinitionTest.php +++ b/tests/Zend/Di/DefinitionTest.php @@ -17,6 +17,12 @@ public function testCanRetrieveConfiguredClassName() $this->assertEquals(__CLASS__, $this->definition->getClass()); } + public function testClassNameIsMutable() + { + $this->definition->setClass('foo'); + $this->assertEquals('foo', $this->definition->getClass()); + } + public function testParamsAreEmptyByDefault() { foreach ($this->definition->getParams() as $param) { @@ -143,6 +149,41 @@ public function testAddingMethodCallsAggregates() } } + public function testCanSerializeDefinitionToArray() + { + $definition = new Definition('Foo'); + $definition->setParams(array( + 'name' => 'foo', + 'class' => 'Foo', + 'object' => array('__reference' => 'Baz'), + )) + ->setParamMap(array( + 'name' => 1, + 'class' => 0, + 'object' => 2, + )) + ->addMethodCall('bar', array('one', 'two')) + ->addMethodCall('baz', array(array('__reference' => 'Bar'))); + $expected = array( + 'class' => 'Foo', + 'methods' => array( + array('name' => 'bar', 'args' => array('one', 'two')), + array('name' => 'baz', 'args' => array(array('__reference' => 'Bar'))), + ), + 'param_map' => array( + 'name' => 1, + 'class' => 0, + 'object' => 2, + ), + 'params' => array( + 'Foo', + 'foo', + array('__reference' => 'Baz'), + ), + ); + $this->assertEquals($expected, $definition->toArray()); + } + public function testNoConstructorCallbackByDefault() { $this->assertFalse($this->definition->hasConstructorCallback()); From 3e00613baa527715696ebac11ef1343d0a3c9509 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Mon, 23 May 2011 08:51:13 -0500 Subject: [PATCH 25/45] Zend\Di refactorings continued: - Created Zend\Mvc to move out Application specific elements - Renamed 'args' to 'params' for consistency - Incorporated DI generator from Proof-of-Concept --- library/Zend/Di/Configuration.php | 8 +- library/Zend/Di/ContainerBuilder.php | 2 +- library/Zend/Di/Definition.php | 18 +- library/Zend/Di/Definitions.php | 106 ++++++++++++ library/Zend/Di/DependencyDefinition.php | 2 +- library/Zend/Di/DependencyInjector.php | 4 +- library/Zend/Di/Generator/Configuration.php | 141 +++++++++++++++ library/Zend/Di/Generator/DefinitionProxy.php | 143 ++++++++++++++++ library/Zend/Di/Generator/Generator.php | 161 ++++++++++++++++++ library/Zend/Di/Generator/Introspector.php | 11 ++ .../Introspector/ConstructorInjection.php | 75 ++++++++ .../Introspector/InterfaceInjection.php | 82 +++++++++ .../Introspector/SetterInjection.php | 69 ++++++++ .../Zend/Di/Generator/ManagedDefinitions.php | 106 ++++++++++++ library/Zend/Di/Generator/TypeManager.php | 107 ++++++++++++ library/Zend/Di/Generator/TypeRegistry.php | 119 +++++++++++++ library/Zend/Di/InjectibleMethod.php | 4 +- library/Zend/Di/Method.php | 12 +- library/Zend/Mvc/Application/Application.php | 61 +++++++ .../Mvc/Application/BootstrapInterface.php | 8 + .../Zend/Mvc/Application/Configuration.php | 29 ++++ .../Zend/Mvc/Application/RunnerInterface.php | 8 + .../ServiceLocatorAwareInterface.php | 8 + .../Application/ServiceLocatorInterface.php | 8 + tests/Zend/Di/ConfigurationTest.php | 4 +- tests/Zend/Di/DefinitionTest.php | 8 +- tests/Zend/Di/MethodTest.php | 8 +- tests/Zend/Di/_files/config.ini | 2 +- tests/Zend/Di/_files/config.json | 2 +- tests/Zend/Di/_files/config.xml | 2 +- tests/Zend/Di/_files/config.yml | 2 +- 31 files changed, 1281 insertions(+), 39 deletions(-) create mode 100644 library/Zend/Di/Definitions.php create mode 100644 library/Zend/Di/Generator/Configuration.php create mode 100644 library/Zend/Di/Generator/DefinitionProxy.php create mode 100644 library/Zend/Di/Generator/Generator.php create mode 100644 library/Zend/Di/Generator/Introspector.php create mode 100644 library/Zend/Di/Generator/Introspector/ConstructorInjection.php create mode 100644 library/Zend/Di/Generator/Introspector/InterfaceInjection.php create mode 100644 library/Zend/Di/Generator/Introspector/SetterInjection.php create mode 100644 library/Zend/Di/Generator/ManagedDefinitions.php create mode 100644 library/Zend/Di/Generator/TypeManager.php create mode 100644 library/Zend/Di/Generator/TypeRegistry.php create mode 100644 library/Zend/Mvc/Application/Application.php create mode 100644 library/Zend/Mvc/Application/BootstrapInterface.php create mode 100644 library/Zend/Mvc/Application/Configuration.php create mode 100644 library/Zend/Mvc/Application/RunnerInterface.php create mode 100644 library/Zend/Mvc/Application/ServiceLocatorAwareInterface.php create mode 100644 library/Zend/Mvc/Application/ServiceLocatorInterface.php diff --git a/library/Zend/Di/Configuration.php b/library/Zend/Di/Configuration.php index 2e8c4d57cbc..f83276a3329 100644 --- a/library/Zend/Di/Configuration.php +++ b/library/Zend/Di/Configuration.php @@ -161,11 +161,11 @@ protected function buildMethods(DependencyDefinition $definition, array $methods continue; } $method = $methodDefinition['name']; - $args = array(); - if (isset($methodDefinition['args']) && is_array($methodDefinition['args'])) { - $args = $this->resolveReferences($methodDefinition['args']); + $params = array(); + if (isset($methodDefinition['params']) && is_array($methodDefinition['params'])) { + $params = $this->resolveReferences($methodDefinition['params']); } - $definition->addMethodCall($method, $args); + $definition->addMethodCall($method, $params); } } diff --git a/library/Zend/Di/ContainerBuilder.php b/library/Zend/Di/ContainerBuilder.php index 014096d637c..37d07516a78 100644 --- a/library/Zend/Di/ContainerBuilder.php +++ b/library/Zend/Di/ContainerBuilder.php @@ -118,7 +118,7 @@ public function getCodeGenerator($filename = null) $methods = ''; foreach ($definition->getMethodCalls() as $method) { $methodName = $method->getName(); - $methodParams = $method->getArgs(); + $methodParams = $method->getParams(); // Create method parameter representation foreach ($methodParams as $key => $param) { diff --git a/library/Zend/Di/Definition.php b/library/Zend/Di/Definition.php index aa1f0dd8a34..102d59b7d49 100644 --- a/library/Zend/Di/Definition.php +++ b/library/Zend/Di/Definition.php @@ -240,12 +240,12 @@ public function isShared() * Add a method to be called and injected * * @param string $name - * @param array $args + * @param array $params * @return Definition */ - public function addMethodCall($name, array $args) + public function addMethodCall($name, array $params) { - $method = new Method($name, $args); + $method = new Method($name, $params); $this->injectibleMethods->insert($method); return $this; } @@ -272,17 +272,17 @@ public function toArray() $methods = array(); foreach ($this->getMethodCalls() as $method) { - $args = array(); - foreach ($method->getArgs() as $key => $arg) { - if ($arg instanceof Reference) { - $args[$key] = array('__reference' => $arg->getServiceName()); + $methodParams = array(); + foreach ($method->getParams() as $key => $methodParam) { + if ($methodParam instanceof Reference) { + $methodParams[$key] = array('__reference' => $methodParam->getServiceName()); } else { - $args[$key] = $arg; + $methodParams[$key] = $methodParam; } } $methods[] = array( 'name' => $method->getName(), - 'args' => $args, + 'params' => $methodParams, ); } diff --git a/library/Zend/Di/Definitions.php b/library/Zend/Di/Definitions.php new file mode 100644 index 00000000000..856b2af9abe --- /dev/null +++ b/library/Zend/Di/Definitions.php @@ -0,0 +1,106 @@ + $value) { + switch (strtolower($key)) { + case 'class': + break; + case 'constructor_callback': + $callback = $value; + if (is_array($value) + && (isset($value['class']) && isset($value['method'])) + ) { + $callback = array($value['class'], $value['method']); + } + $definition->setConstructorCallback($callback); + break; + case 'params': + if (!is_array($value)) { + break; + } + $params = $this->resolveReferences($value); + $definition->setParams($params); + break; + case 'param_map': + $definition->setParamMap($value); + break; + case 'tags': + $definition->addTags($value); + break; + case 'shared': + $definition->setShared((bool) $value); + break; + case 'methods': + $this->buildMethods($definition, $value); + break; + default: + // ignore all other options + break; + } + } + + $this->addDefinition($definition); + } + + public function addDefinition(\Zend\Di\DependencyDefinition $definition) + { + $class = $definition->getClass(); + if ($this->hasDefinition($class)) { + throw new \InvalidArgumentException('A definition for this class already exist.'); + } + $this->definitions[$class] = $definition; + } + + public function hasDefinition($class) + { + return array_key_exists($class, $this->definitions); + } + + public function getDefinition($class) + { + return $this->definitions[$class]; + } + + public function mergeObjectConfiguration($objectConfiguration) + { + foreach ($objectConfiguration as $class => $configValues) { + if (!array_key_exists($class, $this->definitions)) { + continue; + } + $def = $this->definitions[$class]; + $def->setParams($configValues); + } + } + + public function toArray() + { + $defs = array(); + /* @var $definition \Zend\Di\Definition */ + foreach ($this->definitions as $definition) { + $defArray = $definition->toArray(); + $defs[] = $defArray; + } + return $defs; + } + +} diff --git a/library/Zend/Di/DependencyDefinition.php b/library/Zend/Di/DependencyDefinition.php index 5f3fd876fee..294cdbd24d0 100644 --- a/library/Zend/Di/DependencyDefinition.php +++ b/library/Zend/Di/DependencyDefinition.php @@ -23,7 +23,7 @@ public function getParams(); public function setShared($flag = true); public function isShared(); - public function addMethodCall($name, array $args); + public function addMethodCall($name, array $params); /** * @return InjectibleMethods */ diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php index f30e458be3e..fbbaf42757d 100644 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -233,7 +233,7 @@ protected function getInstanceFromClassName($class, array $params) return new $class($param1, $param2); default: $params = $this->resolveReferences($params); - $r = new ReflectionClass($class); + $r = new \ReflectionClass($class); return $r->newInstanceArgs($params); } } @@ -286,7 +286,7 @@ protected function injectMethods($object, DependencyDefinition $definition) continue; } - $params = $info->getArgs(); + $params = $info->getParams(); foreach ($params as $key => $param) { if ($param instanceof DependencyReference) { $params[$key] = $this->get($param->getServiceName()); diff --git a/library/Zend/Di/Generator/Configuration.php b/library/Zend/Di/Generator/Configuration.php new file mode 100644 index 00000000000..452067d1e61 --- /dev/null +++ b/library/Zend/Di/Generator/Configuration.php @@ -0,0 +1,141 @@ + $value) { + if (method_exists($this, 'set' . $name)) { + $this->{'set' . $name}($value); + } + } + } + + /** + * @return string $containerConfigurationPath + */ + public function getContainerConfigurationPath() + { + return $this->containerConfigurationPath; + } + + /** + * @param string $containerConfigurationPath + */ + public function setContainerConfigurationPath($containerConfigurationPath) + { + $this->containerConfigurationPath = $containerConfigurationPath; + return $this; + } + + /** + * @return string $mode + */ + public function getMode() + { + return $this->mode; + } + + /** + * @param string $mode + */ + public function setMode($mode) + { + $this->mode = $mode; + } + + /** + * @return string $developmentFileStatPath + */ + public function getDevelopmentFileStatPath() + { + return $this->developmentFileStatPath; + } + + /** + * @param string $developmentFileStatPath + */ + public function setDevelopmentFileStatPath($developmentFileStatPath) + { + $this->developmentFileStatPath = $developmentFileStatPath; + } + + /** + * @return array $managedDirectories + */ + public function getManagedDirectories() + { + return $this->managedDirectories; + } + + /** + * @param array $managedDirectories + */ + public function setManagedDirectories(array $managedDirectories) + { + $this->managedDirectories = $managedDirectories; + } + + /** + * @return array $managedNamespaces + */ + public function getManagedNamespaces() + { + return $this->managedNamespaces; + } + + /** + * @param array $managedNamespaces + */ + public function setManagedNamespaces(array $managedNamespaces) + { + $this->managedNamespaces = $managedNamespaces; + } + + public function getIntrospectors() + { + return array_keys($this->introspectors); + } + + public function setIntrospectors(array $introspectors) + { + $this->introspectors = array(); + foreach ($introspectors as $name => $value) { + if (is_int($name)) { + $this->introspectors[$value] = array(); + } elseif (is_string($name)) { + $this->introspectors[$name] = $value; + } + } + } + + public function getIntrospectionConfiguration($name) + { + return ((array_key_exists($name, $this->introspectors)) ? $this->introspectors[$name] : array()); + } + + public function setObjectConfigurations($objectConfigurations) + { + $this->objectConfigurations = $objectConfigurations; + } + + public function getObjectConfigurations() + { + return $this->objectConfigurations; + } + +} diff --git a/library/Zend/Di/Generator/DefinitionProxy.php b/library/Zend/Di/Generator/DefinitionProxy.php new file mode 100644 index 00000000000..474a7df67e7 --- /dev/null +++ b/library/Zend/Di/Generator/DefinitionProxy.php @@ -0,0 +1,143 @@ +definition = $definition; + } + + public function setClass($className) + { + $this->definition->setClass($className); + return $this; + } + + public function getClass() + { + return $this->definition->getClass(); + } + + public function setConstructorCallback($callback) + { + $this->definition->setConstructorCallback($callback); + return $this; + } + public function getConstructorCallback() + { + return $this->definition->getConstructorCallback(); + } + + public function hasConstructorCallback() + { + return $this->definition->hasConstructorCallback(); + } + + public function setParam($name, $value) + { + $this->definition->setParam($name, $value); + return $this; + } + + public function setParams(array $params) + { + $this->definition->setParams($params); + return $this; + } + + /** + * @param array $map Map of name => position pairs for constructor arguments + */ + public function setParamMap(array $map) + { + $this->definition->setParamMap($map); + return $this; + } + + public function getParams() + { + return $this->definition->getParams(); + } + + public function setShared($flag = true) + { + $this->definition->setShared($flag); + return $this; + } + + public function isShared() + { + return $this->definition->isShared(); + } + + + public function addTag($tag) + { + throw new \Exception('No Tags'); + } + + public function addTags(array $tags) + { + throw new \Exception('No Tags'); + } + + public function getTags() + { + throw new \Exception('No Tags'); + } + + public function hasTag($tag) + { + throw new \Exception('No Tags'); + } + + public function addMethodCall($name, array $args) + { + return $this->definition->addMethodCall($name, $args); + } + + /** + * @return InjectibleMethods + */ + public function getMethodCalls() {} + + public function toArray() + { + $params = array(); + foreach ($this->definition->constructorParams as $cParamName => $cParamValue) { + if ($cParamValue instanceof \Zend\Di\Reference) { + $cParamValue = array('__reference' => $cParamValue->getServiceName()); + } + $params[$cParamName] = $cParamValue; + } + + $methods = array(); + foreach ($this->definition->injectibleMethods as $method) { + $args = array(); + foreach ($method->getArgs() as $argKey => $arg) { + if ($arg instanceof \Zend\Di\Reference) { + $args[$argKey]['__reference'] = $arg->getServiceName(); + } else { + $args[$argKey][] = $arg; + } + } + $methods[] = array('name' => $method->getName(), 'args' => $args); + } + + return array( + 'class' => $this->definition->className, + 'methods' => $methods, + 'param_map' => ($this->definition->constructorParamMap) ?: array(), + 'params' => $params + ); + } + +} + diff --git a/library/Zend/Di/Generator/Generator.php b/library/Zend/Di/Generator/Generator.php new file mode 100644 index 00000000000..6af94cd1b0a --- /dev/null +++ b/library/Zend/Di/Generator/Generator.php @@ -0,0 +1,161 @@ +configuration = ($configuration) ?: new Configuration(); + $this->di = ($di) ?: new DependencyInjector(); + } + + public function configuration() + { + return $this->configuration; + } + + public function di() + { + return $this->di; + } + + protected function validateConfiguration() + { + if ($this->configuration->getContainerConfigurationPath() == null) { + throw new \Exception('A containerConfigurationPath is required by ' . __CLASS__); + } + + if ($this->mode == Configuration::MODE_DEVELOPMENT) { + // @todo dev time required configuration stuffs + } + } + + protected function processConfiguration() + { +// if (!isset($this->generatorConfig['classes'])) { +// throw new \Exception('A values for classes must exist'); +// } +// if (!isset($this->generatorConfig['classes']['namespaces']) && !isset($this->generatorConfig['classes']['directories'])) { +// throw new \Exception('Either a namespace or a directory must be provided for the classes configuration'); +// } +// +// if (!isset($this->generatorConfig['classes']['namespaces'])) { +// $this->generatorConfig['classes']['namespaces'] = array(); +// } +// if (!isset($this->generatorConfig['classes']['directories'])) { +// $this->generatorConfig['classes']['directories'] = array(); +// } + + } + + + + public function build() + { + // validate the config object ? + $this->validateConfiguration(); + + // process the config ? + $this->processConfiguration(); + + // check to see if there is a dev time file stat cache (performance during dev) + $developmentStatFilePath = $this->configuration->getDevelopmentFileStatPath(); + + if ($developmentStatFilePath !== null) { + if (!file_exists($developmentStatFilePath)) { + // if it doesnt exist, create it with empty values + if (file_put_contents($developmentStatFilePath, 'setFileStatInformation($developmentFileStatInfo); + } + + // create class manager instance, with provided namespaces and directories to manage + $classManager = new TypeManager( + $typeRegistry, + $this->configuration->getManagedNamespaces(), + $this->configuration->getManagedDirectories() + ); + + $classManager->manage(); + + if ($developmentStatFilePath !== null && $typeRegistry->hasFileStatUpdates()) { + file_put_contents($developmentStatFilePath, 'getFileStatInformation(), true) . ';'); + } + + // load the managed di definitions + $managedDefinitions = new ManagedDefinitions(); + $containerConfigPath = $this->configuration->getContainerConfigurationPath(); + if ($containerConfigPath == null) { + throw new \Exception('A container configuration path was not found.'); + } + if (!file_exists($containerConfigPath)) { + if (file_put_contents($containerConfigPath, 'addDefinitionFromArray($containerConfigArray); + } + } + + foreach ($this->configuration->getIntrospectors() as $introspectorName) { + $introspectorClass = 'Zend\Di\Introspector\\' . ucfirst($introspectorName); + /* @var Zend\Di\Introspector $introspector */ + $introspector = new $introspectorClass; + $introspector->setConfiguration($this->configuration->getIntrospectionConfiguration($introspectorName)); + $introspector->setManagedDefinitions($managedDefinitions); + $introspector->setTypeRegistry($typeRegistry); + $introspector->introspect(); + } + + $managedDefinitions->mergeObjectConfiguration($this->configuration->getObjectConfigurations()); + + file_put_contents($containerConfigPath, 'toArray(), true) . ';'); + + } + + public function bootstrap() + { + $this->validateConfiguration(); + + $configuration = new Configuration($this->di); + + if (file_exists($this->generatorConfig['containerConfigurationPath'])) { + $containerConfiguration = include $this->generatorConfig['containerConfigurationPath']; + $configuration->fromArray($containerConfiguration); + } + + $mode = $this->configuration->getMode(); + + if ($mode == Configuration::MODE_PRODUCTION) { + //if (isset($this->config['definitionPath']) && file_exists($this->config['']))) + } + + if ($mode == Configuration::MODE_DEVELOPMENT) { + $this->build(); + } + } + +} \ No newline at end of file diff --git a/library/Zend/Di/Generator/Introspector.php b/library/Zend/Di/Generator/Introspector.php new file mode 100644 index 00000000000..97a7f8a4584 --- /dev/null +++ b/library/Zend/Di/Generator/Introspector.php @@ -0,0 +1,11 @@ +configuration = $configuration; + } + + public function setManagedDefinitions(\Zend\Di\Generator\ManagedDefinitions $managedDefinitions) + { + $this->managedDefinitions = $managedDefinitions; + } + + public function setTypeRegistry(\Zend\Di\Generator\TypeRegistry $classRegistry) + { + $this->typeRegistry = $classRegistry; + } + + public function introspect() + { + foreach ($this->typeRegistry as $type) { + + try { + $refClass = new \ReflectionClass($type); + //echo 'Reflecting ' . $type . PHP_EOL; + if ($refClass->hasMethod('__construct')) { + + $refConstructor = $refClass->getMethod('__construct'); + if ($refParameters = $refConstructor->getParameters()) { + + //echo ' Found injectable __construct ' . $type . PHP_EOL; + + if ($this->managedDefinitions->hasDefinition($type)) { + $definition = $this->managedDefinitions->getDefinition($type); + } else { + $definition = new \Zend\Di\Generator\DefinitionProxy(new \Zend\Di\Definition($type)); + $this->managedDefinitions->addDefinition($definition); + } + + $paramMaps = array(); + $params = array(); + foreach ($refParameters as $refParam) { + $paramMaps[$refParam->getName()] = $refParam->getPosition(); + + if ($refTypeClass = $refParam->getClass()) { + + //echo ' Param type: ' . $refTypeClass->getName() . PHP_EOL; + $params[$refParam->getName()] = new \Zend\Di\Reference($refTypeClass->getName()); + } + } + $definition->setParamMap($paramMaps); + if ($params) { + $definition->setParams($params); + } + } + } + } catch (\ReflectionException $e) { + throw new \Exception('An unmanaged type was found as a dependency'); + } + + } + } + +} diff --git a/library/Zend/Di/Generator/Introspector/InterfaceInjection.php b/library/Zend/Di/Generator/Introspector/InterfaceInjection.php new file mode 100644 index 00000000000..de96780c9a7 --- /dev/null +++ b/library/Zend/Di/Generator/Introspector/InterfaceInjection.php @@ -0,0 +1,82 @@ +interfaces = $configuration; + } + + public function setManagedDefinitions(\Zend\Di\Generator\ManagedDefinitions $managedDefinitions) + { + $this->managedDefinitions = $managedDefinitions; + } + + public function setTypeRegistry(\Zend\Di\Generator\TypeRegistry $classRegistry) + { + $this->typeRegistry = $classRegistry; + } + + public function introspect() + { + $ifaceParams = array(); + + foreach ($this->interfaces as $interface) { + if (!interface_exists($interface, false)) { + throw new \Exception('An unmanaged interface was provided'); + } + + $refInterface = new \ReflectionClass($interface); + + $ifaceParams[$refInterface->getName()] = array(); + + foreach ($refInterface->getMethods() as $refMethod) { + $refParameters = $refMethod->getParameters(); + + //$paramMaps = array(); + $params = array(); + foreach ($refParameters as $refParam) { + //$paramMaps[$refParam->getName()] = $refParam->getPosition(); + + if ($refTypeClass = $refParam->getClass()) { + //echo ' Param type: ' . $refTypeClass->getName() . PHP_EOL; + $params[] = new \Zend\Di\Reference($refTypeClass->getName()); + } + } + } + + $ifaceParams[$refInterface->getName()][$refMethod->getName()] = $params; + + } + + + + foreach ($this->typeRegistry as $type) { + foreach (array_keys($ifaceParams) as $currentInterface) { + if (in_array($currentInterface, class_implements($type, false))) { + //echo 'FOUND INTERFACE INJECTION TYPE ' . $type . ' FOR INTERFACE ' . $currentInterface . PHP_EOL; + + if ($this->managedDefinitions->hasDefinition($type)) { + $definition = $this->managedDefinitions->getDefinition($type); + } else { + $definition = new \Zend\Di\Generator\DefinitionProxy(new \Zend\Di\Definition($type)); + $this->managedDefinitions->addDefinition($definition); + } + + foreach ($ifaceParams[$currentInterface] as $methodName => $methodParams) { + $definition->addMethodCall($methodName, $methodParams); + } + + } + } + } + + } + +} diff --git a/library/Zend/Di/Generator/Introspector/SetterInjection.php b/library/Zend/Di/Generator/Introspector/SetterInjection.php new file mode 100644 index 00000000000..952da17be8b --- /dev/null +++ b/library/Zend/Di/Generator/Introspector/SetterInjection.php @@ -0,0 +1,69 @@ +configuration = $configuration; + } + + public function setManagedDefinitions(\Zend\Di\Generator\ManagedDefinitions $managedDefinitions) + { + $this->managedDefinitions = $managedDefinitions; + } + + public function setTypeRegistry(\Zend\Di\Generator\TypeRegistry $classRegistry) + { + $this->typeRegistry = $classRegistry; + } + + public function introspect() + { + foreach ($this->typeRegistry as $type) { + + try { + $refClass = new \ReflectionClass($type); + echo 'Reflecting ' . $type . PHP_EOL; + + foreach ($refClass->getMethods() as $refMethod) { + if (preg_match('#^set.*#', $refMethod->getName())) { + echo 'Found injectable method: ' . $refMethod->getName() . PHP_EOL; + + if ($this->managedDefinitions->hasDefinition($type)) { + $definition = $this->managedDefinitions->getDefinition($type); + } else { + $definition = new \Zend\Di\Generator\DefinitionProxy(new \Zend\Di\Definition($type)); + $this->managedDefinitions->addDefinition($definition); + } + + if ($refParameters = $refMethod->getParameters()) { + $params = array(); + foreach ($refParameters as $refParam) { + if ($refTypeClass = $refParam->getClass()) { + //echo ' Param type: ' . $refTypeClass->getName() . PHP_EOL; + $params[] = new \Zend\Di\Reference($refTypeClass->getName()); + } + } + $definition->addMethodCall($refMethod->getName(), $params); + } + } + } + + } catch (\ReflectionException $e) { + throw new \Exception('An unmanaged type was found as a dependency'); + } + + } + } + +} diff --git a/library/Zend/Di/Generator/ManagedDefinitions.php b/library/Zend/Di/Generator/ManagedDefinitions.php new file mode 100644 index 00000000000..a4c406ab91a --- /dev/null +++ b/library/Zend/Di/Generator/ManagedDefinitions.php @@ -0,0 +1,106 @@ + $value) { + switch (strtolower($key)) { + case 'class': + break; + case 'constructor_callback': + $callback = $value; + if (is_array($value) + && (isset($value['class']) && isset($value['method'])) + ) { + $callback = array($value['class'], $value['method']); + } + $definition->setConstructorCallback($callback); + break; + case 'params': + if (!is_array($value)) { + break; + } + $params = $this->resolveReferences($value); + $definition->setParams($params); + break; + case 'param_map': + $definition->setParamMap($value); + break; + case 'tags': + $definition->addTags($value); + break; + case 'shared': + $definition->setShared((bool) $value); + break; + case 'methods': + $this->buildMethods($definition, $value); + break; + default: + // ignore all other options + break; + } + } + + $this->addDefinition($definition); + } + + public function addDefinition(\Zend\Di\DependencyDefinition $definition) + { + $class = $definition->getClass(); + if ($this->hasDefinition($class)) { + throw new \InvalidArgumentException('A definition for this class already exist.'); + } + $this->definitions[$class] = $definition; + } + + public function hasDefinition($class) + { + return array_key_exists($class, $this->definitions); + } + + public function getDefinition($class) + { + return $this->definitions[$class]; + } + + public function mergeObjectConfiguration($objectConfiguration) + { + foreach ($objectConfiguration as $class => $configValues) { + if (!array_key_exists($class, $this->definitions)) { + continue; + } + $def = $this->definitions[$class]; + $def->setParams($configValues); + } + } + + public function toArray() + { + $defs = array(); + /* @var $definition \Zend\Di\Definition */ + foreach ($this->definitions as $definition) { + $defArray = $definition->toArray(); + $defs[] = $defArray; + } + return $defs; + } + +} diff --git a/library/Zend/Di/Generator/TypeManager.php b/library/Zend/Di/Generator/TypeManager.php new file mode 100644 index 00000000000..d47bf448574 --- /dev/null +++ b/library/Zend/Di/Generator/TypeManager.php @@ -0,0 +1,107 @@ +typeRegistry = $typeRegistry; + + // create directory iterator basd on namespace + $includePaths = explode(PATH_SEPARATOR, get_include_path()); + foreach ($namespaces as $namespace) { + $this->addNamespace($namespace); + } + + // create directory iterator based on explicit directory + foreach ($directories as $directory) { + $this->addDirectory($directory); + } + } + + public function addNamespace($namespace) + { + $namespaceAsDirectory = ltrim(str_replace('\\', DIRECTORY_SEPARATOR, $namespace), DIRECTORY_SEPARATOR); + if (($directory = stream_resolve_include_path($namespaceAsDirectory)) === false) { + throw new \Exception('Namespace not located in include_path'); + } else { + $this->managedNamespaces[$directory] = $namespace; + //$this->directoryIterators[] = new \RecursiveDirectoryIterator($directory); + } + } + + + /** + * There is an interesting problem with managing directories. Classes need to be handled by autoloading, + * since when they are reflected later, will need to be autoloaded if they are not known. In some cases + * we will have outside types that are not managed by the DI system that are dependencies. We do not + * want to autoload these since they are not inside our managed namespace.. + */ + +// public function addDirectory($directory) +// { +// if (!file_exists($directory)) { +// throw new \Exception('Directory ' . $directory . ' not found'); +// } else { +// $this->directoryIterators[] = new \RecursiveDirectoryIterator($directory); +// } +// } + + + /** + * + */ + public function manage() + { + if ($this->managedNamespaces == null) { + throw new \Exception('Nothing to manage.'); + } + + $currentNamespace = null; + + // convert namespace to directory iterator, but first, create a namespace only autoloader. + $nsAutoloader = function($class) use (&$currentNamespace) { + if (strpos($class, $currentNamespace) !== 0) return; + $file = str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $class) . '.php'; + return (false !== ($file = stream_resolve_include_path($file))) ? include_once($file) : false; + }; + + spl_autoload_register($nsAutoloader); + + //foreach ($this->directoryIterators as $dirIter) { + foreach ($this->managedNamespaces as $directory => $currentNamespace) { + $directoryIterator = new \RecursiveDirectoryIterator($directory); + $rii = new \RecursiveIteratorIterator($directoryIterator); //, $mode, $flags); + foreach ($rii as $item) { + if (!$rii->isDot() && $item->getType() == 'file' && preg_match('#\.php$#', $item->getFilename())) { + + /** + * @todo Short-circuit here is fmtime has not changed and is inside the ClassRegistry + */ + + $classes = get_declared_classes(); + $interfaces = get_declared_interfaces(); + require_once $item->getPathname(); + foreach (array_values(array_diff(get_declared_classes(), $classes)) as $class) { + $this->typeRegistry->register($class, $item->getPathname(), $item->getMtime()); + } + foreach (array_values(array_diff(get_declared_interfaces(), $interfaces)) as $interface) { + $this->typeRegistry->register($interface, $item->getPathname(), $item->getMtime()); + } + } + } + } + + spl_autoload_unregister($nsAutoloader); + + } + +} diff --git a/library/Zend/Di/Generator/TypeRegistry.php b/library/Zend/Di/Generator/TypeRegistry.php new file mode 100644 index 00000000000..814796aeed6 --- /dev/null +++ b/library/Zend/Di/Generator/TypeRegistry.php @@ -0,0 +1,119 @@ +fileStatInformation = $fileStatInformation; + } + + public function hasFileStatUpdates() + { + return true; + } + + public function getFileStatInformation() + { + return $this->fileStatInformation; + } + + public function isFileStatFresh($file, $mtime) + { + return true; + } + + public function register($type, $file = null, $fileMtime = null) + { + if ($file == null || $fileMtime == null) { + list($file, $fileMtime) = $this->discoverClassFile($type); + } + + $this->fileStatInformation[$file] = $fileMtime; + + /** + * @todo If class is already here, and mtime has not changed, do not update entry, return + */ + $isNew = true; + + $this->types[] = array('class' => $type, 'file' => $file, 'mtime' => $fileMtime, 'isNew' => true); + } + + public function setIteratorMode($iteratorMode) + { + $this->iteratorMode = $iteratorMode; + } + + public function rewind() + { + reset($this->types); + if (count($this->types) > 0) { + $this->iteratorValid = true; + } + } + + public function valid() + { + return $this->iteratorValid; + } + + public function next() + { + $return = next($this->types); + $this->iteratorValid = ($return !== false); + } + + public function current() + { + $classInfo = current($this->types); + return $classInfo['class']; + } + + public function key() + { + return key($this->types); + } + + /** + * discoverClassFile() + * + * This should technically never run since in most cases someone else (some iterator) + * will determin the file and fileMtime. But in cases where its not provided in register(), + * this will run. + * + * @param string $class + * @return array ($file, $mtime) + */ + protected function discoverClassFile($class) + { + $classRefl = new \ReflectionClass($class); + $file = $classRefl->getFileName(); + $fmtime = filemtime($file); + unset($classRefl); + return array($file, $fmtime); + } + + public function toArray() + { + $classes = array(); + foreach ($this as $class) { + $classes[] = $class; + } + return $classes; + } + +} \ No newline at end of file diff --git a/library/Zend/Di/InjectibleMethod.php b/library/Zend/Di/InjectibleMethod.php index 57e22665512..04210b615b0 100644 --- a/library/Zend/Di/InjectibleMethod.php +++ b/library/Zend/Di/InjectibleMethod.php @@ -3,7 +3,7 @@ interface InjectibleMethod { - public function __construct($name, array $args); + public function __construct($name, array $params); public function getName(); - public function getArgs(); + public function getParams(); } diff --git a/library/Zend/Di/Method.php b/library/Zend/Di/Method.php index cefe8826113..fb9a2ab0147 100644 --- a/library/Zend/Di/Method.php +++ b/library/Zend/Di/Method.php @@ -21,19 +21,19 @@ class Method implements InjectibleMethod * Arguments to pass to the method * @var array */ - protected $args; + protected $params; /** * Construct the method signature * * @param strinb $name - * @param array $args + * @param array $params * @return void */ - public function __construct($name, array $args) + public function __construct($name, array $params) { $this->name = $name; - $this->args = $args; + $this->params = $params; } /** @@ -51,8 +51,8 @@ public function getName() * * @return array */ - public function getArgs() + public function getParams() { - return $this->args; + return $this->params; } } diff --git a/library/Zend/Mvc/Application/Application.php b/library/Zend/Mvc/Application/Application.php new file mode 100644 index 00000000000..57bcfece9bb --- /dev/null +++ b/library/Zend/Mvc/Application/Application.php @@ -0,0 +1,61 @@ +environment = $environment; + } + + public function setBootstrapperClass($bootstrapperClass) + { + $this->bootstrapperClass = $bootstrapperClass; + } + + public function setServiceLocator(ServiceLocatorInterface $serviceLocator) + { + $this->serviceLocator = $serviceLocator; + } + + public function bootstrap() + { + $this->bootstrapper = new $this->bootstrapperClass; + if (!$this->bootstrapper instanceof BootstrapInterface) { + throw new \RuntimeException('Bootstrap class must implement Zend\Application\BootstrapInterface'); + } + $this->bootstrapper->bootstrap($this); + } + + public function setRunnerClass($runnerClass) + { + $this->runnerClass = $runnerClass; + } + + public function run() + { + $runner = new $this->runnerClass; + + if (!$runner instanceof RunnerInterface) { + throw new \RuntimeException('Runner class must implement Zend\Application\RunnerInterface'); + } + + if ($this->serviceLocator && $runner instanceof ServiceLocatorAwareInterface) { + $runner->setServiceLocator($this->serviceLocator); + } + + $this->dispatcher->run($this); + } + +} diff --git a/library/Zend/Mvc/Application/BootstrapInterface.php b/library/Zend/Mvc/Application/BootstrapInterface.php new file mode 100644 index 00000000000..c926407482d --- /dev/null +++ b/library/Zend/Mvc/Application/BootstrapInterface.php @@ -0,0 +1,8 @@ +configData = $configData; + } + + /* + public function fromArray(array $config) {} + public function fromConfig() {} + */ + + public function configure($application) + { + if (!$application instanceof Application) { + throw new \RuntimeException('No application provided'); + } + } + +} + + + diff --git a/library/Zend/Mvc/Application/RunnerInterface.php b/library/Zend/Mvc/Application/RunnerInterface.php new file mode 100644 index 00000000000..6921e16df1e --- /dev/null +++ b/library/Zend/Mvc/Application/RunnerInterface.php @@ -0,0 +1,8 @@ + array( array( 'name' => 'setObject', - 'args' => array( + 'params' => array( array('__reference' => 'params'), ), ), @@ -164,7 +164,7 @@ public function getConfig() 'methods' => array( array( 'name' => 'method_name', - 'args' => array( /* ... * / ), + 'params' => array( /* ... * / ), // if value is an array, look for '__reference' // key, and, if found, create a Reference object ), diff --git a/tests/Zend/Di/DefinitionTest.php b/tests/Zend/Di/DefinitionTest.php index 18bf166b628..890173628c1 100644 --- a/tests/Zend/Di/DefinitionTest.php +++ b/tests/Zend/Di/DefinitionTest.php @@ -138,10 +138,10 @@ public function testAddingMethodCallsAggregates() foreach ($methods as $name => $method) { switch ($name) { case 'foo': - $this->assertSame(array(), $method->getArgs()); + $this->assertSame(array(), $method->getParams()); break; case 'bar': - $this->assertSame(array('bar'), $method->getArgs()); + $this->assertSame(array('bar'), $method->getParams()); break; default: $this->fail('Unexpected method encountered'); @@ -167,8 +167,8 @@ public function testCanSerializeDefinitionToArray() $expected = array( 'class' => 'Foo', 'methods' => array( - array('name' => 'bar', 'args' => array('one', 'two')), - array('name' => 'baz', 'args' => array(array('__reference' => 'Bar'))), + array('name' => 'bar', 'params' => array('one', 'two')), + array('name' => 'baz', 'params' => array(array('__reference' => 'Bar'))), ), 'param_map' => array( 'name' => 1, diff --git a/tests/Zend/Di/MethodTest.php b/tests/Zend/Di/MethodTest.php index 016b5e29928..d7974e2e1a9 100644 --- a/tests/Zend/Di/MethodTest.php +++ b/tests/Zend/Di/MethodTest.php @@ -14,10 +14,10 @@ public function testMethodReturnsNamePassedToConstructor() $this->assertEquals($name, $method->getName()); } - public function testMethodReturnsArgsPassedToConstructor() + public function testMethodReturnsParamsPassedToConstructor() { $name = uniqid(); - $args = array( + $params = array( 'foo', new \stdClass, true, @@ -26,7 +26,7 @@ public function testMethodReturnsArgsPassedToConstructor() 0.00, array('bar'), ); - $method = new Method($name, $args); - $this->assertEquals($args, $method->getArgs()); + $method = new Method($name, $params); + $this->assertEquals($params, $method->getParams()); } } diff --git a/tests/Zend/Di/_files/config.ini b/tests/Zend/Di/_files/config.ini index 7d45d42d763..4b479281e3a 100644 --- a/tests/Zend/Di/_files/config.ini +++ b/tests/Zend/Di/_files/config.ini @@ -15,7 +15,7 @@ definitions.params.param_map.params = 1 definitions.injected.class = "ZendTest\Di\TestAsset\InjectedMethod" definitions.injected.methods.set_object.name = "setObject" -definitions.injected.methods.set_object.args.1.__reference = "params" +definitions.injected.methods.set_object.params.1.__reference = "params" definitions.inspected.class = "ZendTest\Di\TestAsset\InspectedClass" definitions.inspected.params.baz = "BAZ" diff --git a/tests/Zend/Di/_files/config.json b/tests/Zend/Di/_files/config.json index 8d881e40ad8..c446b500988 100644 --- a/tests/Zend/Di/_files/config.json +++ b/tests/Zend/Di/_files/config.json @@ -32,7 +32,7 @@ "methods": [ { "name": "setObject", - "args": [ { "__reference": "params" } ] + "params": [ { "__reference": "params" } ] } ] }, diff --git a/tests/Zend/Di/_files/config.xml b/tests/Zend/Di/_files/config.xml index 0a61c5c04ed..a5d779c42bb 100644 --- a/tests/Zend/Di/_files/config.xml +++ b/tests/Zend/Di/_files/config.xml @@ -32,7 +32,7 @@ setObject - <__reference>params + <__reference>params diff --git a/tests/Zend/Di/_files/config.yml b/tests/Zend/Di/_files/config.yml index bb61128ef7a..880ef3ffbf5 100644 --- a/tests/Zend/Di/_files/config.yml +++ b/tests/Zend/Di/_files/config.yml @@ -26,7 +26,7 @@ testing: methods: setObject: name: setObject - args: + params: - __reference: params - From f287ca4cd61d7026382d43b9df65163b73799ad9 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Tue, 31 May 2011 09:00:31 -0500 Subject: [PATCH 26/45] Added Zend\Code\Scanner, a component for static (non-reflective) code analysis --- library/Zend/Code/Scanner/ScannerClass.php | 476 ++++++++++++++++++ .../Zend/Code/Scanner/ScannerDirectory.php | 44 ++ .../Zend/Code/Scanner/ScannerDocComment.php | 9 + .../Code/Scanner/ScannerDocCommentTag.php | 8 + library/Zend/Code/Scanner/ScannerFile.php | 36 ++ library/Zend/Code/Scanner/ScannerFunction.php | 9 + .../Zend/Code/Scanner/ScannerInterface.php | 8 + library/Zend/Code/Scanner/ScannerMethod.php | 86 ++++ .../Zend/Code/Scanner/ScannerParameter.php | 8 + library/Zend/Code/Scanner/ScannerProperty.php | 1 + .../Zend/Code/Scanner/ScannerTokenArray.php | 409 +++++++++++++++ library/Zend/Code/Scanner/ScannerVariable.php | 8 + tests/Zend/Code/Scanner/ScannerClassTest.php | 59 +++ tests/Zend/Code/Scanner/ScannerFileTest.php | 48 ++ tests/Zend/Code/TestAsset/FooClass.php | 23 + tests/Zend/Code/TestAsset/functions.php | 11 + 16 files changed, 1243 insertions(+) create mode 100644 library/Zend/Code/Scanner/ScannerClass.php create mode 100644 library/Zend/Code/Scanner/ScannerDirectory.php create mode 100644 library/Zend/Code/Scanner/ScannerDocComment.php create mode 100644 library/Zend/Code/Scanner/ScannerDocCommentTag.php create mode 100644 library/Zend/Code/Scanner/ScannerFile.php create mode 100644 library/Zend/Code/Scanner/ScannerFunction.php create mode 100644 library/Zend/Code/Scanner/ScannerInterface.php create mode 100644 library/Zend/Code/Scanner/ScannerMethod.php create mode 100644 library/Zend/Code/Scanner/ScannerParameter.php create mode 100644 library/Zend/Code/Scanner/ScannerProperty.php create mode 100644 library/Zend/Code/Scanner/ScannerTokenArray.php create mode 100644 library/Zend/Code/Scanner/ScannerVariable.php create mode 100644 tests/Zend/Code/Scanner/ScannerClassTest.php create mode 100644 tests/Zend/Code/Scanner/ScannerFileTest.php create mode 100644 tests/Zend/Code/TestAsset/FooClass.php create mode 100644 tests/Zend/Code/TestAsset/functions.php diff --git a/library/Zend/Code/Scanner/ScannerClass.php b/library/Zend/Code/Scanner/ScannerClass.php new file mode 100644 index 00000000000..59ead3c4020 --- /dev/null +++ b/library/Zend/Code/Scanner/ScannerClass.php @@ -0,0 +1,476 @@ +tokens = $classTokens; + $this->namespace = $namespace; + $this->uses = $uses; + } + + public function scan() + { + if (!$this->tokens) { + throw new \RuntimeException('No tokens were provided'); + } + + for ($tokenIndex = 0; $tokenIndex < count($this->tokens); $tokenIndex++) { + $token = $this->tokens[$tokenIndex]; + + if (is_string($token)) { + continue; + } + + // tokens with some value are arrays (will have a token identifier, & line num) + $fastForward = 0; + switch ($token[0]) { + case T_CLASS: + case T_INTERFACE: + $this->scanClassInfo($tokenIndex, $fastForward); + break; + + case T_CONST: + $this->scanConstant($tokenIndex, $fastForward); + break; + case T_FINAL: + case T_ABSTRACT: + if (!$this->name) { + break; + } + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + case T_STATIC: + case T_FUNCTION: + case T_VAR: + $subTokenIndex = $tokenIndex; + do { + $subToken = $this->tokens[$subTokenIndex++]; + } while (!(is_array($subToken) && $subToken[0] == T_FUNCTION) && !(is_string($subToken) && $subToken == '=')); + + if (is_array($subToken)) { + $this->scanMethod($tokenIndex, $fastForward); + } else { + $this->scanProperty($tokenIndex, $fastForward); + } + + break; + } + + if ($fastForward) { + $tokenIndex += $fastForward - 1; + } + } + + // find constants + // find properties + // find methods + // var_dump($this); + } + + protected function scanClassInfo($classTokenIndex, &$fastForward) + { + if (isset($this->tokens[$classTokenIndex-2]) && is_array($this->tokens[$classTokenIndex-2])) { + $tokenTwoBack = $this->tokens[$classTokenIndex-2]; + } + $this->isAbstract = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_ABSTRACT)); + $this->isFinal = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_FINAL)); + $this->isInterface = (is_array($this->tokens[$classTokenIndex]) && $this->tokens[$classTokenIndex][0] == T_INTERFACE); + $this->shortName = $this->tokens[$classTokenIndex+2][1]; + $this->name = (($this->namespace) ? $this->namespace . '\\' : '') . $this->shortName; + + $index = $classTokenIndex; + + $context = null; + $interfaceIndex = 0; + while (true) { + $fastForward++; + $token = $this->tokens[$index++]; + if (is_string($token)) { + if ($context == T_IMPLEMENTS && $token == ',') { + $interfaceIndex++; + $this->shortInterfaces[$interfaceIndex] = ''; + } + if ($token == '{') { + $context = null; + break; + } + } + if (is_array($token)) { + if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { + if ($context == T_EXTENDS) { + $this->shortParentClass .= $token[1]; + } elseif ($context == T_IMPLEMENTS) { + $this->shortInterfaces[$interfaceIndex] .= $token[1]; + } + } + if ($token[0] == T_EXTENDS) { + die('found extends'); + $fastForward += 2; + $index += 2; + $this->shortParentClass = ''; + } + if ($token[0] == T_IMPLEMENTS) { + $context = T_IMPLEMENTS; + $this->shortInterfaces[$interfaceIndex] = ''; + } + } + } + + $namespace = $this->namespace; + $uses = $this->uses; + $resolveUseFunc = function (&$value, $key = null) use (&$namespace, &$uses) { + if (!$uses || strlen($value) <= 0 || $value{0} == '\\') { + $value = ltrim($value, '\\'); + return; + } + + if ($namespace || $uses) { + $firstPartEnd = (strpos($value, '\\')) ?: strlen($value-1); + $firstPart = substr($value, 0, $firstPartEnd); + if (array_key_exists($firstPart, $uses)) { + $value = substr_replace($value, $uses[$firstPart], 0, $firstPartEnd); + return; + } + if ($namespace) { + $value = $namespace . '\\' . $value; + return; + } + } + }; + + if ($this->shortInterfaces) { + $this->interfaces = $this->shortInterfaces; + array_walk($this->interfaces, $resolveUseFunc); + } + + if ($this->shortParentClass) { + $this->parentClass = $this->shortParentClass; + $resolveUseFunc($this->parentClass); + } + + } + + protected function scanConstant($constTokenIndex, &$fastForward) + { + $info = array( + 'type' => 'constant', + 'tokenStart' => $constTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$constTokenIndex][2], + 'lineEnd' => null, + 'name' => null, + 'value' => null + ); + + $index = $constTokenIndex; + + do { + $fastForward++; + $token = $this->tokens[$index++]; + + if ((is_array($token) && $token[0] == T_WHITESPACE) || (is_string($token) && $token == '=')) { + continue; + } + + $info['value'] .= (is_array($token)) ? $token[1] : $token; + + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + } + + } while (!(is_string($token) && $token == ';')); + + $info['tokenEnd'] = $index; + $this->infos[] = $info; + } + + protected function scanMethod($memberTokenIndex, &$fastForward) + { + //static $visibilities = array(T_PUBLIC => 'public', T_PROTECTED => 'protected', T_PRIVATE => 'private'); + + $info = array( + 'type' => 'method', + 'tokenStart' => $memberTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$memberTokenIndex][2], + 'lineEnd' => null, + 'name' => null + /* + 'visibility' => null, + 'isFinal' => false, + 'isStatic' => false + */ + ); + + $index = $memberTokenIndex; + + $braceCount = 0; + do { + $fastForward++; + $token = $this->tokens[$index++]; + + if (is_string($token)) { + if ($token == '{') { + $braceCount++; + } + if ($token == '}') { + $braceCount = ($braceCount == 1) ? false : ($braceCount - 1); + } + } + + switch ($token[0]) { + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + case T_FINAL: + case T_STATIC: + continue; + } + + /* + if (isset($visibilities[$token[0]])) { // T_PUBLIC, T_PROTECTED, T_PRIVATE + //$info['visibility'] = $visibilities[$token[0]]; + continue; + } + + if ($token[0] === T_FINAL) { + //$info['isFinal'] = true; + continue; + } + + if ($token[0] === T_STATIC) { + //$info['isStatic'] = true; + continue; + } + */ + + if ($token[0] === T_FUNCTION) { + $info['name'] = $this->tokens[$index+1][1]; + continue; + } + + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + } + + } while ($braceCount !== false); + + $info['tokenEnd'] = $index; + $this->infos[] = $info; + } + + protected function scanProperty($propertyTokenIndex, &$fastForward) + { + //static $visibilities = array(T_PUBLIC => 'public', T_PROTECTED => 'protected', T_PRIVATE => 'private'); + $info = array( + 'type' => 'property', + 'tokenStart' => $propertyTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$propertyTokenIndex][2], + 'lineEnd' => null, + 'name' => null + /* + 'visibility' => null, + 'isStatic' => false, + 'value' => null + */ + ); + + $index = $propertyTokenIndex; + + do { + $fastForward++; + $token = $this->tokens[$index++]; + + switch ($token[0]) { + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + case T_FINAL: + case T_STATIC: + continue; + } + + /* + if (isset($visibilities[$token[0]])) { // T_PUBLIC, T_PROTECTED, T_PRIVATE + $info['visibility'] = $visibilities[$token[0]]; + continue; + } + + if ($token[0] === T_STATIC) { + $info['isStatic'] = true; + continue; + } + */ + + if ($token[0] === T_VARIABLE) { + $info['name'] = ltrim($token[1], '$'); + continue; + } + + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + } + + } while (!(is_string($token) && $token == ';')); + + $info['tokenEnd'] = $index; + $this->infos[] = $info; + } + + public function getName() + { + return $this->name; + } + + public function getShortName() + { + return $this->shortName; + } + + public function isFinal() + { + return $this->isFinal; + } + + public function isAbstract() + { + return $this->isAbstract; + } + + public function isInterface() + { + return $this->isInterface; + } + + public function getInterfaces() + { + return $this->interfaces; + } + + public function getConstants() + { + $return = array(); + + foreach ($this->infos as $info) { + if ($info['type'] != 'constant') { + continue; + } + + //if (!$returnScanner) { + $return[] = $info['name']; + //} else { + // $return[] = $this->getClass($info['name'], $returnScannerProperty); + //} + } + return $return; + } + + public function getProperties($returnScannerProperty = false) + { + $return = array(); + + foreach ($this->infos as $info) { + if ($info['type'] != 'property') { + continue; + } + + if (!$returnScannerProperty) { + $return[] = $info['name']; + } else { + $return[] = $this->getClass($info['name'], $returnScannerProperty); + } + } + return $return; + } + + public function getMethods($returnScannerMethod = false) + { + $return = array(); + + foreach ($this->infos as $info) { + if ($info['type'] != 'method') { + continue; + } + + if (!$returnScannerMethod) { + $return[] = $info['name']; + } else { + $return[] = $this->getMethod($info['name'], $returnScannerMethod); + } + } + return $return; + } + + public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerMethod') + { + // process the class requested + static $baseScannerClass = 'Zend\Code\Scanner\ScannerMethod'; + if ($returnScannerClass !== $baseScannerClass) { + if (!is_string($returnScannerClass)) { + $returnScannerClass = $baseScannerClass; + } + $returnScannerClass = ltrim($returnScannerClass, '\\'); + if ($returnScannerClass !== $baseScannerClass && !is_subclass_of($returnScannerClass, $baseScannerClass)) { + throw new \RuntimeException('Class must be or extend ' . $baseScannerClass); + } + } + + if (is_int($methodNameOrInfoIndex)) { + $info = $this->infos[$methodNameOrInfoIndex]; + if ($info['type'] != 'method') { + throw new \InvalidArgumentException('Index of info offset is not about a method'); + } + } elseif (is_string($methodNameOrInfoIndex)) { + $methodFound = false; + foreach ($this->infos as $infoIndex => $info) { + if ($info['type'] === 'method' && $info['name'] === $methodNameOrInfoIndex) { + $methodFound = true; + break; + } + } + if (!$methodFound) { + return false; + } + } + + $uses = array(); + for ($u = 0; $u < count($this->infos); $u++) { + if ($this->infos[$u]['type'] == 'use') { + foreach ($this->infos[$u]['statements'] as $useStatement) { + $useKey = ($useStatement['as']) ?: $useStatement['asComputed']; + $uses[$useKey] = $useStatement['use']; + } + } + } + + return new $returnScannerClass( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart']), + $info['namespace'], + $uses + ); + } + + +} diff --git a/library/Zend/Code/Scanner/ScannerDirectory.php b/library/Zend/Code/Scanner/ScannerDirectory.php new file mode 100644 index 00000000000..6204dbede8f --- /dev/null +++ b/library/Zend/Code/Scanner/ScannerDirectory.php @@ -0,0 +1,44 @@ +addDirectory($directory); + } elseif (is_array($directory)) { + foreach ($directory as $d) { + $this->addDirectory($d); + } + } + } + } + + public function setFileScannerClass($fileScannerClass) + { + $this->fileScannerClass = $fileScannerClass; + } + + public function addDirectory($directory) + { + $realDir = realpath($directory); + if (!$realDir) { + throw new \InvalidArgumentException('Directory does not exist'); + } + $this->directories[] = $realDir; + } + + public function scan() + { + // iterate directories creating file scanners + } + + + +} diff --git a/library/Zend/Code/Scanner/ScannerDocComment.php b/library/Zend/Code/Scanner/ScannerDocComment.php new file mode 100644 index 00000000000..7ce38a50804 --- /dev/null +++ b/library/Zend/Code/Scanner/ScannerDocComment.php @@ -0,0 +1,9 @@ +setFile($file); + } + } + + public function setFile($file) + { + $this->file = $file; + if (!file_exists($file)) { + throw new \InvalidArgumentException('File not found'); + } + } + + public function scan() + { + if (!$this->file) { + throw new \RuntimeException('File was not provided'); + } + $this->setTokens(token_get_all(file_get_contents($this->file))); + parent::scan(); + } + + + + +} diff --git a/library/Zend/Code/Scanner/ScannerFunction.php b/library/Zend/Code/Scanner/ScannerFunction.php new file mode 100644 index 00000000000..a3f86f15cfe --- /dev/null +++ b/library/Zend/Code/Scanner/ScannerFunction.php @@ -0,0 +1,9 @@ +tokens = $methodTokens; + $this->class = $class; + $this->uses = $uses; + } + + public function scan() + { + if (!$this->tokens) { + throw new \RuntimeException('No tokens were provided'); + } + + $currentNamespace = null; + + for ($tokenIndex = 0; $tokenIndex < count($this->tokens); $tokenIndex++) { + $token = $this->tokens[$tokenIndex]; + + if (is_string($token)) { + continue; + } + + // tokens with some value are arrays (will have a token identifier, & line num) + $fastForward = 0; + switch ($token[0]) { + case T_CLASS: + case T_INTERFACE: + $this->scanClassInfo($tokenIndex, $fastForward); + break; + + case T_CONST: + $this->scanConstant($tokenIndex, $fastForward); + break; + case T_FINAL: + case T_ABSTRACT: + if (!$this->name) { + break; + } + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + case T_STATIC: + case T_FUNCTION: + case T_VAR: + $subTokenIndex = $tokenIndex; + do { + $subToken = $this->tokens[$subTokenIndex++]; + } while (!(is_array($subToken) && $subToken[0] == T_FUNCTION) && !(is_string($subToken) && $subToken == '=')); + + if (is_array($subToken)) { + $this->scanMethod($tokenIndex, $fastForward); + } else { + $this->scanProperty($tokenIndex, $fastForward); + } + + break; + } + + if ($fastForward) { + $tokenIndex += $fastForward - 1; + } + } + + // find constants + // find properties + // find methods + // var_dump($this); + } + +} \ No newline at end of file diff --git a/library/Zend/Code/Scanner/ScannerParameter.php b/library/Zend/Code/Scanner/ScannerParameter.php new file mode 100644 index 00000000000..9d297474206 --- /dev/null +++ b/library/Zend/Code/Scanner/ScannerParameter.php @@ -0,0 +1,8 @@ +setTokens($tokens); + } + } + + public function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + public function scan() + { + if (!$this->tokens) { + throw new \RuntimeException('No tokens were provided'); + } + + $currentNamespace = null; + + for ($tokenIndex = 0; $tokenIndex < count($this->tokens); $tokenIndex++) { + $token = $this->tokens[$tokenIndex]; + + // tokens with some value are arrays (will have a token identifier, & line num) + $fastForward = 0; + switch ($token[0]) { + case T_DOC_COMMENT: + echo 'Found Doc Comment' . PHP_EOL; + break; + case T_NAMESPACE: + $currentNamespace = $this->scanNamespace($tokenIndex, $fastForward); + break; + case T_USE: + $this->scanUse($tokenIndex, $fastForward); + // process uses + break; + case T_INCLUDE: + case T_INCLUDE_ONCE: + case T_REQUIRE: + case T_REQUIRE_ONCE: + $this->scanInclude($tokenIndex, $fastForward); + // process include + break; + case T_FINAL: + case T_ABSTRACT: + case T_CLASS: + case T_INTERFACE: + $this->scanClass($tokenIndex, $fastForward, $currentNamespace); + break; + case T_FUNCTION: + $this->scanFunction($tokenIndex, $fastForward, $currentNamespace); + break; + } + if ($fastForward) { + $tokenIndex += $fastForward - 1; + } + } + } + + protected function scanNamespace($namespaceTokenIndex, &$fastForward) + { + $info = array( + 'type' => 'namespace', + 'tokenStart' => $namespaceTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$namespaceTokenIndex][2], + 'lineEnd' => null, + 'namespace' => null + ); + $namespaceName = ''; + $index = $namespaceTokenIndex; + $token = null; + do { + $fastForward++; + $token = $this->tokens[$index++]; + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + if ($token[0] == T_WHITESPACE) { + continue; + } + if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { + $namespaceName .= (is_string($token)) ? $token : $token[1]; + } + } + } while (!(is_string($token) && $token == ';')); + + $info['tokenEnd'] = $index; + $info['namespace'] = $namespaceName; + $this->infos[] = $info; + return $namespaceName; + } + + protected function scanUse($useTokenIndex, &$fastForward) + { + $info = array( + 'type' => 'use', + 'tokenStart' => $useTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$useTokenIndex][2], + 'lineEnd' => null, + 'statements' => array() + ); + static $statementTemplate = array( + 'use' => null, + 'as' => null, + 'asComputed' => null + ); + $statement = $statementTemplate; + $hasAs = false; + $index = $useTokenIndex; + $token = null; + do { + $fastForward++; + $token = $this->tokens[$index++]; + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + if ($token[0] == T_WHITESPACE) { + continue; + } + if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { + if ($hasAs == false) { + $statement['use'] .= (is_string($token)) ? $token : $token[1]; + } else { + $statement['as'] = $token[1]; // always a string + } + } + if ($token[0] == T_AS) { + $hasAs = true; + } + } + if (is_string($token) && $token == ',' || $token == ';') { + if (!$hasAs) { + $statement['asComputed'] = substr($statement['use'], strrpos($statement['use'], '\\')+1); + } + $info['statements'][] = $statement; + $statement = $statementTemplate; + $hasAs = false; + } + } + while (!(is_string($token) && $token == ';')); + + $info['tokenEnd'] = $index; + $this->infos[] = $info; + } + + protected function scanInclude($includeTokenIndex, &$fastForward) + { + static $types = array(T_INCLUDE => 'include', T_INCLUDE_ONCE => 'include_once', T_REQUIRE => 'require', T_REQUIRE_ONCE => 'require_once'); + $info = array( + 'type' => 'include', + 'tokenStart' => $includeTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$includeTokenIndex][2], + 'lineEnd' => null, + 'includeType' => $types[$this->tokens[$includeTokenIndex][0]], + 'path' => '' + ); + + $path = ''; + $index = $includeTokenIndex; + + // move past include & the required whitespace + $fastForward += 2; + $index += 2; + + do { + $fastForward++; + $token = $this->tokens[$index++]; + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + } + $info['path'] .= (is_string($token)) ? $token : $token[1]; + } while (!(is_string($token) && $token == ';')); + + $info['tokenEnd'] = $index; + $this->infos[] = $info; + } + + protected function scanClass($classTokenIndex, &$fastForward, $namespace = null) + { + $info = array( + 'type' => 'class', + 'tokenStart' => $classTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$classTokenIndex][2], + 'lineEnd' => null, + 'namespace' => $namespace, + 'name' => null, + 'shortName' => null + ); + + $index = $classTokenIndex; + + $firstToken = $this->tokens[$classTokenIndex]; + + if ($firstToken[0] === T_FINAL || $firstToken[0] === T_ABSTRACT) { + $info['shortName'] = $this->tokens[$classTokenIndex+4][1]; + } else { + $info['shortName'] = $this->tokens[$classTokenIndex+2][1]; + } + + $info['name'] = (($namespace) ? $namespace . '\\' : '') . $info['shortName']; + + $braceCount = 0; + do { + $fastForward++; + $token = $this->tokens[$index++]; + if (is_string($token)) { + if ($token == '{') { + $braceCount++; + } + if ($token == '}') { + $braceCount = ($braceCount == 1) ? false : ($braceCount - 1); + } + } + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + } + } while ($braceCount !== false); + + $info['tokenEnd'] = $index; + $this->infos[] = $info; + } + + protected function scanFunction($functionTokenIndex, &$fastForward, $namespace = null, $usesComputed = array()) + { + $info = array( + 'type' => 'function', + 'tokenStart' => $functionTokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$functionTokenIndex][2], + 'lineEnd' => null, + 'name' => $namespace . '\\' . $this->tokens[$functionTokenIndex+2][1], + 'shortName' => $this->tokens[$functionTokenIndex+2][1], + 'namespace' => $namespace, + ); + + $index = $functionTokenIndex; + $braceCount = 0; + do { + $fastForward++; + $token = $this->tokens[$index++]; + if (is_string($token)) { + if ($token == '{') { + $context = null; + $braceCount++; + } + if ($token == '}') { + $braceCount = ($braceCount == 1) ? false : ($braceCount - 1); + } + } + if (is_array($token)) { + $info['lineEnd'] = $token[2]; + } + } while ($braceCount !== false); + + $info['tokenEnd'] = $index; + $this->infos[] = $info; + } + + + public function getNamespaces($returnScannerClass = false) + { + if (!$returnScannerClass) { + $namespaces = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'namespace') { + $namespaces[] = $info['namespace']; + } + } + return $namespaces; + } else { + if ($returnScannerClass === true) { + $returnScannerClass = '\Zend\Code\Scanner\ScannerNamespace'; + } + $scannerClass = new $returnScannerClass; + // @todo + } + } + + public function getUses($returnScannerClass = false) + { + if (!$returnScannerClass) { + $namespaces = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'namespace') { + $namespaces[] = $info['namespace']; + } + } + return $namespaces; + } /*else { + if ($returnScannerClass === true) { + $returnScannerClass = '\Zend\Code\Scanner\ScannerClasss'; + } + $scannerClass = new $returnScannerClass; + // @todo + } */ + } + + public function getIncludes($returnScannerClass = false) + { + // @todo Implement getIncludes() in ScannerTokenArray + } + + public function getClasses($returnScannerClass = false) + { + $return = array(); + + foreach ($this->infos as $info) { + if ($info['type'] != 'class') { + continue; + } + + if (!$returnScannerClass) { + $return[] = $info['name']; + } else { + $return[] = $this->getClass($info['name'], $returnScannerClass); + } + } + return $return; + } + + /** + * + * Enter description here ... + * @param string|int $classNameOrInfoIndex + * @param string $returnScannerClass + * @return Zend\Code\Scanner\ScannerClass + */ + public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerClass') + { + // process the class requested + static $baseScannerClass = 'Zend\Code\Scanner\ScannerClass'; + if ($returnScannerClass !== $baseScannerClass) { + if (!is_string($returnScannerClass)) { + $returnScannerClass = $baseScannerClass; + } + $returnScannerClass = ltrim($returnScannerClass, '\\'); + if ($returnScannerClass !== $baseScannerClass && !is_subclass_of($returnScannerClass, $baseScannerClass)) { + throw new \RuntimeException('Class must be or extend ' . $baseScannerClass); + } + } + + if (is_int($classNameOrInfoIndex)) { + $info = $this->infos[$classNameOrInfoIndex]; + if ($info['type'] != 'class') { + throw new \InvalidArgumentException('Index of info offset is not about a class'); + } + } elseif (is_string($classNameOrInfoIndex)) { + $classFound = false; + foreach ($this->infos as $infoIndex => $info) { + if ($info['type'] === 'class' && $info['name'] === $classNameOrInfoIndex) { + $classFound = true; + break; + } + } + if (!$classFound) { + return false; + } + } + + $uses = array(); + for ($u = 0; $u < count($this->infos); $u++) { + if ($this->infos[$u]['type'] == 'use') { + foreach ($this->infos[$u]['statements'] as $useStatement) { + $useKey = ($useStatement['as']) ?: $useStatement['asComputed']; + $uses[$useKey] = $useStatement['use']; + } + } + } + + return new $returnScannerClass( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart']), + $info['namespace'], + $uses + ); + } + + public function getFunctions($returnScannerClass = false) + { + if (!$returnScannerClass) { + $functions = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'function') { + $functions[] = $info['name']; + } + } + return $functions; + } else { + if ($returnScannerClass === true) { + $returnScannerClass = '\Zend\Code\Scanner\ScannerFunction'; + } + $scannerClass = new $returnScannerClass; + // @todo + } + } + +} diff --git a/library/Zend/Code/Scanner/ScannerVariable.php b/library/Zend/Code/Scanner/ScannerVariable.php new file mode 100644 index 00000000000..889f95a79a9 --- /dev/null +++ b/library/Zend/Code/Scanner/ScannerVariable.php @@ -0,0 +1,8 @@ +scan(); + $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); + $class->scan(); + $this->assertEquals('ZendTest\Code\TestAsset\FooClass', $class->getName()); + $this->assertEquals('FooClass', $class->getShortName()); + $this->assertFalse($class->isFinal()); + $this->assertTrue($class->isAbstract()); + $this->assertFalse($class->isInterface()); + $interfaces = $class->getInterfaces(); + $this->assertContains('ArrayAccess', $interfaces); + $this->assertContains('A\B\C\D\Blarg', $interfaces); + $this->assertContains('ZendTest\Code\TestAsset\Local\SubClass', $interfaces); + $methods = $class->getMethods(); + $this->assertInternalType('array', $methods); + $this->assertContains('fooBarBaz', $methods); + } + + public function testScannerClassHasConstant() + { + $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $fileScanner->scan(); + $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); + $class->scan(); + $this->assertInternalType('array', $class->getConstants()); + // + } + + public function testScannerClassHasProperties() + { + $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $fileScanner->scan(); + $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); + $class->scan(); + $this->assertInternalType('array', $class->getProperties()); + $this->assertContains('bar', $class->getProperties()); + } + + public function testScannerClassHasMethods() + { + $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $fileScanner->scan(); + $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); + $class->scan(); + $this->assertContains('fooBarBaz', $class->getMethods()); + } + +} \ No newline at end of file diff --git a/tests/Zend/Code/Scanner/ScannerFileTest.php b/tests/Zend/Code/Scanner/ScannerFileTest.php new file mode 100644 index 00000000000..b600838ee99 --- /dev/null +++ b/tests/Zend/Code/Scanner/ScannerFileTest.php @@ -0,0 +1,48 @@ +scan(); + $namespaces = $fileScanner->getNamespaces(); + $this->assertInternalType('array', $namespaces); + $this->assertContains('ZendTest\Code\TestAsset', $namespaces); + } + + public function testFileScannerReturnsClassesWithoutScannerClass() + { + $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $fileScanner->scan(); + $classes = $fileScanner->getClasses(); + $this->assertInternalType('array', $classes); + $this->assertContains('ZendTest\Code\TestAsset\FooClass', $classes); + } + + public function testFileScannerReturnsFunctionsWithoutScannerClass() + { + $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/functions.php'); + $fileScanner->scan(); + $functions = $fileScanner->getFunctions(); + $this->assertInternalType('array', $functions); + $this->assertContains('ZendTest\Code\TestAsset\foo_bar', $functions); + } + + public function testFileScannerReturnsClassesWithScannerClass() + { + $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $fileScanner->scan(); + $classes = $fileScanner->getClasses(true); + $this->assertInternalType('array', $classes); + $class = array_shift($classes); + $this->assertInstanceOf('Zend\Code\Scanner\ScannerClass', $class); + } + +} + diff --git a/tests/Zend/Code/TestAsset/FooClass.php b/tests/Zend/Code/TestAsset/FooClass.php new file mode 100644 index 00000000000..9d3c1a28d9b --- /dev/null +++ b/tests/Zend/Code/TestAsset/FooClass.php @@ -0,0 +1,23 @@ + Date: Tue, 31 May 2011 18:52:23 +0200 Subject: [PATCH 27/45] Prototype of circular dependencies check at runtime --- library/Zend/Di/DependencyInjector.php | 42 +++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php index fbbaf42757d..b836a6de384 100644 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -20,6 +20,13 @@ class DependencyInjector implements DependencyInjection */ protected $instances = array(); + /** + * All the class dependencies [source][dependency] + * + * @var array + */ + protected $dependencies= array(); + /** * Lazy-load a class * @@ -194,7 +201,28 @@ protected function getDefinitionFromAlias($name) return $this->definitions[$service]; } - + /** + * Check for Circular Dependencies + * + * @param string $class + * @param array|string $dependency + * @return boolean + */ + protected function checkCircularDependency($class, $dependency) + { + if (is_array($dependency)) { + foreach ($dependency as $dep) { + if (isset($this->dependencies[$dep][$class]) && $this->dependencies[$dep][$class]) { + throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dep and viceversa"); + } + } + } else { + if (isset($this->dependencies[$dependency][$class]) && $this->dependencies[$dependency][$class]) { + throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dependency and viceversa"); + } + } + return true; + } /** * Retrieve a class instance based on class name * @@ -208,6 +236,7 @@ protected function getDefinitionFromAlias($name) */ protected function getInstanceFromClassName($class, array $params) { + $originalParams= $params; // Hack to avoid Reflection in most common use cases switch (count($params)) { case 0: @@ -219,20 +248,31 @@ protected function getInstanceFromClassName($class, array $params) } if ($param instanceof DependencyReference) { $param = $this->get($param->getServiceName()); + $this->dependencies[$class][$param]= true; } + $this->checkCircularDependency($class, $param); return new $class($param); case 2: $param1 = array_shift($params); if ($param1 instanceof DependencyReference) { $param1 = $this->get($param1->getServiceName()); + $this->dependencies[$class][$param1]= true; } $param2 = array_shift($params); if ($param2 instanceof DependencyReference) { $param2 = $this->get($param2->getServiceName()); + $this->dependencies[$class][$param2]= true; } + $this->checkCircularDependency($class, $originalParams); return new $class($param1, $param2); default: + foreach ($params as $key => $value) { + if ($value instanceof DependencyReference) { + $this->dependencies[$class][$value]= true; + } + } $params = $this->resolveReferences($params); + $this->checkCircularDependency($class, $originalParams); $r = new \ReflectionClass($class); return $r->newInstanceArgs($params); } From 08ae4c995a3aab0cf90421a20a2ff77994baf33d Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Wed, 1 Jun 2011 10:06:10 -0500 Subject: [PATCH 28/45] Updated Zend\Code\Scanner with ScannerMethod and ScannerParameter Refactored Zend\Code\Scanner\ScannerClass for better token index management --- library/Zend/Code/Scanner/ScannerClass.php | 216 ++++++-------- library/Zend/Code/Scanner/ScannerFile.php | 13 +- .../Zend/Code/Scanner/ScannerInterface.php | 3 +- library/Zend/Code/Scanner/ScannerMethod.php | 282 +++++++++++++++--- .../Zend/Code/Scanner/ScannerParameter.php | 11 +- .../Zend/Code/Scanner/ScannerTokenArray.php | 234 ++++++++++----- library/Zend/Code/Scanner/ScannerValue.php | 8 + tests/Zend/Code/Scanner/ScannerClassTest.php | 33 +- tests/Zend/Code/Scanner/ScannerFileTest.php | 12 +- tests/Zend/Code/Scanner/ScannerMethodTest.php | 42 +++ tests/Zend/Code/TestAsset/BarClass.php | 33 ++ 11 files changed, 626 insertions(+), 261 deletions(-) create mode 100644 library/Zend/Code/Scanner/ScannerValue.php create mode 100644 tests/Zend/Code/Scanner/ScannerMethodTest.php create mode 100644 tests/Zend/Code/TestAsset/BarClass.php diff --git a/library/Zend/Code/Scanner/ScannerClass.php b/library/Zend/Code/Scanner/ScannerClass.php index 59ead3c4020..8cb9d3a1ba3 100644 --- a/library/Zend/Code/Scanner/ScannerClass.php +++ b/library/Zend/Code/Scanner/ScannerClass.php @@ -4,6 +4,8 @@ class ScannerClass implements ScannerInterface { + protected $isScanned = false; + protected $namespace = null; protected $uses = array(); protected $name = null; @@ -28,7 +30,7 @@ public function __construct(array $classTokens, $namespace = null, array $uses = $this->uses = $uses; } - public function scan() + protected function scan() { if (!$this->tokens) { throw new \RuntimeException('No tokens were provided'); @@ -82,40 +84,42 @@ public function scan() } } - // find constants - // find properties - // find methods - // var_dump($this); + $this->isScanned = true; } - protected function scanClassInfo($classTokenIndex, &$fastForward) + protected function scanClassInfo($tokenIndex, &$fastForward) { - if (isset($this->tokens[$classTokenIndex-2]) && is_array($this->tokens[$classTokenIndex-2])) { - $tokenTwoBack = $this->tokens[$classTokenIndex-2]; + if (isset($this->tokens[$tokenIndex-2]) && is_array($this->tokens[$tokenIndex-2])) { + $tokenTwoBack = $this->tokens[$tokenIndex-2]; } + + // T_ABSTRACT & T_FINAL will have been bypassed if no class name, and will alwasy be 2 tokens behind T_CLASS $this->isAbstract = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_ABSTRACT)); $this->isFinal = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_FINAL)); - $this->isInterface = (is_array($this->tokens[$classTokenIndex]) && $this->tokens[$classTokenIndex][0] == T_INTERFACE); - $this->shortName = $this->tokens[$classTokenIndex+2][1]; + + $this->isInterface = (is_array($this->tokens[$tokenIndex]) && $this->tokens[$tokenIndex][0] == T_INTERFACE); + $this->shortName = $this->tokens[$tokenIndex+2][1]; $this->name = (($this->namespace) ? $this->namespace . '\\' : '') . $this->shortName; - $index = $classTokenIndex; $context = null; $interfaceIndex = 0; while (true) { $fastForward++; - $token = $this->tokens[$index++]; - if (is_string($token)) { - if ($context == T_IMPLEMENTS && $token == ',') { - $interfaceIndex++; - $this->shortInterfaces[$interfaceIndex] = ''; - } - if ($token == '{') { - $context = null; - break; - } + $tokenIndex++; + $token = $this->tokens[$tokenIndex]; + + // BREAK ON + if (is_string($token) && $token == '{') { + break; + } + + // ANALYZE + if (is_string($token) && $context == T_IMPLEMENTS && $token == ',') { + $interfaceIndex++; + $this->shortInterfaces[$interfaceIndex] = ''; } + if (is_array($token)) { if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { if ($context == T_EXTENDS) { @@ -127,7 +131,7 @@ protected function scanClassInfo($classTokenIndex, &$fastForward) if ($token[0] == T_EXTENDS) { die('found extends'); $fastForward += 2; - $index += 2; + $tokenIndex += 2; $this->shortParentClass = ''; } if ($token[0] == T_IMPLEMENTS) { @@ -135,8 +139,10 @@ protected function scanClassInfo($classTokenIndex, &$fastForward) $this->shortInterfaces[$interfaceIndex] = ''; } } + } + // create function to resolve short names with uses $namespace = $this->namespace; $uses = $this->uses; $resolveUseFunc = function (&$value, $key = null) use (&$namespace, &$uses) { @@ -171,23 +177,27 @@ protected function scanClassInfo($classTokenIndex, &$fastForward) } - protected function scanConstant($constTokenIndex, &$fastForward) + protected function scanConstant($tokenIndex, &$fastForward) { $info = array( 'type' => 'constant', - 'tokenStart' => $constTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$constTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'name' => null, 'value' => null ); - $index = $constTokenIndex; - - do { + while (true) { $fastForward++; - $token = $this->tokens[$index++]; + $tokenIndex++; + $token = $this->tokens[$tokenIndex]; + + // BREAK ON + if (is_string($token) && $token == ';') { + break; + } if ((is_array($token) && $token[0] == T_WHITESPACE) || (is_string($token) && $token == '=')) { continue; @@ -199,74 +209,47 @@ protected function scanConstant($constTokenIndex, &$fastForward) $info['lineEnd'] = $token[2]; } - } while (!(is_string($token) && $token == ';')); + } - $info['tokenEnd'] = $index; + $info['tokenEnd'] = $tokenIndex; $this->infos[] = $info; } - protected function scanMethod($memberTokenIndex, &$fastForward) + protected function scanMethod($tokenIndex, &$fastForward) { - //static $visibilities = array(T_PUBLIC => 'public', T_PROTECTED => 'protected', T_PRIVATE => 'private'); - $info = array( 'type' => 'method', - 'tokenStart' => $memberTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$memberTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'name' => null - /* - 'visibility' => null, - 'isFinal' => false, - 'isStatic' => false - */ ); - $index = $memberTokenIndex; - $braceCount = 0; - do { + while (true) { $fastForward++; - $token = $this->tokens[$index++]; + $tokenIndex++; + $token = $this->tokens[$tokenIndex]; + // BREAK ON + if (is_string($token) && $token == '}' && $braceCount == 1) { + break; + } + + // ANALYZE if (is_string($token)) { if ($token == '{') { $braceCount++; } if ($token == '}') { - $braceCount = ($braceCount == 1) ? false : ($braceCount - 1); + $braceCount--; } } - switch ($token[0]) { - case T_PUBLIC: - case T_PROTECTED: - case T_PRIVATE: - case T_FINAL: - case T_STATIC: - continue; - } - - /* - if (isset($visibilities[$token[0]])) { // T_PUBLIC, T_PROTECTED, T_PRIVATE - //$info['visibility'] = $visibilities[$token[0]]; - continue; - } - - if ($token[0] === T_FINAL) { - //$info['isFinal'] = true; - continue; - } - - if ($token[0] === T_STATIC) { - //$info['isStatic'] = true; - continue; - } - */ - if ($token[0] === T_FUNCTION) { - $info['name'] = $this->tokens[$index+1][1]; + // next token after T_WHITESPACE is name + $info['name'] = $this->tokens[$tokenIndex+2][1]; continue; } @@ -274,56 +257,36 @@ protected function scanMethod($memberTokenIndex, &$fastForward) $info['lineEnd'] = $token[2]; } - } while ($braceCount !== false); + } - $info['tokenEnd'] = $index; + $info['tokenEnd'] = $tokenIndex; $this->infos[] = $info; } - protected function scanProperty($propertyTokenIndex, &$fastForward) + protected function scanProperty($tokenIndex, &$fastForward) { - //static $visibilities = array(T_PUBLIC => 'public', T_PROTECTED => 'protected', T_PRIVATE => 'private'); $info = array( 'type' => 'property', - 'tokenStart' => $propertyTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$propertyTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'name' => null - /* - 'visibility' => null, - 'isStatic' => false, - 'value' => null - */ ); - $index = $propertyTokenIndex; + $index = $tokenIndex; - do { + while (true) { $fastForward++; - $token = $this->tokens[$index++]; - - switch ($token[0]) { - case T_PUBLIC: - case T_PROTECTED: - case T_PRIVATE: - case T_FINAL: - case T_STATIC: - continue; - } - - /* - if (isset($visibilities[$token[0]])) { // T_PUBLIC, T_PROTECTED, T_PRIVATE - $info['visibility'] = $visibilities[$token[0]]; - continue; - } + $tokenIndex++; + $token = $this->tokens[$tokenIndex]; - if ($token[0] === T_STATIC) { - $info['isStatic'] = true; - continue; + // BREAK ON + if (is_string($token) && $token = ';') { + break; } - */ + // ANALYZE if ($token[0] === T_VARIABLE) { $info['name'] = ltrim($token[1], '$'); continue; @@ -333,7 +296,7 @@ protected function scanProperty($propertyTokenIndex, &$fastForward) $info['lineEnd'] = $token[2]; } - } while (!(is_string($token) && $token == ';')); + } $info['tokenEnd'] = $index; $this->infos[] = $info; @@ -341,36 +304,44 @@ protected function scanProperty($propertyTokenIndex, &$fastForward) public function getName() { + $this->scan(); return $this->name; } public function getShortName() { + $this->scan(); return $this->shortName; } public function isFinal() { + $this->scan(); return $this->isFinal; } public function isAbstract() { + $this->scan(); return $this->isAbstract; } public function isInterface() { + $this->scan(); return $this->isInterface; } public function getInterfaces() { + $this->scan(); return $this->interfaces; } public function getConstants() { + $this->scan(); + $return = array(); foreach ($this->infos as $info) { @@ -389,6 +360,8 @@ public function getConstants() public function getProperties($returnScannerProperty = false) { + $this->scan(); + $return = array(); foreach ($this->infos as $info) { @@ -407,6 +380,8 @@ public function getProperties($returnScannerProperty = false) public function getMethods($returnScannerMethod = false) { + $this->scan(); + $return = array(); foreach ($this->infos as $info) { @@ -425,6 +400,8 @@ public function getMethods($returnScannerMethod = false) public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerMethod') { + $this->scan(); + // process the class requested static $baseScannerClass = 'Zend\Code\Scanner\ScannerMethod'; if ($returnScannerClass !== $baseScannerClass) { @@ -455,22 +432,21 @@ public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Co } } - $uses = array(); - for ($u = 0; $u < count($this->infos); $u++) { - if ($this->infos[$u]['type'] == 'use') { - foreach ($this->infos[$u]['statements'] as $useStatement) { - $useKey = ($useStatement['as']) ?: $useStatement['asComputed']; - $uses[$useKey] = $useStatement['use']; - } - } - } - return new $returnScannerClass( - array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart']), - $info['namespace'], - $uses + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] - 1), + $this->name, + $this->uses ); } + public static function export() + { + // @todo + } + + public function __toString() + { + // @todo + } } diff --git a/library/Zend/Code/Scanner/ScannerFile.php b/library/Zend/Code/Scanner/ScannerFile.php index cea451a5b29..8a8bc29037f 100644 --- a/library/Zend/Code/Scanner/ScannerFile.php +++ b/library/Zend/Code/Scanner/ScannerFile.php @@ -19,9 +19,10 @@ public function setFile($file) if (!file_exists($file)) { throw new \InvalidArgumentException('File not found'); } + $this->reset(); } - public function scan() + protected function scan() { if (!$this->file) { throw new \RuntimeException('File was not provided'); @@ -30,7 +31,15 @@ public function scan() parent::scan(); } - + public static function export() + { + // @todo + } + + public function __toString() + { + // @todo + } } diff --git a/library/Zend/Code/Scanner/ScannerInterface.php b/library/Zend/Code/Scanner/ScannerInterface.php index c67af82f4bc..575ed92f7cc 100644 --- a/library/Zend/Code/Scanner/ScannerInterface.php +++ b/library/Zend/Code/Scanner/ScannerInterface.php @@ -4,5 +4,6 @@ interface ScannerInterface { - public function scan(); + public static function export(); + public function __toString(); } diff --git a/library/Zend/Code/Scanner/ScannerMethod.php b/library/Zend/Code/Scanner/ScannerMethod.php index 02ed5d72484..5354a2eb2ca 100644 --- a/library/Zend/Code/Scanner/ScannerMethod.php +++ b/library/Zend/Code/Scanner/ScannerMethod.php @@ -2,15 +2,20 @@ namespace Zend\Code\Scanner; -class ScannerClass implements ScannerInterface +class ScannerMethod implements ScannerInterface { + protected $isScanned = false; + protected $class = null; protected $uses = array(); protected $name = null; protected $isFinal = false; protected $isAbstract = false; - protected $isInterface = false; - + protected $isPublic = true; + protected $isProtected = false; + protected $isPrivate = false; + protected $isStatic = false; + protected $tokens = array(); protected $infos = array(); @@ -21,66 +26,265 @@ public function __construct(array $methodTokens, $class = null, array $uses = ar $this->uses = $uses; } - public function scan() + protected function scan() { + if ($this->isScanned) { + return; + } + if (!$this->tokens) { throw new \RuntimeException('No tokens were provided'); } - $currentNamespace = null; + $fastForward = 0; + $tokenIndex = 0; + + $this->scanMethodInfo($tokenIndex, $fastForward); - for ($tokenIndex = 0; $tokenIndex < count($this->tokens); $tokenIndex++) { + if ($fastForward) { + $tokenIndex += $fastForward - 1; + $fastForward = 0; + } + + // advance to first paren + while ($this->tokens[$tokenIndex] != '(') { + $tokenIndex++; + } + + $this->scanParameters($tokenIndex, $fastForward); + + /* + if ($this->tokens[$tokenIndex] != '{') { + return; + } + */ + + //$this->scanBody($tokenIndex); + + $this->isScanned = true; + } + + protected function scanMethodInfo($tokenIndex, &$fastForward) + { + while (true) { $token = $this->tokens[$tokenIndex]; - + + // BREAK ON + if (is_string($token) && $token == '(') { + break; + } + + // ANALYZE if (is_string($token)) { continue; } - // tokens with some value are arrays (will have a token identifier, & line num) - $fastForward = 0; switch ($token[0]) { - case T_CLASS: - case T_INTERFACE: - $this->scanClassInfo($tokenIndex, $fastForward); - break; - - case T_CONST: - $this->scanConstant($tokenIndex, $fastForward); - break; case T_FINAL: + $this->isFinal = true; + continue; case T_ABSTRACT: - if (!$this->name) { - break; - } + $this->isAbstract = true; + continue; case T_PUBLIC: + continue; case T_PROTECTED: + $this->isProtected = true; + $this->isPublic = false; + continue; case T_PRIVATE: + $this->isPrivate = true; + $this->isPublic = false; + continue; case T_STATIC: - case T_FUNCTION: - case T_VAR: - $subTokenIndex = $tokenIndex; - do { - $subToken = $this->tokens[$subTokenIndex++]; - } while (!(is_array($subToken) && $subToken[0] == T_FUNCTION) && !(is_string($subToken) && $subToken == '=')); + $this->isStatic = true; + continue; + case T_STRING: + $this->name = $token[1]; + continue; + } + + $fastForward++; + $tokenIndex++; + } + } + + protected function scanParameters($tokenIndex, &$fastForward) + { + // first token is paren let loop increase + $parenCount = 1; + $info = null; + + while (true) { + $tokenIndex++; + $fastForward++; + $token = $this->tokens[$tokenIndex]; + + // BREAK ON + if ($parenCount == 1 && is_string($token) && $token == ')') { + break; + } + + // ANALYZE + if (is_string($token)) { + if ($token == '(') { + $parenCount++; + } + if ($token == ')') { + $parenCount--; + } + } + + if ($parenCount == 1 && isset($info)) { + $nextToken = (isset($info['name']) && is_string($this->tokens[$tokenIndex+1])) ? $this->tokens[$tokenIndex+1] : null; + if ((is_string($token) && $token == ',') || (isset($nextToken) && $nextToken == ')')) { + $info['tokenEnd'] = $tokenIndex; + $this->infos[] = $info; + unset($info); + } + unset($nextToken); + } + + if (is_array($token) && isset($info)) { + $info['lineEnd'] = $token[2]; + } + + if ($parenCount > 1 || is_string($token)) { + continue; + } + + // gather line information if we can + if (!isset($info)) { + $info = array( + 'type' => 'parameter', + 'tokenStart' => $tokenIndex, + 'tokenEnd' => null, + 'lineStart' => $this->tokens[$tokenIndex][2], + 'lineEnd' => null, + 'name' => null + ); + } - if (is_array($subToken)) { - $this->scanMethod($tokenIndex, $fastForward); - } else { - $this->scanProperty($tokenIndex, $fastForward); - } - - break; + if (is_array($token) && $token[0] === T_VARIABLE) { + $info['name'] = ltrim($token[1], '$'); + } + + } + + } + + public function getName() + { + $this->scan(); + return $this->name; + } + + public function isFinal() + { + $this->scan(); + return $this->isFinal; + } + + public function isAbstract() + { + $this->scan(); + return $this->isAbstract; + } + + public function isPublic() + { + $this->scan(); + return $this->isPublic; + } + + public function isProtected() + { + $this->scan(); + return $this->isProtected; + } + + public function isPrivate() + { + $this->scan(); + return $this->isPrivate; + } + + public function isStatic() + { + $this->scan(); + return $this->isStatic; + } + + public function getParameters($returnScanner = false) + { + $this->scan(); + + $return = array(); + + foreach ($this->infos as $info) { + if ($info['type'] != 'parameter') { + continue; } - if ($fastForward) { - $tokenIndex += $fastForward - 1; + if (!$returnScanner) { + $return[] = $info['name']; + } else { + $return[] = $this->getParameter($info['name'], $returnScanner); + } + } + return $return; + } + + public function getParameter($parameterNameOrInfoIndex, $returnScanner = 'Zend\Code\Scanner\ScannerParameter') + { + $this->scan(); + + // process the class requested + static $baseScannerClass = 'Zend\Code\Scanner\ScannerParameter'; + if ($returnScanner !== $baseScannerClass) { + if (!is_string($returnScanner)) { + $returnScanner = $baseScannerClass; + } + $returnScanner = ltrim($returnScanner, '\\'); + if ($returnScanner !== $baseScannerClass && !is_subclass_of($returnScanner, $baseScannerClass)) { + throw new \RuntimeException('Class must be or extend ' . $baseScannerClass); } } - // find constants - // find properties - // find methods - // var_dump($this); + if (is_int($parameterNameOrInfoIndex)) { + $info = $this->infos[$parameterNameOrInfoIndex]; + if ($info['type'] != 'parameter') { + throw new \InvalidArgumentException('Index of info offset is not about a parameter'); + } + } elseif (is_string($parameterNameOrInfoIndex)) { + $methodFound = false; + foreach ($this->infos as $infoIndex => $info) { + if ($info['type'] === 'parameter' && $info['name'] === $parameterNameOrInfoIndex) { + $methodFound = true; + break; + } + } + if (!$methodFound) { + return false; + } + } + + return new $returnScanner( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] - 1), + $this->name, + $this->uses + ); + } + + public static function export() + { + // @todo + } + + public function __toString() + { + $this->scan(); + return var_export($this, true); } } \ No newline at end of file diff --git a/library/Zend/Code/Scanner/ScannerParameter.php b/library/Zend/Code/Scanner/ScannerParameter.php index 9d297474206..eb502b2725e 100644 --- a/library/Zend/Code/Scanner/ScannerParameter.php +++ b/library/Zend/Code/Scanner/ScannerParameter.php @@ -4,5 +4,14 @@ class ScannerParameter { - // @todo + protected $tokens = null; + protected $class = null; + protected $uses = array(); + + public function __construct(array $parameterTokens, $class = null, array $uses = array()) + { + $this->tokens = $parameterTokens; + $this->class = $class; + $this->uses = $uses; + } } \ No newline at end of file diff --git a/library/Zend/Code/Scanner/ScannerTokenArray.php b/library/Zend/Code/Scanner/ScannerTokenArray.php index 8d9dc730ad1..f653b5781de 100644 --- a/library/Zend/Code/Scanner/ScannerTokenArray.php +++ b/library/Zend/Code/Scanner/ScannerTokenArray.php @@ -4,6 +4,7 @@ class ScannerTokenArray implements ScannerInterface { + protected $isScanned = false; protected $tokens = array(); @@ -16,13 +17,24 @@ public function __construct($tokens = null, $options = null) } } + public function reset() + { + $this->isScanned = false; + $this->infos = array(); + } + public function setTokens(array $tokens) { $this->tokens = $tokens; + $this->reset(); } - public function scan() + protected function scan() { + if ($this->isScanned) { + return; + } + if (!$this->tokens) { throw new \RuntimeException('No tokens were provided'); } @@ -66,191 +78,244 @@ public function scan() $tokenIndex += $fastForward - 1; } } + + $this->isScanned = true; } - protected function scanNamespace($namespaceTokenIndex, &$fastForward) + protected function scanNamespace($tokenIndex, &$fastForward) { $info = array( 'type' => 'namespace', - 'tokenStart' => $namespaceTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$namespaceTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'namespace' => null - ); - $namespaceName = ''; - $index = $namespaceTokenIndex; - $token = null; - do { + ); + + // move past current T_NAMESPACE & following T_WHITESPACE + $tokenIndex++; + $fastForward++; + + while (true) { + $tokenIndex++; $fastForward++; - $token = $this->tokens[$index++]; + $token = $this->tokens[$tokenIndex]; + + // BREAK ON: + if (is_string($token) && $token == ';') { + break; + } + + // ANALYZE if (is_array($token)) { $info['lineEnd'] = $token[2]; if ($token[0] == T_WHITESPACE) { continue; } if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { - $namespaceName .= (is_string($token)) ? $token : $token[1]; + $info['namespace'] .= (is_string($token)) ? $token : $token[1]; } } - } while (!(is_string($token) && $token == ';')); + } - $info['tokenEnd'] = $index; - $info['namespace'] = $namespaceName; + $info['tokenEnd'] = $tokenIndex; $this->infos[] = $info; - return $namespaceName; + + return $info['namespace']; } - protected function scanUse($useTokenIndex, &$fastForward) + protected function scanUse($tokenIndex, &$fastForward) { $info = array( 'type' => 'use', - 'tokenStart' => $useTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$useTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'statements' => array() - ); + ); + static $statementTemplate = array( 'use' => null, 'as' => null, 'asComputed' => null - ); - $statement = $statementTemplate; + ); + + // skip current token T_USE and following T_WHITESPACE + $tokenIndex++; + $fastForward++; + + $sCount = 0; + $info['statements'][$sCount] = $statementTemplate; $hasAs = false; - $index = $useTokenIndex; - $token = null; - do { + + while (true) { + $tokenIndex++; $fastForward++; - $token = $this->tokens[$index++]; + $token = $this->tokens[$tokenIndex]; + + // BREAK ON: + if (is_string($token) && $token == ';') { + break; + } + + // ANALYZE if (is_array($token)) { + // store known line end $info['lineEnd'] = $token[2]; - if ($token[0] == T_WHITESPACE) { - continue; - } + if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { if ($hasAs == false) { - $statement['use'] .= (is_string($token)) ? $token : $token[1]; + $info['statements'][$sCount]['use'] .= (is_string($token)) ? $token : $token[1]; } else { - $statement['as'] = $token[1]; // always a string + $info['statements'][$sCount]['as'] = $token[1]; // always a string } } if ($token[0] == T_AS) { $hasAs = true; } } - if (is_string($token) && $token == ',' || $token == ';') { + + if (is_string($token) && $token == ',') { if (!$hasAs) { - $statement['asComputed'] = substr($statement['use'], strrpos($statement['use'], '\\')+1); + $statement['asComputed'] = substr( + $info['statements'][$sCount]['use'], + (strrpos($info['statements'][$sCount]['use'],'\\')+1) + ); } - $info['statements'][] = $statement; - $statement = $statementTemplate; + $sCount++; + $info['statements'][$sCount] = $statementTemplate; $hasAs = false; } + } - while (!(is_string($token) && $token == ';')); - $info['tokenEnd'] = $index; + $info['tokenEnd'] = $tokenIndex; $this->infos[] = $info; } - protected function scanInclude($includeTokenIndex, &$fastForward) + protected function scanInclude($tokenIndex, &$fastForward) { static $types = array(T_INCLUDE => 'include', T_INCLUDE_ONCE => 'include_once', T_REQUIRE => 'require', T_REQUIRE_ONCE => 'require_once'); $info = array( 'type' => 'include', - 'tokenStart' => $includeTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$includeTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, - 'includeType' => $types[$this->tokens[$includeTokenIndex][0]], + 'includeType' => $types[$this->tokens[$tokenIndex][0]], 'path' => '' ); $path = ''; - $index = $includeTokenIndex; + $index = $tokenIndex; // move past include & the required whitespace - $fastForward += 2; - $index += 2; + $fastForward++; + $index++; - do { + while (true) { $fastForward++; + $tokenIndex++; $token = $this->tokens[$index++]; + + // BREAK ON + if (is_string($token) && $token == ';') { + break; + } + + // ANALYZE if (is_array($token)) { $info['lineEnd'] = $token[2]; - } + } + $info['path'] .= (is_string($token)) ? $token : $token[1]; - } while (!(is_string($token) && $token == ';')); + } - $info['tokenEnd'] = $index; + $info['tokenEnd'] = $tokenIndex; $this->infos[] = $info; } - protected function scanClass($classTokenIndex, &$fastForward, $namespace = null) + protected function scanClass($tokenIndex, &$fastForward, $namespace = null) { $info = array( 'type' => 'class', - 'tokenStart' => $classTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$classTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'namespace' => $namespace, 'name' => null, 'shortName' => null ); - $index = $classTokenIndex; - - $firstToken = $this->tokens[$classTokenIndex]; - - if ($firstToken[0] === T_FINAL || $firstToken[0] === T_ABSTRACT) { - $info['shortName'] = $this->tokens[$classTokenIndex+4][1]; + // if FINAL or ABSTRACT marker is found, find name accordingly + if ($this->tokens[$tokenIndex][0] === T_FINAL || $this->tokens[$tokenIndex][0] === T_ABSTRACT) { + $info['shortName'] = $this->tokens[$tokenIndex+4][1]; } else { - $info['shortName'] = $this->tokens[$classTokenIndex+2][1]; + $info['shortName'] = $this->tokens[$tokenIndex+2][1]; } $info['name'] = (($namespace) ? $namespace . '\\' : '') . $info['shortName']; - + $braceCount = 0; - do { + while (true) { $fastForward++; - $token = $this->tokens[$index++]; + $tokenIndex++; + $token = $this->tokens[$tokenIndex]; + + // BREAK ON + if (is_string($token) && $token == '}' && $braceCount == 1) { + break; + } + + // ANALYZE if (is_string($token)) { if ($token == '{') { $braceCount++; } if ($token == '}') { - $braceCount = ($braceCount == 1) ? false : ($braceCount - 1); + $braceCount--; } } + if (is_array($token)) { $info['lineEnd'] = $token[2]; } - } while ($braceCount !== false); + + + } - $info['tokenEnd'] = $index; + $info['tokenEnd'] = $tokenIndex; $this->infos[] = $info; } - protected function scanFunction($functionTokenIndex, &$fastForward, $namespace = null, $usesComputed = array()) + protected function scanFunction($tokenIndex, &$fastForward, $namespace = null, $usesComputed = array()) { $info = array( 'type' => 'function', - 'tokenStart' => $functionTokenIndex, + 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$functionTokenIndex][2], + 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, - 'name' => $namespace . '\\' . $this->tokens[$functionTokenIndex+2][1], - 'shortName' => $this->tokens[$functionTokenIndex+2][1], + 'name' => $namespace . '\\' . $this->tokens[$tokenIndex+2][1], + 'shortName' => $this->tokens[$tokenIndex+2][1], 'namespace' => $namespace, ); - $index = $functionTokenIndex; $braceCount = 0; - do { + while (true) { $fastForward++; - $token = $this->tokens[$index++]; + $tokenIndex++; + $token = $this->tokens[$tokenIndex]; + + // BREAK ON + if ($braceCount === false) { + break; + } + + // ANALYZE if (is_string($token)) { if ($token == '{') { $context = null; @@ -263,15 +328,17 @@ protected function scanFunction($functionTokenIndex, &$fastForward, $namespace = if (is_array($token)) { $info['lineEnd'] = $token[2]; } - } while ($braceCount !== false); + } - $info['tokenEnd'] = $index; + $info['tokenEnd'] = $tokenIndex; $this->infos[] = $info; } public function getNamespaces($returnScannerClass = false) { + $this->scan(); + if (!$returnScannerClass) { $namespaces = array(); foreach ($this->infos as $info) { @@ -291,6 +358,8 @@ public function getNamespaces($returnScannerClass = false) public function getUses($returnScannerClass = false) { + $this->scan(); + if (!$returnScannerClass) { $namespaces = array(); foreach ($this->infos as $info) { @@ -310,11 +379,14 @@ public function getUses($returnScannerClass = false) public function getIncludes($returnScannerClass = false) { + $this->scan(); // @todo Implement getIncludes() in ScannerTokenArray } public function getClasses($returnScannerClass = false) { + $this->scan(); + $return = array(); foreach ($this->infos as $info) { @@ -340,6 +412,8 @@ public function getClasses($returnScannerClass = false) */ public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerClass') { + $this->scan(); + // process the class requested static $baseScannerClass = 'Zend\Code\Scanner\ScannerClass'; if ($returnScannerClass !== $baseScannerClass) { @@ -381,7 +455,7 @@ public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code } return new $returnScannerClass( - array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart']), + array_slice($this->tokens, $info['tokenStart'], ($info['tokenEnd'] - $info['tokenStart'] - 1)), // zero indexed array $info['namespace'], $uses ); @@ -389,6 +463,8 @@ public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code public function getFunctions($returnScannerClass = false) { + $this->scan(); + if (!$returnScannerClass) { $functions = array(); foreach ($this->infos as $info) { @@ -406,4 +482,14 @@ public function getFunctions($returnScannerClass = false) } } + public static function export() + { + // @todo + } + + public function __toString() + { + // @todo + } + } diff --git a/library/Zend/Code/Scanner/ScannerValue.php b/library/Zend/Code/Scanner/ScannerValue.php new file mode 100644 index 00000000000..0c63484784c --- /dev/null +++ b/library/Zend/Code/Scanner/ScannerValue.php @@ -0,0 +1,8 @@ +scan(); - $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); - $class->scan(); + $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertEquals('ZendTest\Code\TestAsset\FooClass', $class->getName()); $this->assertEquals('FooClass', $class->getShortName()); $this->assertFalse($class->isFinal()); @@ -29,31 +27,34 @@ public function testScannerClassHasClassInformation() public function testScannerClassHasConstant() { - $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $fileScanner->scan(); - $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); - $class->scan(); + $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertInternalType('array', $class->getConstants()); // } public function testScannerClassHasProperties() { - $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $fileScanner->scan(); - $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); - $class->scan(); + $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertInternalType('array', $class->getProperties()); $this->assertContains('bar', $class->getProperties()); } public function testScannerClassHasMethods() { - $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $fileScanner->scan(); - $class = $fileScanner->getClass('ZendTest\Code\TestAsset\FooClass'); - $class->scan(); + $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertContains('fooBarBaz', $class->getMethods()); } + public function testScannerClassReturnsMethodsWithScannerMethod() + { + $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); + $methods = $class->getMethods(true); + $method = array_shift($methods); + $this->assertInstanceOf('Zend\Code\Scanner\ScannerMethod', $method); + } + } \ No newline at end of file diff --git a/tests/Zend/Code/Scanner/ScannerFileTest.php b/tests/Zend/Code/Scanner/ScannerFileTest.php index b600838ee99..6e3890fe051 100644 --- a/tests/Zend/Code/Scanner/ScannerFileTest.php +++ b/tests/Zend/Code/Scanner/ScannerFileTest.php @@ -7,37 +7,33 @@ class ScannerFileTest extends \PHPUnit_Framework_TestCase { - public function testFileScannerReturnsNamespacesWithoutScannerClass() + public function testScannerFileReturnsNamespacesWithoutScannerClass() { $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $fileScanner->scan(); $namespaces = $fileScanner->getNamespaces(); $this->assertInternalType('array', $namespaces); $this->assertContains('ZendTest\Code\TestAsset', $namespaces); } - public function testFileScannerReturnsClassesWithoutScannerClass() + public function testScannerFileReturnsClassesWithoutScannerClass() { $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $fileScanner->scan(); $classes = $fileScanner->getClasses(); $this->assertInternalType('array', $classes); $this->assertContains('ZendTest\Code\TestAsset\FooClass', $classes); } - public function testFileScannerReturnsFunctionsWithoutScannerClass() + public function testScannerFileReturnsFunctionsWithoutScannerClass() { $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/functions.php'); - $fileScanner->scan(); $functions = $fileScanner->getFunctions(); $this->assertInternalType('array', $functions); $this->assertContains('ZendTest\Code\TestAsset\foo_bar', $functions); } - public function testFileScannerReturnsClassesWithScannerClass() + public function testScannerFileReturnsClassesWithScannerClass() { $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $fileScanner->scan(); $classes = $fileScanner->getClasses(true); $this->assertInternalType('array', $classes); $class = array_shift($classes); diff --git a/tests/Zend/Code/Scanner/ScannerMethodTest.php b/tests/Zend/Code/Scanner/ScannerMethodTest.php new file mode 100644 index 00000000000..4531ac33ebd --- /dev/null +++ b/tests/Zend/Code/Scanner/ScannerMethodTest.php @@ -0,0 +1,42 @@ +getClass('ZendTest\Code\TestAsset\FooClass'); + $method = $class->getMethod('fooBarBaz'); + $this->assertEquals('fooBarBaz', $method->getName()); + $this->assertFalse($method->isAbstract()); + $this->assertTrue($method->isFinal()); + $this->assertTrue($method->isPublic()); + $this->assertFalse($method->isProtected()); + $this->assertFalse($method->isPrivate()); + $this->assertFalse($method->isStatic()); + } + + public function testScannerMethodReturnsParameters() + { + $file = new ScannerFile(__DIR__ . '/../TestAsset/BarClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); + $method = $class->getMethod('three'); + $parameters = $method->getParameters(); + $this->assertInternalType('array', $parameters); + } + + public function testScannerMethodReturnsParameterScanner() + { + $file = new ScannerFile(__DIR__ . '/../TestAsset/BarClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); + $method = $class->getMethod('three'); + $parameters = $method->getParameter('t'); + var_dump($parameters); + } + +} diff --git a/tests/Zend/Code/TestAsset/BarClass.php b/tests/Zend/Code/TestAsset/BarClass.php new file mode 100644 index 00000000000..04b0853252d --- /dev/null +++ b/tests/Zend/Code/TestAsset/BarClass.php @@ -0,0 +1,33 @@ + Date: Wed, 1 Jun 2011 21:56:32 +0200 Subject: [PATCH 29/45] Circular dependencies check + unit test --- library/Zend/Di/DependencyInjector.php | 64 +++++++++++------------- tests/Zend/Di/DependencyInjectorTest.php | 33 ++++++++++++ tests/Zend/Di/TestAsset/ClassA.php | 19 +++++++ tests/Zend/Di/TestAsset/ClassB.php | 20 ++++++++ 4 files changed, 101 insertions(+), 35 deletions(-) mode change 100644 => 100755 library/Zend/Di/DependencyInjector.php mode change 100644 => 100755 tests/Zend/Di/DependencyInjectorTest.php create mode 100755 tests/Zend/Di/TestAsset/ClassA.php create mode 100755 tests/Zend/Di/TestAsset/ClassB.php diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php old mode 100644 new mode 100755 index b836a6de384..8ef9ecc63b5 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -110,7 +110,28 @@ public function setDefinitions($definitions) } return $this; } - + /** + * Check for Circular Dependencies + * + * @param string $class + * @param array|string $dependency + * @return boolean + */ + protected function checkCircularDependency($class, $dependency) + { + if (is_array($dependency)) { + foreach ($dependency as $dep) { + if (isset($this->dependencies[$dep][$class]) && $this->dependencies[$dep][$class]) { + throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dep and viceversa"); + } + } + } else { + if (isset($this->dependencies[$dependency][$class]) && $this->dependencies[$dependency][$class]) { + throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dependency and viceversa"); + } + } + return true; + } /** * Add a definition, optionally with a service name alias * @@ -125,6 +146,13 @@ public function setDefinition(DependencyDefinition $definition, $serviceName = n if (null !== $serviceName && !empty($serviceName)) { $this->aliases[$serviceName] = $className; } + foreach ($definition->getParams() as $param) { + if ($param instanceof Reference) { + $serviceName= $param->getServiceName(); + $this->dependencies[$className][$serviceName]= true; + $this->checkCircularDependency($className, $serviceName); + } + } return $this; } @@ -201,28 +229,6 @@ protected function getDefinitionFromAlias($name) return $this->definitions[$service]; } - /** - * Check for Circular Dependencies - * - * @param string $class - * @param array|string $dependency - * @return boolean - */ - protected function checkCircularDependency($class, $dependency) - { - if (is_array($dependency)) { - foreach ($dependency as $dep) { - if (isset($this->dependencies[$dep][$class]) && $this->dependencies[$dep][$class]) { - throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dep and viceversa"); - } - } - } else { - if (isset($this->dependencies[$dependency][$class]) && $this->dependencies[$dependency][$class]) { - throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dependency and viceversa"); - } - } - return true; - } /** * Retrieve a class instance based on class name * @@ -236,7 +242,6 @@ protected function checkCircularDependency($class, $dependency) */ protected function getInstanceFromClassName($class, array $params) { - $originalParams= $params; // Hack to avoid Reflection in most common use cases switch (count($params)) { case 0: @@ -248,31 +253,20 @@ protected function getInstanceFromClassName($class, array $params) } if ($param instanceof DependencyReference) { $param = $this->get($param->getServiceName()); - $this->dependencies[$class][$param]= true; } - $this->checkCircularDependency($class, $param); return new $class($param); case 2: $param1 = array_shift($params); if ($param1 instanceof DependencyReference) { $param1 = $this->get($param1->getServiceName()); - $this->dependencies[$class][$param1]= true; } $param2 = array_shift($params); if ($param2 instanceof DependencyReference) { $param2 = $this->get($param2->getServiceName()); - $this->dependencies[$class][$param2]= true; } - $this->checkCircularDependency($class, $originalParams); return new $class($param1, $param2); default: - foreach ($params as $key => $value) { - if ($value instanceof DependencyReference) { - $this->dependencies[$class][$value]= true; - } - } $params = $this->resolveReferences($params); - $this->checkCircularDependency($class, $originalParams); $r = new \ReflectionClass($class); return $r->newInstanceArgs($params); } diff --git a/tests/Zend/Di/DependencyInjectorTest.php b/tests/Zend/Di/DependencyInjectorTest.php old mode 100644 new mode 100755 index fabb08930bb..8069bdd450c --- a/tests/Zend/Di/DependencyInjectorTest.php +++ b/tests/Zend/Di/DependencyInjectorTest.php @@ -342,6 +342,39 @@ public function testConstructorCallbackAllowsPassingReferences() $this->assertSame($testParams, $testStruct->params, sprintf('Params: %s; struct: %s', var_export($testParams, 1), var_export($testStruct, 1))); } + /** + * Test for Circular Dependencies + */ + public function testCircularDependencies() + { + $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); + $classA->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); + $this->di->setDefinition($classA, 'A'); + + $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); + $classB->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassA')); + $this->setExpectedException('Zend\Di\Exception\RuntimeException'); + $this->di->setDefinition($classB, 'B'); + } + /** + * Test for No Circular Dependencies + */ + public function testNoCircularDependencies() + { + $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); + $this->di->setDefinition($classA, 'A'); + + $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); + $this->di->setDefinition($classB, 'B'); + + $a= $this->di->get('A'); + $b= $this->di->get('B'); + + $a->setParam($b); + $b->setParam($a); + $this->assertEquals($a->getParam(),$b); + $this->assertEquals($b->getParam(),$a); + } /** * @todo tests for recursive DI calls */ diff --git a/tests/Zend/Di/TestAsset/ClassA.php b/tests/Zend/Di/TestAsset/ClassA.php new file mode 100755 index 00000000000..8bef8ea2059 --- /dev/null +++ b/tests/Zend/Di/TestAsset/ClassA.php @@ -0,0 +1,19 @@ +param = $param; + } + public function getParam() + { + return $this->param; + } + public function setParam($param) { + $this->param= $param; + } +} diff --git a/tests/Zend/Di/TestAsset/ClassB.php b/tests/Zend/Di/TestAsset/ClassB.php new file mode 100755 index 00000000000..5789b09b40f --- /dev/null +++ b/tests/Zend/Di/TestAsset/ClassB.php @@ -0,0 +1,20 @@ +param = $param; + } + public function getParam() + { + return $this->param; + } + public function setParam($param) + { + $this->param= $param; + } +} From 3c385928cb4afb165dc42346118069bb70507b82 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Wed, 1 Jun 2011 16:16:02 -0500 Subject: [PATCH 30/45] Completed the parameter scanner for Zend\Code\Scanner Added additional tests --- library/Zend/Code/Scanner/ScannerClass.php | 13 +- library/Zend/Code/Scanner/ScannerMethod.php | 36 ++- .../Zend/Code/Scanner/ScannerParameter.php | 216 +++++++++++++++++- .../Zend/Code/Scanner/ScannerTokenArray.php | 2 +- tests/Zend/Code/Scanner/ScannerClassTest.php | 2 +- tests/Zend/Code/Scanner/ScannerMethodTest.php | 7 +- .../Code/Scanner/ScannerParameterTest.php | 27 +++ tests/Zend/Code/TestAsset/BarClass.php | 2 +- 8 files changed, 286 insertions(+), 19 deletions(-) create mode 100644 tests/Zend/Code/Scanner/ScannerParameterTest.php diff --git a/library/Zend/Code/Scanner/ScannerClass.php b/library/Zend/Code/Scanner/ScannerClass.php index 8cb9d3a1ba3..e00c042e548 100644 --- a/library/Zend/Code/Scanner/ScannerClass.php +++ b/library/Zend/Code/Scanner/ScannerClass.php @@ -398,6 +398,11 @@ public function getMethods($returnScannerMethod = false) return $return; } + /** + * @param string|int $methodNameOrInfoIndex + * @param string $returnScannerClass + * @return Zend\Code\Scanner\ScannerMethod + */ public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerMethod') { $this->scan(); @@ -432,11 +437,13 @@ public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Co } } - return new $returnScannerClass( - array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] - 1), - $this->name, + $m = new $returnScannerClass( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1), $this->uses ); + $m->setClass($this->name); + $m->setScannerClass($this); + return $m; } public static function export() diff --git a/library/Zend/Code/Scanner/ScannerMethod.php b/library/Zend/Code/Scanner/ScannerMethod.php index 5354a2eb2ca..c0c38082a44 100644 --- a/library/Zend/Code/Scanner/ScannerMethod.php +++ b/library/Zend/Code/Scanner/ScannerMethod.php @@ -6,6 +6,7 @@ class ScannerMethod implements ScannerInterface { protected $isScanned = false; + protected $scannerClass = null; protected $class = null; protected $uses = array(); protected $name = null; @@ -19,13 +20,27 @@ class ScannerMethod implements ScannerInterface protected $tokens = array(); protected $infos = array(); - public function __construct(array $methodTokens, $class = null, array $uses = array()) + public function __construct(array $methodTokens, array $uses = array()) { $this->tokens = $methodTokens; - $this->class = $class; $this->uses = $uses; } + public function setClass($class) + { + $this->class = $class; + } + + public function setScannerClass(ScannerClass $scannerClass) + { + $this->scannerClass = $scannerClass; + } + + public function getClassScanner() + { + return $this->scannerClass; + } + protected function scan() { if ($this->isScanned) { @@ -114,6 +129,7 @@ protected function scanParameters($tokenIndex, &$fastForward) // first token is paren let loop increase $parenCount = 1; $info = null; + $position = 0; while (true) { $tokenIndex++; @@ -138,7 +154,7 @@ protected function scanParameters($tokenIndex, &$fastForward) if ($parenCount == 1 && isset($info)) { $nextToken = (isset($info['name']) && is_string($this->tokens[$tokenIndex+1])) ? $this->tokens[$tokenIndex+1] : null; if ((is_string($token) && $token == ',') || (isset($nextToken) && $nextToken == ')')) { - $info['tokenEnd'] = $tokenIndex; + $info['tokenEnd'] = $tokenIndex - 1; $this->infos[] = $info; unset($info); } @@ -161,7 +177,8 @@ protected function scanParameters($tokenIndex, &$fastForward) 'tokenEnd' => null, 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, - 'name' => null + 'name' => null, + 'position' => ++$position ); } @@ -269,11 +286,16 @@ public function getParameter($parameterNameOrInfoIndex, $returnScanner = 'Zend\C } } - return new $returnScanner( - array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] - 1), - $this->name, + $p = new $returnScanner( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1), $this->uses ); + $p->setDeclaringFunction($this->name); + $p->setDeclaringScannerFunction($this); + $p->setDeclaringClass($this->class); + $p->setDeclaringScannerClass($this->scannerClass); + $p->setPosition($info['position']); + return $p; } public static function export() diff --git a/library/Zend/Code/Scanner/ScannerParameter.php b/library/Zend/Code/Scanner/ScannerParameter.php index eb502b2725e..7b1508e07a3 100644 --- a/library/Zend/Code/Scanner/ScannerParameter.php +++ b/library/Zend/Code/Scanner/ScannerParameter.php @@ -4,14 +4,224 @@ class ScannerParameter { - protected $tokens = null; + protected $isScanned = false; + + protected $declaringScannerClass = null; + protected $declaringClass = null; + protected $declaringScannerFunction = null; + protected $declaringFunction = null; + protected $defaultValue = null; protected $class = null; + protected $name = null; + protected $position = null; + protected $isArray = false; + protected $isDefaultValueAvailable = false; + protected $isOptional = false; + protected $isPassedByReference = false; + + protected $tokens = null; protected $uses = array(); - public function __construct(array $parameterTokens, $class = null, array $uses = array()) + public function __construct(array $parameterTokens, array $uses = array()) { $this->tokens = $parameterTokens; - $this->class = $class; $this->uses = $uses; } + + public function setDeclaringClass($class) + { + $this->declaringClass = $class; + } + + public function setDeclaringScannerClass(ScannerClass $scannerClass) + { + $this->declaringScannerClass = $scannerClass; + } + + public function setDeclaringFunction($function) + { + $this->declaringFunction = $function; + } + + public function setDeclaringScannerFunction(ScannerMethod $scannerFunction) + { + $this->declaringScannerFunction = $scannerFunction; + } + + public function setPosition($position) + { + $this->position = $position; + } + + protected function scan() + { + if ($this->isScanned) { + return; + } + + $tokenIndex = 0; + $token = $this->tokens[$tokenIndex]; + + if ($token[0] !== T_VARIABLE) { + while (true) { + if ($token[0] == T_WHITESPACE) { + break; + } + $this->class .= $token[1]; + $token = $this->tokens[++$tokenIndex]; + } + } + + if ($token[0] == T_WHITESPACE) { + $token = $this->tokens[++$tokenIndex]; + } + + if (is_string($token) && $token == '&') { + $this->isPassedByReference = true; + $token = $this->tokens[++$tokenIndex]; + } + + // next token is sure a T_VARIABLE + $this->name = ltrim($token[1], '$'); + $token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : null; + + if (!$token) { + return; + } + + // move past whitespace if it exist + if ($token[0] == T_WHITESPACE) { + $token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : null; + } + + if (!$token) { + return; + } + + if (!(is_string($token) && $token == '=')) { + return; + } + + // get past = + $token = $this->tokens[++$tokenIndex]; + + // move past whitespace if it exist + if ($token[0] == T_WHITESPACE) { + $token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : null; + } + + $this->isOptional = true; + $this->isDefaultValueAvailable = true; + + do { + $this->defaultValue .= ((is_array($token)) ? $token[1] : $token); + } while (($token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : false)); + + $this->isScanned = true; + } + /** + * @return the $declaringScannerClass + */ + public function getDeclaringScannerClass() + { + return $this->declaringScannerClass; + } + + /** + * @return the $declaringClass + */ + public function getDeclaringClass() + { + return $this->declaringClass; + } + + /** + * @return the $declaringScannerFunction + */ + public function getDeclaringScannerFunction() + { + return $this->declaringScannerFunction; + } + + /** + * @return the $declaringFunction + */ + public function getDeclaringFunction() + { + return $this->declaringFunction; + } + + /** + * @return the $defaultValue + */ + public function getDefaultValue() + { + $this->scan(); + return $this->defaultValue; + } + + /** + * @return the $class + */ + public function getClass() + { + $this->scan(); + return $this->class; + } + + /** + * @return the $name + */ + public function getName() + { + $this->scan(); + return $this->name; + } + + /** + * @return the $position + */ + public function getPosition() + { + $this->scan(); + return $this->position; + } + + /** + * @return the $isArray + */ + public function isArray() + { + $this->scan(); + return $this->isArray; + } + + /** + * @return the $isDefaultValueAvailable + */ + public function isDefaultValueAvailable() + { + $this->scan(); + return $this->isDefaultValueAvailable; + } + + /** + * @return the $isOptional + */ + public function isOptional() + { + $this->scan(); + return $this->isOptional; + } + + /** + * @return the $isPassedByReference + */ + public function isPassedByReference() + { + $this->scan(); + return $this->isPassedByReference; + } + + } \ No newline at end of file diff --git a/library/Zend/Code/Scanner/ScannerTokenArray.php b/library/Zend/Code/Scanner/ScannerTokenArray.php index f653b5781de..bbd6e80cb65 100644 --- a/library/Zend/Code/Scanner/ScannerTokenArray.php +++ b/library/Zend/Code/Scanner/ScannerTokenArray.php @@ -455,7 +455,7 @@ public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code } return new $returnScannerClass( - array_slice($this->tokens, $info['tokenStart'], ($info['tokenEnd'] - $info['tokenStart'] - 1)), // zero indexed array + array_slice($this->tokens, $info['tokenStart'], ($info['tokenEnd'] - $info['tokenStart'] + 1)), // zero indexed array $info['namespace'], $uses ); diff --git a/tests/Zend/Code/Scanner/ScannerClassTest.php b/tests/Zend/Code/Scanner/ScannerClassTest.php index 9de2b53d892..20d5a0045dc 100644 --- a/tests/Zend/Code/Scanner/ScannerClassTest.php +++ b/tests/Zend/Code/Scanner/ScannerClassTest.php @@ -4,7 +4,7 @@ use Zend\Code\Scanner\ScannerFile; -final class ScannerClassTest extends \PHPUnit_Framework_TestCase +class ScannerClassTest extends \PHPUnit_Framework_TestCase { public function testScannerClassHasClassInformation() diff --git a/tests/Zend/Code/Scanner/ScannerMethodTest.php b/tests/Zend/Code/Scanner/ScannerMethodTest.php index 4531ac33ebd..7f1ad687acc 100644 --- a/tests/Zend/Code/Scanner/ScannerMethodTest.php +++ b/tests/Zend/Code/Scanner/ScannerMethodTest.php @@ -4,7 +4,7 @@ use Zend\Code\Scanner\ScannerFile; -final class ScannerMethodTest extends \PHPUnit_Framework_TestCase +class ScannerMethodTest extends \PHPUnit_Framework_TestCase { public function testScannerMethodHasMethodInformation() @@ -35,8 +35,9 @@ public function testScannerMethodReturnsParameterScanner() $file = new ScannerFile(__DIR__ . '/../TestAsset/BarClass.php'); $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); $method = $class->getMethod('three'); - $parameters = $method->getParameter('t'); - var_dump($parameters); + $parameter = $method->getParameter('t'); + $this->assertInstanceOf('Zend\Code\Scanner\ScannerParameter', $parameter); + $this->assertEquals('t', $parameter->getName()); } } diff --git a/tests/Zend/Code/Scanner/ScannerParameterTest.php b/tests/Zend/Code/Scanner/ScannerParameterTest.php new file mode 100644 index 00000000000..78182c84e83 --- /dev/null +++ b/tests/Zend/Code/Scanner/ScannerParameterTest.php @@ -0,0 +1,27 @@ +getClass('ZendTest\Code\TestAsset\BarClass'); + $method = $class->getMethod('three'); + $parameter = $method->getParameter('t'); + $this->assertEquals('ZendTest\Code\TestAsset\BarClass', $parameter->getDeclaringClass()); + $this->assertEquals('three', $parameter->getDeclaringFunction()); + $this->assertEquals('t', $parameter->getName()); + $this->assertEquals(2, $parameter->getPosition()); + $this->assertEquals('2', $parameter->getDefaultValue()); + $this->assertFalse($parameter->isArray()); + $this->assertTrue($parameter->isDefaultValueAvailable()); + $this->assertTrue($parameter->isOptional()); + $this->assertTrue($parameter->isPassedByReference()); + + } +} \ No newline at end of file diff --git a/tests/Zend/Code/TestAsset/BarClass.php b/tests/Zend/Code/TestAsset/BarClass.php index 04b0853252d..1ad7e98cbec 100644 --- a/tests/Zend/Code/TestAsset/BarClass.php +++ b/tests/Zend/Code/TestAsset/BarClass.php @@ -25,7 +25,7 @@ protected function two(\ArrayObject $o = null) // two } - protected function three(\ArrayObject $o, $t = 2, FooBarBaz\BazBarFoo $bbf = self::BAR) + protected function three(\ArrayObject $o, &$t = 2, FooBarBaz\BazBarFoo $bbf = self::BAR) { // two } From 6708970b4bb19a69c2adfe6b368f33a5e9c0e454 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Sun, 5 Jun 2011 15:53:47 -0500 Subject: [PATCH 31/45] Parameter maps for method objects - Implemented parameter maps for method objects - TODO: ordering method arguments should likely be moved to a separate object/static method, as the logic is basically the same between constructor params in Definition objects and params in InjectibleMethods. --- library/Zend/Di/Definition.php | 28 +++- library/Zend/Di/DependencyDefinition.php | 2 +- library/Zend/Di/InjectibleMethod.php | 7 +- library/Zend/Di/Method.php | 167 ++++++++++++++++++++++- tests/Zend/Di/DefinitionTest.php | 47 ++++++- tests/Zend/Di/MethodTest.php | 62 +++++++++ 6 files changed, 302 insertions(+), 11 deletions(-) diff --git a/library/Zend/Di/Definition.php b/library/Zend/Di/Definition.php index 102d59b7d49..1ac39b93076 100644 --- a/library/Zend/Di/Definition.php +++ b/library/Zend/Di/Definition.php @@ -240,12 +240,32 @@ public function isShared() * Add a method to be called and injected * * @param string $name - * @param array $params + * @param array|null $params + * @param array|null $paramMap * @return Definition */ - public function addMethodCall($name, array $params) + public function addMethodCall($name, array $params = null, array $paramMap = null) { - $method = new Method($name, $params); + if (!is_string($name) && !($name instanceof InjectibleMethod)) { + throw new Exception\InvalidArgumentException(sprintf( + 'addMethodCall() expects a string method name or InjectibleMethod object as an argument; received "%s"', + (is_object($name) ? get_class($name) : gettype($name)) + )); + } + + if ($name instanceof InjectibleMethod) { + $method = $name; + if (is_array($params)) { + $method->setParams($params); + } + if (is_array($paramMap)) { + $method->setParamMap($paramMap); + } + } else { + $method = new Method($name, $params, $paramMap); + } + + $method->setClass($this->getClass()); $this->injectibleMethods->insert($method); return $this; } @@ -281,7 +301,7 @@ public function toArray() } } $methods[] = array( - 'name' => $method->getName(), + 'name' => $method->getName(), 'params' => $methodParams, ); } diff --git a/library/Zend/Di/DependencyDefinition.php b/library/Zend/Di/DependencyDefinition.php index 294cdbd24d0..7b13b1228a2 100644 --- a/library/Zend/Di/DependencyDefinition.php +++ b/library/Zend/Di/DependencyDefinition.php @@ -23,7 +23,7 @@ public function getParams(); public function setShared($flag = true); public function isShared(); - public function addMethodCall($name, array $params); + public function addMethodCall($name, array $params = null, array $paramMap = null); /** * @return InjectibleMethods */ diff --git a/library/Zend/Di/InjectibleMethod.php b/library/Zend/Di/InjectibleMethod.php index 04210b615b0..76d4976f9b0 100644 --- a/library/Zend/Di/InjectibleMethod.php +++ b/library/Zend/Di/InjectibleMethod.php @@ -3,7 +3,12 @@ interface InjectibleMethod { - public function __construct($name, array $params); + public function __construct($name, array $params = null, array $paramMap = null); public function getName(); + public function setParams(array $params); public function getParams(); + public function setClass($class); + public function getClass(); + public function setParamMap(array $map); + public function getParamMap(); } diff --git a/library/Zend/Di/Method.php b/library/Zend/Di/Method.php index fb9a2ab0147..458806ad694 100644 --- a/library/Zend/Di/Method.php +++ b/library/Zend/Di/Method.php @@ -1,6 +1,9 @@ argument name) + * @var array + */ + protected $paramMap; + + /** + * The parameters in argument order + * @var null|array + */ + protected $orderedParams; + /** * Construct the method signature * * @param strinb $name * @param array $params + * @param array|null $paramMap * @return void */ - public function __construct($name, array $params) + public function __construct($name, array $params = null, array $paramMap = null) { - $this->name = $name; - $this->params = $params; + $this->name = $name; + if (is_array($params)) { + $this->setParams($params); + } + if (is_array($paramMap)) { + $this->setParamMap($paramMap); + } } /** @@ -48,11 +75,143 @@ public function getName() /** * Retrieve the arguments to pass to the method + * + * If no parameter map is present, the parameter values are returned in + * the order defined. + * + * If a parameter map is found, the parameter values are returned in the + * order specified by the map. * * @return array */ public function getParams() { - return $this->params; + if (null !== $this->orderedParams) { + return $this->orderedParams; + } + + $map = $this->getParamMap(); + if (!$map) { + return array_values($this->params); + } + + // Sort map, and flip such that positions become keys + asort($map); + $map = array_flip($map); + + $params = array(); + foreach ($map as $index => $key) { + $value = null; + if (isset($this->params[$key])) { + $value = $this->params[$key]; + } elseif (isset($this->params[$index])) { + $value = $this->params[$index]; + } + $params[] = $value; + } + + $this->orderedParams = $params; + return $params; + } + + /** + * Set method parameters + * + * @param array $params + * @return Method + */ + public function setParams(array $params) + { + $this->params = $params; + $this->orderedParams = null; + return $this; + } + + /** + * Set class to which method belongs + * + * @param string $class + * @return Method + */ + public function setClass($class) + { + $this->class = $class; + return $this; + } + + /** + * Retrieve class to which method belongs, if set. + * + * @return null|string + */ + public function getClass() + { + return $this->class; + } + + /** + * Set parameter map (order of named arguments) + * + * Should be an ordered array, with string keys pointing to integer + * placement order. + * + * @param array $map + * @return Method + */ + public function setParamMap(array $map) + { + $this->paramMap = $map; + return $this; + } + + /** + * Get parameter map + * + * If a parameter map is present, returns it. + * + * If no map is set, and no class is set, returns false. + * + * If no map is set, but the class is set, creates a parameter map by + * reflecting on the method. + * + * @return array|false + */ + public function getParamMap() + { + if (is_array($this->paramMap)) { + return $this->paramMap; + } + + $class = $this->getClass(); + if ((null === $class) || !class_exists($class)) { + return false; + } + + $map = $this->buildParamMapFromReflection(); + $this->setParamMap($map); + return $map; + } + + /** + * Create a parameter map from Reflection + * + * @return array + */ + protected function buildParamMapFromReflection() + { + try { + $method = new ReflectionMethod($this->getClass(), $this->getName()); + } catch (ReflectionException $e) { + throw new Exception\RuntimeException(sprintf( + 'Method definition for method "%s" cannot be reflected', + $this->getName() + ), $e->getCode(), $e); + } + $parameters = $method->getParameters(); + $map = array(); + foreach ($parameters as $param) { + $map[$param->getName()] = $param->getPosition(); + } + return $map; } } diff --git a/tests/Zend/Di/DefinitionTest.php b/tests/Zend/Di/DefinitionTest.php index 890173628c1..19559d3800b 100644 --- a/tests/Zend/Di/DefinitionTest.php +++ b/tests/Zend/Di/DefinitionTest.php @@ -1,7 +1,9 @@ definition = new Definition(__CLASS__); } + /** + * Stub for testing + */ + public function foo() + { + } + + /** + * Stub for testing + */ + public function bar($message) + { + } + public function testCanRetrieveConfiguredClassName() { $this->assertEquals(__CLASS__, $this->definition->getClass()); @@ -129,6 +145,9 @@ public function testCanOverrideSharedFlag() $this->assertFalse($this->definition->isShared()); } + /** + * @group fml + */ public function testAddingMethodCallsAggregates() { $this->definition->addMethodCall('foo', array()); @@ -149,6 +168,32 @@ public function testAddingMethodCallsAggregates() } } + public function testCanPassInjectibleMethodObjectToAddMethodCall() + { + $definition = new Definition('Foo'); + $method = new Method('bar', array()); + $definition->addMethodCall($method); + $this->assertEquals('Foo', $method->getClass()); + } + + public function testCanPassParameterMapWhenCallingAddMethodCallWithStringMethod() + { + $definition = new Definition('Foo'); + $definition->addMethodCall('bar', array('message' => 'BAR'), array('message' => 0)); + foreach ($definition->getMethodCalls() as $method) { + $this->assertEquals(array('message' => 0), $method->getParamMap()); + } + } + + public function testCanPassParametersAndParameterMapWhenCallingAddMethodCallWithInjectibleMethod() + { + $definition = new Definition('Foo'); + $method = new Method('bar', array()); + $definition->addMethodCall($method, array('message' => 'BAR'), array('message' => 0)); + $this->assertEquals(array('message' => 0), $method->getParamMap()); + $this->assertEquals(array('BAR'), $method->getParams()); + } + public function testCanSerializeDefinitionToArray() { $definition = new Definition('Foo'); diff --git a/tests/Zend/Di/MethodTest.php b/tests/Zend/Di/MethodTest.php index d7974e2e1a9..3a2cd9f6211 100644 --- a/tests/Zend/Di/MethodTest.php +++ b/tests/Zend/Di/MethodTest.php @@ -29,4 +29,66 @@ public function testMethodReturnsParamsPassedToConstructor() $method = new Method($name, $params); $this->assertEquals($params, $method->getParams()); } + + public function testNoClassSetByDefault() + { + $name = uniqid(); + $method = new Method($name, array()); + $this->assertNull($method->getClass()); + } + + public function testClassIsMutable() + { + $name = uniqid(); + $method = new Method($name, array()); + $method->setClass(__CLASS__); + $this->assertEquals(__CLASS__, $method->getClass()); + } + + public function testParamsAreReturnedInOrderProvidedWhenNoParamMapGivenAndNoClassProvided() + { + $name = uniqid(); + $method = new Method($name, array('bar' => 'BAR', 'foo' => 'FOO')); + + $params = $method->getParams(); + $this->assertEquals(array('BAR', 'FOO'), $params); + } + + public function testParamsAreReturnedInArgOrderWhenNoParamMapGivenAndClassIsProvided() + { + $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO')); + $method->setClass($this); + $params = $method->getParams(); + $this->assertEquals(array('FOO', 'BAR'), $params); + } + + public function testParamsAreReturnedInParamMapOrderWhenNoClassProvided() + { + $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO'), array('foo' => 0, 'bar' => 1)); + $params = $method->getParams(); + $this->assertEquals(array('FOO', 'BAR'), $params); + } + + public function testParamsAreReturnedInParamMapOrderWhenClassProvided() + { + $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO'), array('bar' => 0, 'foo' => 1)); + $method->setClass($this); + $params = $method->getParams(); + $this->assertEquals(array('BAR', 'FOO'), $params); + } + + public function testParamMapIsMutable() + { + $map = array('bar' => 0, 'foo' => 1); + $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO'), $map); + $this->assertEquals($map, $method->getParamMap()); + + $map = array('foo' => 0, 'bar' => 1); + $method->setParamMap($map); + $this->assertEquals($map, $method->getParamMap()); + } + + public function methodWithArgs($foo, $bar) + { + } } From 28e08e207e709fe93da79ae1911303cc31d016c8 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Jun 2011 19:05:44 +0200 Subject: [PATCH 32/45] Improved the check of circular dependencies (more general) --- library/Zend/Di/DependencyInjector.php | 27 +++++++++++- tests/Zend/Di/DependencyInjectorTest.php | 54 ++++++++++++++++++++++-- tests/Zend/Di/TestAsset/ClassC.php | 20 +++++++++ tests/Zend/Di/TestAsset/ClassD.php | 20 +++++++++ 4 files changed, 116 insertions(+), 5 deletions(-) create mode 100755 tests/Zend/Di/TestAsset/ClassC.php create mode 100755 tests/Zend/Di/TestAsset/ClassD.php diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php index 8ef9ecc63b5..e8d8ae08cf7 100755 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -26,7 +26,12 @@ class DependencyInjector implements DependencyInjection * @var array */ protected $dependencies= array(); - + /** + * All the class references [dependency][source] + * + * @var array + */ + protected $references= array(); /** * Lazy-load a class * @@ -132,6 +137,24 @@ protected function checkCircularDependency($class, $dependency) } return true; } + /** + * Check the circular dependencies path between two definitions + * + * @param type $class + * @param type $dependency + * @return void + */ + protected function checkPathDependencies($class, $dependency) { + if (!empty($this->references[$class])) { + foreach ($this->references[$class] as $key => $value) { + if ($this->dependencies[$key][$class]) { + $this->dependencies[$key][$dependency]= true; + $this->checkCircularDependency($key, $dependency); + $this->checkPathDependencies($key,$dependency); + } + } + } + } /** * Add a definition, optionally with a service name alias * @@ -150,7 +173,9 @@ public function setDefinition(DependencyDefinition $definition, $serviceName = n if ($param instanceof Reference) { $serviceName= $param->getServiceName(); $this->dependencies[$className][$serviceName]= true; + $this->references[$serviceName][$className]= true; $this->checkCircularDependency($className, $serviceName); + $this->checkPathDependencies($className, $serviceName); } } return $this; diff --git a/tests/Zend/Di/DependencyInjectorTest.php b/tests/Zend/Di/DependencyInjectorTest.php index 8069bdd450c..fb43b83b3ac 100755 --- a/tests/Zend/Di/DependencyInjectorTest.php +++ b/tests/Zend/Di/DependencyInjectorTest.php @@ -342,10 +342,12 @@ public function testConstructorCallbackAllowsPassingReferences() $this->assertSame($testParams, $testStruct->params, sprintf('Params: %s; struct: %s', var_export($testParams, 1), var_export($testStruct, 1))); } - /** - * Test for Circular Dependencies + /** + * Test for Circular Dependencies (case 1) + * + * A->B, B->A */ - public function testCircularDependencies() + public function testCircularDependencies1() { $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); $classA->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); @@ -357,7 +359,51 @@ public function testCircularDependencies() $this->di->setDefinition($classB, 'B'); } /** - * Test for No Circular Dependencies + * Test for Circular Dependencies (case 2) + * + * A->B, B->C, C->A + */ + public function testCircularDependencies2() + { + $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); + $classA->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); + $this->di->setDefinition($classA, 'A'); + + $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); + $classB->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassC')); + $this->di->setDefinition($classB, 'B'); + + $classC= new Definition('ZendTest\Di\TestAsset\ClassC'); + $classC->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassA')); + $this->setExpectedException('Zend\Di\Exception\RuntimeException'); + $this->di->setDefinition($classC, 'C'); + } + /** + * Test for Circular Dependencies (case 3) + * + * A->B, B->C, C->D, D->B + */ + public function testCircularDependencies3() + { + $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); + $classA->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); + $this->di->setDefinition($classA, 'A'); + + $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); + $classB->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassC')); + $this->di->setDefinition($classB, 'B'); + + $classC= new Definition('ZendTest\Di\TestAsset\ClassC'); + $classC->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassD')); + $this->di->setDefinition($classC, 'C'); + + $classD= new Definition('ZendTest\Di\TestAsset\ClassD'); + $classD->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); + $this->setExpectedException('Zend\Di\Exception\RuntimeException'); + $this->di->setDefinition($classD, 'D'); + } + /** + * Test for NO Circular Dependencies */ public function testNoCircularDependencies() { diff --git a/tests/Zend/Di/TestAsset/ClassC.php b/tests/Zend/Di/TestAsset/ClassC.php new file mode 100755 index 00000000000..493a365acbe --- /dev/null +++ b/tests/Zend/Di/TestAsset/ClassC.php @@ -0,0 +1,20 @@ +param = $param; + } + public function getParam() + { + return $this->param; + } + public function setParam($param) + { + $this->param= $param; + } +} diff --git a/tests/Zend/Di/TestAsset/ClassD.php b/tests/Zend/Di/TestAsset/ClassD.php new file mode 100755 index 00000000000..d05a79aff08 --- /dev/null +++ b/tests/Zend/Di/TestAsset/ClassD.php @@ -0,0 +1,20 @@ +param = $param; + } + public function getParam() + { + return $this->param; + } + public function setParam($param) + { + $this->param= $param; + } +} From b4464058bb4ff743a885a1416176fd29292e87de Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Tue, 7 Jun 2011 07:50:11 -0500 Subject: [PATCH 33/45] Fixes to Zend\Code\Scanner's parameter handling --- library/Zend/Code/Scanner/ScannerClass.php | 27 +++-- .../Zend/Code/Scanner/ScannerDirectory.php | 103 +++++++++++++++++- library/Zend/Code/Scanner/ScannerFile.php | 7 ++ library/Zend/Code/Scanner/ScannerMethod.php | 66 ++++++----- .../Zend/Code/Scanner/ScannerParameter.php | 51 +++++++++ tests/Zend/Code/Scanner/ScannerMethodTest.php | 1 + .../Code/Scanner/ScannerParameterTest.php | 2 +- 7 files changed, 214 insertions(+), 43 deletions(-) diff --git a/library/Zend/Code/Scanner/ScannerClass.php b/library/Zend/Code/Scanner/ScannerClass.php index e00c042e548..5b8049e3421 100644 --- a/library/Zend/Code/Scanner/ScannerClass.php +++ b/library/Zend/Code/Scanner/ScannerClass.php @@ -129,9 +129,7 @@ protected function scanClassInfo($tokenIndex, &$fastForward) } } if ($token[0] == T_EXTENDS) { - die('found extends'); - $fastForward += 2; - $tokenIndex += 2; + $context = T_EXTENDS; $this->shortParentClass = ''; } if ($token[0] == T_IMPLEMENTS) { @@ -332,6 +330,12 @@ public function isInterface() return $this->isInterface; } + public function getParentClass() + { + $this->scan(); + return $this->parentClass; + } + public function getInterfaces() { $this->scan(); @@ -348,12 +352,7 @@ public function getConstants() if ($info['type'] != 'constant') { continue; } - - //if (!$returnScanner) { - $return[] = $info['name']; - //} else { - // $return[] = $this->getClass($info['name'], $returnScannerProperty); - //} + $return[] = $info['name']; } return $return; } @@ -446,6 +445,16 @@ public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Co return $m; } + public function hasMethod($name) + { + foreach ($this->infos as $infoIndex => $info) { + if ($info['type'] === 'method' && $info['name'] === $name) { + return true; + } + } + return false; + } + public static function export() { // @todo diff --git a/library/Zend/Code/Scanner/ScannerDirectory.php b/library/Zend/Code/Scanner/ScannerDirectory.php index 6204dbede8f..89a196b1c09 100644 --- a/library/Zend/Code/Scanner/ScannerDirectory.php +++ b/library/Zend/Code/Scanner/ScannerDirectory.php @@ -4,8 +4,10 @@ class ScannerDirectory implements ScannerInterface { - protected $fileScannerClass = 'Zend\Code\Scanner\ScannerFile'; - protected $fileScanners = array(); + protected $isScanned = false; + protected $directories = array(); + protected $fileScannerFileClass = 'Zend\Code\Scanner\ScannerFile'; + protected $scannerFiles = array(); public function __construct($directory = null) { @@ -20,10 +22,12 @@ public function __construct($directory = null) } } + /* public function setFileScannerClass($fileScannerClass) { $this->fileScannerClass = $fileScannerClass; } + */ public function addDirectory($directory) { @@ -34,11 +38,104 @@ public function addDirectory($directory) $this->directories[] = $realDir; } - public function scan() + protected function scan() { + if ($this->isScanned) { + return; + } + + + // iterate directories creating file scanners + foreach ($this->directories as $directory) { + foreach (new \RecursiveDirectoryIterator($directory) as $item) { + if ($item->isFile() && preg_match('#\.php$#', $item->getRealPath())) { + $this->scannerFiles[] = new ScannerFile($item->getRealpath()); + } + } + } } + public function getNamespaces() + {} + + public function getClasses($returnScannerClass = false) + { + $this->scan(); + + $classes = array(); + foreach ($this->scannerFiles as $scannerFile) { + /* @var $scannerFile Zend\Code\Scanner\ScannerFile */ + $classes = array_merge($classes, $scannerFile->getClasses($returnScannerClass)); + } + + return $classes; + } + + + /** + * + * Enter description here ... + * @param string|int $classNameOrInfoIndex + * @param string $returnScannerClass + * @return Zend\Code\Scanner\ScannerClass + */ + /* + public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerClass') + { + $this->scan(); + + // process the class requested + static $baseScannerClass = 'Zend\Code\Scanner\ScannerClass'; + if ($returnScannerClass !== $baseScannerClass) { + if (!is_string($returnScannerClass)) { + $returnScannerClass = $baseScannerClass; + } + $returnScannerClass = ltrim($returnScannerClass, '\\'); + if ($returnScannerClass !== $baseScannerClass && !is_subclass_of($returnScannerClass, $baseScannerClass)) { + throw new \RuntimeException('Class must be or extend ' . $baseScannerClass); + } + } + + if (is_int($classNameOrInfoIndex)) { + $info = $this->infos[$classNameOrInfoIndex]; + if ($info['type'] != 'class') { + throw new \InvalidArgumentException('Index of info offset is not about a class'); + } + } elseif (is_string($classNameOrInfoIndex)) { + $classFound = false; + foreach ($this->infos as $infoIndex => $info) { + if ($info['type'] === 'class' && $info['name'] === $classNameOrInfoIndex) { + $classFound = true; + break; + } + } + if (!$classFound) { + return false; + } + } + + $uses = array(); + for ($u = 0; $u < count($this->infos); $u++) { + if ($this->infos[$u]['type'] == 'use') { + foreach ($this->infos[$u]['statements'] as $useStatement) { + $useKey = ($useStatement['as']) ?: $useStatement['asComputed']; + $uses[$useKey] = $useStatement['use']; + } + } + } + + return new $returnScannerClass( + array_slice($this->tokens, $info['tokenStart'], ($info['tokenEnd'] - $info['tokenStart'] + 1)), // zero indexed array + $info['namespace'], + $uses + ); + } + */ + + + public static function export() {} + public function __toString() {} } diff --git a/library/Zend/Code/Scanner/ScannerFile.php b/library/Zend/Code/Scanner/ScannerFile.php index 8a8bc29037f..24598a195aa 100644 --- a/library/Zend/Code/Scanner/ScannerFile.php +++ b/library/Zend/Code/Scanner/ScannerFile.php @@ -6,6 +6,8 @@ class ScannerFile extends ScannerTokenArray implements ScannerInterface { protected $isScanned = false; + protected $file = null; + public function __construct($file = null, $options = null) { if ($file) { @@ -22,6 +24,11 @@ public function setFile($file) $this->reset(); } + public function getFile() + { + return $this->file; + } + protected function scan() { if (!$this->file) { diff --git a/library/Zend/Code/Scanner/ScannerMethod.php b/library/Zend/Code/Scanner/ScannerMethod.php index c0c38082a44..d211ce7069c 100644 --- a/library/Zend/Code/Scanner/ScannerMethod.php +++ b/library/Zend/Code/Scanner/ScannerMethod.php @@ -75,7 +75,7 @@ protected function scan() */ //$this->scanBody($tokenIndex); - + $this->isScanned = true; } @@ -138,49 +138,55 @@ protected function scanParameters($tokenIndex, &$fastForward) // BREAK ON if ($parenCount == 1 && is_string($token) && $token == ')') { - break; - } - - // ANALYZE - if (is_string($token)) { - if ($token == '(') { - $parenCount++; - } - if ($token == ')') { - $parenCount--; - } - } - - if ($parenCount == 1 && isset($info)) { - $nextToken = (isset($info['name']) && is_string($this->tokens[$tokenIndex+1])) ? $this->tokens[$tokenIndex+1] : null; - if ((is_string($token) && $token == ',') || (isset($nextToken) && $nextToken == ')')) { + if ($info) { $info['tokenEnd'] = $tokenIndex - 1; $this->infos[] = $info; - unset($info); } - unset($nextToken); - } - - if (is_array($token) && isset($info)) { - $info['lineEnd'] = $token[2]; - } - - if ($parenCount > 1 || is_string($token)) { - continue; + break; } + // ANALYZE + // gather line information if we can if (!isset($info)) { $info = array( 'type' => 'parameter', 'tokenStart' => $tokenIndex, 'tokenEnd' => null, - 'lineStart' => $this->tokens[$tokenIndex][2], - 'lineEnd' => null, + 'lineStart' => $token[2], + 'lineEnd' => $token[2], 'name' => null, 'position' => ++$position ); } + + if (is_array($token) && isset($info)) { + $info['lineEnd'] = $token[2]; + } + + if (is_array($token) && $token[0] === T_WHITESPACE) { + continue; + } + + if (is_string($token)) { + if ($token == '(') { + $parenCount++; + } + if ($token == ')') { + $parenCount--; + } + + if ($parenCount !== 1) { + continue; + } + + } + + if (isset($info) && is_string($token) && $token == ',') { + $info['tokenEnd'] = $tokenIndex - 1; + $this->infos[] = $info; + unset($info); + } if (is_array($token) && $token[0] === T_VARIABLE) { $info['name'] = ltrim($token[1], '$'); @@ -237,7 +243,7 @@ public function getParameters($returnScanner = false) $this->scan(); $return = array(); - + foreach ($this->infos as $info) { if ($info['type'] != 'parameter') { continue; diff --git a/library/Zend/Code/Scanner/ScannerParameter.php b/library/Zend/Code/Scanner/ScannerParameter.php index 7b1508e07a3..ec53dc12f39 100644 --- a/library/Zend/Code/Scanner/ScannerParameter.php +++ b/library/Zend/Code/Scanner/ScannerParameter.php @@ -72,6 +72,24 @@ protected function scan() } } + if ($this->class) { + $namespace = (($decClassLastSlash = strrpos($this->declaringClass, '\\')) !== false) + ? substr($this->declaringClass, 0, $decClassLastSlash) : null; + if ((!$this->uses && !$namespace) || strlen($this->class) <= 0 || $this->class{0} == '\\') { + $this->class = ltrim($this->class, '\\'); + } else { + if ($namespace || $this->uses) { + $firstPartEnd = (strpos($this->class, '\\')) ?: strlen($this->class-1); + $firstPart = substr($this->class, 0, $firstPartEnd); + if (array_key_exists($firstPart, $this->uses)) { + $this->class = substr_replace($this->class, $this->uses[$firstPart], 0, $firstPartEnd); + } elseif ($namespace) { + $this->class = $namespace . '\\' . $this->class; + } + } + } + } + if ($token[0] == T_WHITESPACE) { $token = $this->tokens[++$tokenIndex]; } @@ -86,6 +104,7 @@ protected function scan() $token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : null; if (!$token) { + $this->isScanned = true; return; } @@ -95,10 +114,12 @@ protected function scan() } if (!$token) { + $this->isScanned = true; return; } if (!(is_string($token) && $token == '=')) { + $this->isScanned = true; return; } @@ -117,6 +138,36 @@ protected function scan() $this->defaultValue .= ((is_array($token)) ? $token[1] : $token); } while (($token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : false)); + if ($this->class) { + // create function to resolve short names with uses + + $uses = $this->uses; + $resolveUseFunc = function (&$value, $key = null) use (&$namespace, &$uses) { + if (!$uses || strlen($value) <= 0 || $value{0} == '\\') { + $value = ltrim($value, '\\'); + return; + } + + if ($namespace || $uses) { + $firstPartEnd = (strpos($value, '\\')) ?: strlen($value-1); + $firstPart = substr($value, 0, $firstPartEnd); + if (array_key_exists($firstPart, $uses)) { + $value = substr_replace($value, $uses[$firstPart], 0, $firstPartEnd); + return; + } + if ($namespace) { + $value = $namespace . '\\' . $value; + return; + } + } + }; + + if ($this->shortInterfaces) { + $this->interfaces = $this->shortInterfaces; + array_walk($this->interfaces, $resolveUseFunc); + } + } + $this->isScanned = true; } /** diff --git a/tests/Zend/Code/Scanner/ScannerMethodTest.php b/tests/Zend/Code/Scanner/ScannerMethodTest.php index 7f1ad687acc..fb1448909fa 100644 --- a/tests/Zend/Code/Scanner/ScannerMethodTest.php +++ b/tests/Zend/Code/Scanner/ScannerMethodTest.php @@ -35,6 +35,7 @@ public function testScannerMethodReturnsParameterScanner() $file = new ScannerFile(__DIR__ . '/../TestAsset/BarClass.php'); $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); $method = $class->getMethod('three'); + $this->assertEquals(array('o', 't', 'bbf'), $method->getParameters()); $parameter = $method->getParameter('t'); $this->assertInstanceOf('Zend\Code\Scanner\ScannerParameter', $parameter); $this->assertEquals('t', $parameter->getName()); diff --git a/tests/Zend/Code/Scanner/ScannerParameterTest.php b/tests/Zend/Code/Scanner/ScannerParameterTest.php index 78182c84e83..c0ace896228 100644 --- a/tests/Zend/Code/Scanner/ScannerParameterTest.php +++ b/tests/Zend/Code/Scanner/ScannerParameterTest.php @@ -22,6 +22,6 @@ public function testScannerParamterHasParameterInformation() $this->assertTrue($parameter->isDefaultValueAvailable()); $this->assertTrue($parameter->isOptional()); $this->assertTrue($parameter->isPassedByReference()); - } + } \ No newline at end of file From f9a939ac027ddc8907c2f3cb1f2a31575f499084 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Wed, 8 Jun 2011 08:54:29 -0500 Subject: [PATCH 34/45] Zend\Di refactor merged into feature branch from outside prototype branch --- library/Zend/Di/Configuration.php | 208 +-------- library/Zend/Di/Definition.php | 343 +------------- .../Di/Definition/AggregateDefinition.php | 96 ++++ .../Zend/Di/Definition/ArrayDefinition.php | 118 +++++ .../Di/Definition/Builder/InjectionMethod.php | 36 ++ .../Zend/Di/Definition/Builder/PhpClass.php | 48 ++ .../Zend/Di/Definition/BuilderDefinition.php | 120 +++++ library/Zend/Di/Definition/Compiler.php | 122 +++++ .../Zend/Di/Definition/RuntimeDefinition.php | 100 ++++ library/Zend/Di/Definitions.php | 106 ----- library/Zend/Di/DependencyDefinition.php | 38 -- library/Zend/Di/DependencyEnabled.php | 8 - library/Zend/Di/DependencyInjection.php | 18 +- .../Zend/Di/DependencyInjectionContainer.php | 61 --- library/Zend/Di/DependencyInjector.php | 438 ++++++++---------- library/Zend/Di/DependencyReference.php | 8 - library/Zend/Di/Generator/Configuration.php | 141 ------ library/Zend/Di/Generator/DefinitionProxy.php | 143 ------ library/Zend/Di/Generator/Generator.php | 161 ------- library/Zend/Di/Generator/Introspector.php | 11 - .../Introspector/ConstructorInjection.php | 75 --- .../Introspector/InterfaceInjection.php | 82 ---- .../Introspector/SetterInjection.php | 69 --- .../Zend/Di/Generator/ManagedDefinitions.php | 106 ----- library/Zend/Di/Generator/TypeManager.php | 107 ----- library/Zend/Di/Generator/TypeRegistry.php | 119 ----- library/Zend/Di/InjectibleMethod.php | 14 - library/Zend/Di/InjectibleMethods.php | 19 - library/Zend/Di/InstanceCollection.php | 17 + library/Zend/Di/InstanceManager.php | 142 ++++++ library/Zend/Di/Method.php | 217 --------- library/Zend/Di/Methods.php | 75 --- library/Zend/Di/Reference.php | 41 -- .../Generator.php} | 6 +- .../{ => ServiceLocator}/ServiceLocation.php | 2 +- .../{ => ServiceLocator}/ServiceLocator.php | 3 +- 36 files changed, 1018 insertions(+), 2400 deletions(-) create mode 100644 library/Zend/Di/Definition/AggregateDefinition.php create mode 100644 library/Zend/Di/Definition/ArrayDefinition.php create mode 100644 library/Zend/Di/Definition/Builder/InjectionMethod.php create mode 100644 library/Zend/Di/Definition/Builder/PhpClass.php create mode 100644 library/Zend/Di/Definition/BuilderDefinition.php create mode 100644 library/Zend/Di/Definition/Compiler.php create mode 100644 library/Zend/Di/Definition/RuntimeDefinition.php delete mode 100644 library/Zend/Di/Definitions.php delete mode 100644 library/Zend/Di/DependencyDefinition.php delete mode 100644 library/Zend/Di/DependencyEnabled.php delete mode 100644 library/Zend/Di/DependencyInjectionContainer.php delete mode 100644 library/Zend/Di/DependencyReference.php delete mode 100644 library/Zend/Di/Generator/Configuration.php delete mode 100644 library/Zend/Di/Generator/DefinitionProxy.php delete mode 100644 library/Zend/Di/Generator/Generator.php delete mode 100644 library/Zend/Di/Generator/Introspector.php delete mode 100644 library/Zend/Di/Generator/Introspector/ConstructorInjection.php delete mode 100644 library/Zend/Di/Generator/Introspector/InterfaceInjection.php delete mode 100644 library/Zend/Di/Generator/Introspector/SetterInjection.php delete mode 100644 library/Zend/Di/Generator/ManagedDefinitions.php delete mode 100644 library/Zend/Di/Generator/TypeManager.php delete mode 100644 library/Zend/Di/Generator/TypeRegistry.php delete mode 100644 library/Zend/Di/InjectibleMethod.php delete mode 100644 library/Zend/Di/InjectibleMethods.php create mode 100644 library/Zend/Di/InstanceCollection.php create mode 100644 library/Zend/Di/InstanceManager.php delete mode 100644 library/Zend/Di/Method.php delete mode 100644 library/Zend/Di/Methods.php delete mode 100644 library/Zend/Di/Reference.php rename library/Zend/Di/{ContainerBuilder.php => ServiceLocator/Generator.php} (98%) rename library/Zend/Di/{ => ServiceLocator}/ServiceLocation.php (79%) rename library/Zend/Di/{ => ServiceLocator}/ServiceLocator.php (98%) diff --git a/library/Zend/Di/Configuration.php b/library/Zend/Di/Configuration.php index f83276a3329..93f81f716c8 100644 --- a/library/Zend/Di/Configuration.php +++ b/library/Zend/Di/Configuration.php @@ -1,209 +1,9 @@ injector = $injector; - } - - /** - * Update injector based on array configuration - * - * @param array $config - * @return void - */ - public function fromArray(array $config) - { - if (isset($config['definitions']) && is_array($config['definitions'])) { - $this->buildDefinitions($config['definitions']); - } - if (isset($config['aliases']) && is_array($config['aliases'])) { - $this->buildAliases($config['aliases']); - } - } - - /** - * Load definitions from a configuration object - * - * Accepts an array, an object with a toArray() method, or a Traversable - * object. - * - * @param array|object $config - * @return void - */ - public function fromConfig($config) - { - if (!is_object($config)) { - if (is_array($config)) { - return $this->fromArray($config); - } - throw new Exception\InvalidArgumentException(sprintf( - 'Configuration must be provided as either an array or Traversable object; "%s" was provided', - gettype($config) - )); - } - if (method_exists($config, 'toArray')) { - return $this->fromArray($config->toArray()); - } - if (!$config instanceof \Traversable) { - throw new Exception\InvalidArgumentException(sprintf( - 'Configuration must be provided as either an array or Traversable object; "%s" was provided', - get_class($config) - )); - } - $array = array(); - foreach ($config as $key => $value) { - $array[$key] = $value; - } - return $this->fromArray($array); - } - - /** - * Create definitions and inject into dependency injector - * - * @param array $definitions - * @return void - */ - protected function buildDefinitions(array $definitions) - { - foreach ($definitions as $definition) { - $this->buildDefinition($definition); - } - } - - /** - * Build a definition to add to the injector - * - * @param array $values - * @return void - */ - protected function buildDefinition(array $values) - { - if (!isset($values['class']) - || !is_string($values['class']) - || empty($values['class']) - ) { - throw new Exception\InvalidArgumentException(sprintf( - 'Cannot create definition; provided definition contains no class key (%s)', - var_export($values, 1) - )); - } - - $definition = new Definition($values['class']); - - foreach ($values as $key => $value) { - switch (strtolower($key)) { - case 'class': - break; - case 'constructor_callback': - $callback = $value; - if (is_array($value) - && (isset($value['class']) && isset($value['method'])) - ) { - $callback = array($value['class'], $value['method']); - } - $definition->setConstructorCallback($callback); - break; - case 'params': - if (!is_array($value)) { - break; - } - $params = $this->resolveReferences($value); - $definition->setParams($params); - break; - case 'param_map': - $definition->setParamMap($value); - break; - case 'shared': - $definition->setShared((bool) $value); - break; - case 'methods': - $this->buildMethods($definition, $value); - break; - default: - // ignore all other options - break; - } - } - - $this->injector->setDefinition($definition); - } - - /** - * Build injectible methods for a definition - * - * @param DependencyDefinition $definition - * @param array $methods - * @return void - */ - protected function buildMethods(DependencyDefinition $definition, array $methods) - { - foreach ($methods as $methodDefinition) { - if (!is_array($methodDefinition)) { - continue; - } - if (!isset($methodDefinition['name'])) { - continue; - } - $method = $methodDefinition['name']; - $params = array(); - if (isset($methodDefinition['params']) && is_array($methodDefinition['params'])) { - $params = $this->resolveReferences($methodDefinition['params']); - } - $definition->addMethodCall($method, $params); - } - } - - /** - * Resolve parameters that are references - * - * If a parameter value is an array containing a key "__reference", replace - * it with a Reference object that is seeded with the value of that key. - * - * @param array $params - * @return array - */ - protected function resolveReferences(array $params) - { - foreach ($params as $key => $value) { - if (!is_array($value)) { - continue; - } - if (!isset($value['__reference'])) { - continue; - } - $params[$key] = new Reference($value['__reference']); - } - return $params; - } - - /** - * Build aliases from provided configuration - * - * $aliases should be array of $alias => $target pairs. - * - * @param array $aliases - * @return void - */ - protected function buildAliases(array $aliases) - { - foreach ($aliases as $alias => $target) { - $this->injector->setAlias($alias, $target); - } - } -} + protected $definition = null; + protected $instanceManager = null; +} \ No newline at end of file diff --git a/library/Zend/Di/Definition.php b/library/Zend/Di/Definition.php index 1ac39b93076..51a09c2eb7a 100644 --- a/library/Zend/Di/Definition.php +++ b/library/Zend/Di/Definition.php @@ -1,337 +1,16 @@ setClass($className); - $this->injectibleMethods = new Methods(); - } - - /** - * Get the defined class name - * - * @return string - */ - public function getClass() - { - return $this->className; - } - - /** - * Set the class name the definition describes - * - * @param string $name - * @return Definition - */ - public function setClass($name) - { - $this->className = $name; - return $this; - } - - /** - * Provide a callback to use in order to get an instance - * - * @param callback $callback - * @return Definition - */ - public function setConstructorCallback($callback) - { - $this->constructorCallback = $callback; - return $this; - } - - /** - * Retrieve the constructor callback, if any - * - * @return false|callback - */ - public function getConstructorCallback() - { - return $this->constructorCallback; - } - - /** - * Do we define a constructor callback? - * - * @return bool - */ - public function hasConstructorCallback() - { - if (false === $this->constructorCallback) { - return false; - } - - return true; - } - - /** - * Set a constructor parameter - * - * @param string $name - * @param mixed $value - * @return Definition - */ - public function setParam($name, $value) - { - $this->constructorParams[$name] = $value; - $this->orderedConstructorParams = null; - return $this; - } - - /** - * Set all parameters at once - * - * @param array $params - * @return Definition - */ - public function setParams(array $params) - { - $this->constructorParams = $params; - $this->orderedConstructorParams = null; - return $this; - } - - /** - * Define the constructor parameter map - * - * Maps parameter names to position in order to specify argument order. - * - * @param array $map Map of name => position pairs for constructor arguments - * @return Definition - */ - public function setParamMap(array $map) - { - foreach ($map as $name => $position) { - if (!is_int($position) && !is_numeric($position)) { - throw new Exception\InvalidPositionException(); - } - if (!is_string($name) || empty($name)) { - throw new Exception\InvalidParamNameException(); - } - } - $positions = array_values($map); - sort($positions); - if (!empty($positions) && ($positions != range(0, count($positions) - 1))) { - throw new Exception\InvalidPositionException('Positions are non-sequential'); - } - $this->constructorParamMap = $map; - $this->orderedConstructorParams = null; - return $this; - } - - /** - * Retrieve constructor parameters - * - * Returns the constructor parameters in the order in which they should be - * passed to the constructor, as an indexed array. - * - * @return array - */ - public function getParams() - { - if (null !== $this->orderedConstructorParams) { - return $this->orderedConstructorParams; - } - - if (null === $this->constructorParamMap) { - $this->buildConstructorParamMapFromReflection(); - } - $map = $this->constructorParamMap; - - // Sort map, and flip such that positions become keys - asort($map); - $map = array_flip($map); - - $params = array(); - foreach ($map as $key) { - $value = isset($this->constructorParams[$key]) ? $this->constructorParams[$key] : null; - $params[] = $value; - } - - $this->orderedConstructorParams = $params; - return $params; - } - - /** - * Should the container return the same or different instances? - * - * @param bool $flag - * @return Definition - */ - public function setShared($flag = true) - { - $this->shareInstances = (bool) $flag; - return $this; - } - - /** - * Should the container return the same or different instances? - * - * @return bool - */ - public function isShared() - { - return $this->shareInstances; - } - - /** - * Add a method to be called and injected - * - * @param string $name - * @param array|null $params - * @param array|null $paramMap - * @return Definition - */ - public function addMethodCall($name, array $params = null, array $paramMap = null) - { - if (!is_string($name) && !($name instanceof InjectibleMethod)) { - throw new Exception\InvalidArgumentException(sprintf( - 'addMethodCall() expects a string method name or InjectibleMethod object as an argument; received "%s"', - (is_object($name) ? get_class($name) : gettype($name)) - )); - } - - if ($name instanceof InjectibleMethod) { - $method = $name; - if (is_array($params)) { - $method->setParams($params); - } - if (is_array($paramMap)) { - $method->setParamMap($paramMap); - } - } else { - $method = new Method($name, $params, $paramMap); - } - - $method->setClass($this->getClass()); - $this->injectibleMethods->insert($method); - return $this; - } - - /** - * Get collection of injectible methods - * - * @return InjectibleMethods - */ - public function getMethodCalls() - { - return $this->injectibleMethods; - } - - public function toArray() - { - $params = array(); - foreach ($this->getParams() as $name => $value) { - if ($value instanceof Reference) { - $value = array('__reference' => $value->getServiceName()); - } - $params[$name] = $value; - } - - $methods = array(); - foreach ($this->getMethodCalls() as $method) { - $methodParams = array(); - foreach ($method->getParams() as $key => $methodParam) { - if ($methodParam instanceof Reference) { - $methodParams[$key] = array('__reference' => $methodParam->getServiceName()); - } else { - $methodParams[$key] = $methodParam; - } - } - $methods[] = array( - 'name' => $method->getName(), - 'params' => $methodParams, - ); - } - - return array( - 'class' => $this->getClass(), - 'methods' => $methods, - 'param_map' => ($this->constructorParamMap) ?: array(), - 'params' => $params, - ); - } - - /** - * Build the constructor parameter map from Reflection - * - * @return void - */ - protected function buildConstructorParamMapFromReflection() - { - $class = new ReflectionClass($this->getClass()); - if (!$class->hasMethod('__construct')) { - $this->setParamMap(array()); - return; - } - $constructor = $class->getMethod('__construct'); - $parameters = $constructor->getParameters(); - $params = array(); - foreach ($parameters as $param) { - $params[$param->getName()] = $param->getPosition(); - } - $this->setParamMap($params); - } + public function getClasses(); + public function hasClass($class); + public function getClassSupertypes($class); + public function getInstantiator($class); + public function hasInjectionMethods($class); + public function getInjectionMethods($class); + public function hasInjectionMethod($class, $method); + public function getInjectionMethodParameters($class, $method); } + diff --git a/library/Zend/Di/Definition/AggregateDefinition.php b/library/Zend/Di/Definition/AggregateDefinition.php new file mode 100644 index 00000000000..4a5a18de219 --- /dev/null +++ b/library/Zend/Di/Definition/AggregateDefinition.php @@ -0,0 +1,96 @@ +definitions[] = $definition; + } + + public function getClasses() + { + $classes = array(); + foreach ($this->definitions as $definition) { + $classes = array_merge($classes, $definition->getClasses()); + } + return $classes; + } + + public function hasClass($class) + { + foreach ($this->definitions as $definition) { + if ($definition->hasClass($class)) { + return true; + } + } + return false; + } + + public function getClassSupertypes($class) + { + $superTypes = array(); + foreach ($this->definitions as $definition) { + $superTypes = array_merge($superTypes, $definition->getSuperTypes()); + } + return $superTypes; + } + + public function getInstantiator($class) + { + foreach ($this->definitions as $definition) { + if ($definition->hasClass($class)) { + return $definition->getInstantiator($class); + } + } + return false; + } + + public function hasInjectionMethods($class) + { + foreach ($this->definitions as $definition) { + if ($definition->hasClass($class)) { + return $definition->hasInjectionMethods($class); + } + } + return false; + } + + public function hasInjectionMethod($class, $method) + { + foreach ($this->definitions as $definition) { + if ($definition->hasClass($class)) { + return $definition->hasInjectionMethod($class); + } + } + return false; + } + + public function getInjectionMethods($class) + { + foreach ($this->definitions as $definition) { + if ($definition->hasClass($class)) { + return $definition->getInjectionMethods($class); + } + } + return false; + } + + public function getInjectionMethodParameters($class, $method) + { + foreach ($this->definitions as $definition) { + if ($definition->hasClass($class)) { + return $definition->getInjectionMethodParameters($class); + } + } + return false; + } + +} \ No newline at end of file diff --git a/library/Zend/Di/Definition/ArrayDefinition.php b/library/Zend/Di/Definition/ArrayDefinition.php new file mode 100644 index 00000000000..04353fa7af5 --- /dev/null +++ b/library/Zend/Di/Definition/ArrayDefinition.php @@ -0,0 +1,118 @@ +dataArray = $dataArray; + } + + public function getClasses() + { + return array_keys($this->dataArray); + } + + public function hasClass($class) + { + return array_key_exists($class, $this->dataArray); + } + + public function getClassSupertypes($class) + { + if (!isset($this->dataArray[$class])) { + return array(); + } + + if (!isset($this->dataArray[$class]['superTypes'])) { + return array(); + } + + return $this->dataArray[$class]['superTypes']; + } + + public function getInstantiator($class) + { + if (!isset($this->dataArray[$class])) { + return array(); + } + + if (!isset($this->dataArray[$class]['instantiator'])) { + return array(); + } + + return $this->dataArray[$class]['instantiator']; + } + + public function hasInjectionMethods($class) + { + if (!isset($this->dataArray[$class])) { + return array(); + } + + if (!isset($this->dataArray[$class]['injectionMethods'])) { + return array(); + } + + return (count($this->dataArray[$class]['injectionMethods']) > 0); + } + + public function hasInjectionMethod($class, $method) + { + if (!isset($this->dataArray[$class])) { + return array(); + } + + if (!isset($this->dataArray[$class]['injectionMethods'])) { + return array(); + } + + if (!isset($this->dataArray[$class]['injectionMethods'][$method])) { + return array(); + } + + return array_key_exists($method, $this->dataArray[$class]['injectionMethods']); + } + + public function getInjectionMethods($class) + { + if (!isset($this->dataArray[$class])) { + return array(); + } + + if (!isset($this->dataArray[$class]['injectionMethods'])) { + return array(); + } + + return array_keys($this->dataArray[$class]['injectionMethods']); + } + + public function getInjectionMethodParameters($class, $method) + { + if (!isset($this->dataArray[$class])) { + return array(); + } + + if (!isset($this->dataArray[$class]['injectionMethods'])) { + return array(); + } + + if (!isset($this->dataArray[$class]['injectionMethods'][$method])) { + return array(); + } + + return $this->dataArray[$class]['injectionMethods'][$method]; + } + + public function toArray() + { + return $this->dataArray; + } + +} diff --git a/library/Zend/Di/Definition/Builder/InjectionMethod.php b/library/Zend/Di/Definition/Builder/InjectionMethod.php new file mode 100644 index 00000000000..9f1ac9d9b5a --- /dev/null +++ b/library/Zend/Di/Definition/Builder/InjectionMethod.php @@ -0,0 +1,36 @@ +name = $name; + } + + public function getName($name) + { + return $this->name; + } + + public function addParameter($name, $class = null, $position = self::PARAMETER_POSTION_NEXT) + { + if ($position == self::PARAMETER_POSTION_NEXT) { + $this->parameters[$name] = $class; + } else { + throw new \Exception('Implementation for parameter placement is incomplete'); + } + } + + public function getParameters() + { + return $this->parameters; + } + +} diff --git a/library/Zend/Di/Definition/Builder/PhpClass.php b/library/Zend/Di/Definition/Builder/PhpClass.php new file mode 100644 index 00000000000..1f1cf351704 --- /dev/null +++ b/library/Zend/Di/Definition/Builder/PhpClass.php @@ -0,0 +1,48 @@ +name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setInstantiator($instantiator) + { + $this->instantiator = $instantiator; + return $this; + } + + public function addSuperType($superType) + { + $this->superTypes[] = $superType; + } + + public function getSuperTypes() + { + return $this->superTypes; + } + + public function addInjectionMethod(InjectionMethod $injectionMethod) + { + $this->injectionMethods[] = $injectionMethod; + } + + public function getInjectionMethods() + { + $this->injectionMethods; + } + +} diff --git a/library/Zend/Di/Definition/BuilderDefinition.php b/library/Zend/Di/Definition/BuilderDefinition.php new file mode 100644 index 00000000000..149c03a3ad5 --- /dev/null +++ b/library/Zend/Di/Definition/BuilderDefinition.php @@ -0,0 +1,120 @@ +classes[] = $phpClass; + } + + public function getClasses() + { + $classNames = array(); + foreach ($this->classes as $class) { + $classNames[] = $class->getName(); + } + return $classNames; + } + + public function hasClass($class) + { + foreach ($this->classes as $classObj) { + if ($classObj->getName() === $class) { + return true; + } + } + return false; + } + + protected function getClass($name) + { + foreach ($this->classes as $classObj) { + if ($classObj->getName() === $name) { + return $classObj; + } + } + return false; + } + + public function getClassSupertypes($class) + { + $class = $this->getClass($class); + if ($class === false) { + throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); + } + return $class->getSuperTypes(); + } + + public function getInstantiator($class) + { + $class = $this->getClass($class); + if ($class === false) { + throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); + } + return $class->getInstantiator(); + } + + public function hasInjectionMethods($class) + { + $class = $this->getClass($class); + if ($class === false) { + throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); + } + return $class->getInstantiator(); + } + + public function getInjectionMethods($class) + { + $class = $this->getClass($class); + if ($class === false) { + throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); + } + $methods = $class->getInjectionMethods(); + $methodNames = array(); + foreach ($methods as $methodObj) { + $methodNames[] = $methodObj->getName(); + } + return $methodNames; + } + + public function hasInjectionMethod($class, $method) + { + $class = $this->getClass($class); + if ($class === false) { + throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); + } + $methods = $class->getInjectionMethods(); + foreach ($methods as $methodObj) { + if ($methodObj->getName() === $method) { + return true; + } + } + return false; + } + + public function getInjectionMethodParameters($class, $method) + { + $class = $this->getClass($class); + if ($class === false) { + throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); + } + $methods = $class->getInjectionMethods(); + foreach ($methods as $methodObj) { + if ($methodObj->getName() === $method) { + $method = $methodObj; + } + } + if (!$method instanceof Builder\InjectionMethod) { + throw new Exception\RuntimeException('Cannot find method object for method ' . $method . ' in this builder definition.'); + } + return $method->getParameters(); + } +} \ No newline at end of file diff --git a/library/Zend/Di/Definition/Compiler.php b/library/Zend/Di/Definition/Compiler.php new file mode 100644 index 00000000000..9cc78326c8a --- /dev/null +++ b/library/Zend/Di/Definition/Compiler.php @@ -0,0 +1,122 @@ +codeScanners[] = $scannerDirectory; + } + + public function addCodeScannerFile(\Zend\Code\Scanner\ScannerFile $scannerFile) + { + $this->codeScanners[] = $scannerFile; + } + + public function addCodeReflection($reflectionFileOrClass, $followTypes = true) + { + //$this->codeScanners[] = array($reflectionFileOrClass, $followTypes); + } + + public function compile() + { + $data = array(); + + foreach ($this->codeScanners as $codeScanner) { + + $scannerClasses = $codeScanner->getClasses(true); + + /* @var $class Zend\Code\Scanner\ScannerClass */ + foreach ($scannerClasses as $scannerClass) { + + if ($scannerClass->isAbstract() || $scannerClass->isInterface()) { + continue; + } + + $className = $scannerClass->getName(); + $data[$className] = array(); + + // determine supertypes + $superTypes = array(); + if (($parentClass = $scannerClass->getParentClass()) !== null) { + $superTypes[] = $parentClass; + } + if (($interfaces = $scannerClass->getInterfaces())) { + $superTypes = array_merge($superTypes, $interfaces); + } + + $data[$className]['superTypes'] = $superTypes; + + $data[$className]['instantiator'] = $this->compileScannerInstantiator($scannerClass); + $data[$className]['injectionMethods'] = $this->compileScannerInjectionMethods($scannerClass); + + + } + } + + return new ArrayDefinition($data); + } + + public function compileScannerInstantiator(\Zend\Code\Scanner\ScannerClass $scannerClass) + { + if ($scannerClass->hasMethod('__construct')) { + $construct = $scannerClass->getMethod('__construct'); + if ($construct->isPublic()) { + return '__construct'; + } + } + + return null; + + // @todo scan parent classes for instantiator + //$scannerClass->getParentClass(); + } + + public function compileScannerInjectionMethods(\Zend\Code\Scanner\ScannerClass $scannerClass) + { + $data = array(); + $className = $scannerClass->getName(); + foreach ($scannerClass->getMethods(true) as $scannerMethod) { + $methodName = $scannerMethod->getName(); + + // determine initiator & constructor dependencies + if ($methodName === '__construct' && $scannerMethod->isPublic()) { + $params = $scannerMethod->getParameters(true); + if ($params) { + $data[$methodName] = array(); + foreach ($params as $param) { + $data[$methodName][$param->getName()] = $param->getClass(); + } + } + } + + // scan for setter injection + if (preg_match('#^set[A-Z]#', $methodName)) { + $data[$methodName] = $scannerMethod->getParameters(); + $params = $scannerMethod->getParameters(true); + $data[$methodName] = array(); + foreach ($params as $param) { + $data[$methodName][$param->getName()] = $param->getClass(); + } + } + } + return $data; + } + + + /* + public function hasClass($class); + public function getClassSupertypes($class); + public function getInstantiator($class); + public function hasInjectionMethods($class); + public function hasInjectionMethod($class, $method); + public function getInjectionMethods($class); + public function getInjectionMethodParameters($class, $method); + */ + +} diff --git a/library/Zend/Di/Definition/RuntimeDefinition.php b/library/Zend/Di/Definition/RuntimeDefinition.php new file mode 100644 index 00000000000..27b3065743f --- /dev/null +++ b/library/Zend/Di/Definition/RuntimeDefinition.php @@ -0,0 +1,100 @@ +classes[] = $class; + } + + public function setLookupType($lookupType) + { + $this->lookupType = $lookupType; + } + + public function hasClass($class) + { + return class_exists($class, true); + } + + public function getClassSupertypes($class) + { + return class_parents($class) + class_implements($class); + } + + public function getInstantiator($class) + { + $class = new \ReflectionClass($class); + if ($class->isInstantiable()) { + return '__construct'; + } + return false; + } + + public function hasInjectionMethods($class) + { + + } + + public function hasInjectionMethod($class, $method) + { + $c = new \ReflectionClass($class); + return $c->hasMethod($method); + } + + public function getInjectionMethods($class) + { + $methods = array(); + $c = new \ReflectionClass($class); + if ($c->hasMethod('__construct')) { + $methods[] = '__construct'; + } + foreach ($c->getMethods() as $m) { + if (preg_match('#^set[A-Z]#', $m->getName())) { + $methods[] = $m->getName(); + } + } + return $methods; + } + + public function getInjectionMethodParameters($class, $method) + { + $params = array(); + $rc = new \ReflectionClass($class); + if (($rm = $rc->getMethod($method)) === false) { + throw new \Exception('method not found'); + } + + $rps = $rm->getParameters(); + foreach ($rps as $rp) { + $rpClass = $rp->getClass(); + $params[$rp->getName()] = ($rpClass !== null) ? $rpClass->getName() : null; + } + + return $params; + } + + + +} \ No newline at end of file diff --git a/library/Zend/Di/Definitions.php b/library/Zend/Di/Definitions.php deleted file mode 100644 index 856b2af9abe..00000000000 --- a/library/Zend/Di/Definitions.php +++ /dev/null @@ -1,106 +0,0 @@ - $value) { - switch (strtolower($key)) { - case 'class': - break; - case 'constructor_callback': - $callback = $value; - if (is_array($value) - && (isset($value['class']) && isset($value['method'])) - ) { - $callback = array($value['class'], $value['method']); - } - $definition->setConstructorCallback($callback); - break; - case 'params': - if (!is_array($value)) { - break; - } - $params = $this->resolveReferences($value); - $definition->setParams($params); - break; - case 'param_map': - $definition->setParamMap($value); - break; - case 'tags': - $definition->addTags($value); - break; - case 'shared': - $definition->setShared((bool) $value); - break; - case 'methods': - $this->buildMethods($definition, $value); - break; - default: - // ignore all other options - break; - } - } - - $this->addDefinition($definition); - } - - public function addDefinition(\Zend\Di\DependencyDefinition $definition) - { - $class = $definition->getClass(); - if ($this->hasDefinition($class)) { - throw new \InvalidArgumentException('A definition for this class already exist.'); - } - $this->definitions[$class] = $definition; - } - - public function hasDefinition($class) - { - return array_key_exists($class, $this->definitions); - } - - public function getDefinition($class) - { - return $this->definitions[$class]; - } - - public function mergeObjectConfiguration($objectConfiguration) - { - foreach ($objectConfiguration as $class => $configValues) { - if (!array_key_exists($class, $this->definitions)) { - continue; - } - $def = $this->definitions[$class]; - $def->setParams($configValues); - } - } - - public function toArray() - { - $defs = array(); - /* @var $definition \Zend\Di\Definition */ - foreach ($this->definitions as $definition) { - $defArray = $definition->toArray(); - $defs[] = $defArray; - } - return $defs; - } - -} diff --git a/library/Zend/Di/DependencyDefinition.php b/library/Zend/Di/DependencyDefinition.php deleted file mode 100644 index 7b13b1228a2..00000000000 --- a/library/Zend/Di/DependencyDefinition.php +++ /dev/null @@ -1,38 +0,0 @@ - position pairs for constructor arguments - */ - public function setParamMap(array $map); - public function getParams(); - - public function setShared($flag = true); - public function isShared(); - - public function addMethodCall($name, array $params = null, array $paramMap = null); - /** - * @return InjectibleMethods - */ - public function getMethodCalls(); - - /** - * Serialization - * - * @return array - */ - public function toArray(); -} diff --git a/library/Zend/Di/DependencyEnabled.php b/library/Zend/Di/DependencyEnabled.php deleted file mode 100644 index a13bb0ec561..00000000000 --- a/library/Zend/Di/DependencyEnabled.php +++ /dev/null @@ -1,8 +0,0 @@ -injector = $di; - return $this; - } - - /** - * Get DI manager for this service locator - * - * If none has been injected, injects an instance of DependencyInjector. - * - * @return DependencyInjection - */ - public function getInjector() - { - if (null === $this->injector) { - $this->setInjector(new DependencyInjector()); - } - return $this->injector; - } - - /** - * Retrieve a registered service - * - * Attempts to retrieve a registered service. If none matching is found, it - * then looks in the dependency injector to see if it can find it; if so, - * it returns it. - * - * @param string $name - * @param array $params - * @return mixed - */ - public function get($name, array $params = array()) - { - $service = parent::get($name, $params); - if (null !== $service) { - return $service; - } - - return $this->getInjector()->get($name, $params); - } -} diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php index e8d8ae08cf7..cd48a536c77 100755 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -1,37 +1,75 @@ config) { + $this->setConfiguration($config); + } + } + + public function setConfiguration(Configuration $config) + { + // @todo process this + } + + public function setDefinition(Definition\DefinitionInterface $definition) + { + $this->definition = $definition; + return $this; + } + + + public function getDefinition() + { + if ($this->definition == null) { + $this->definition = new Definition\RuntimeDefinition(); + } + return $this->definition; + } + + /** + * + * @return Zend\Di\InstanceManager + */ + public function getInstanceManager() + { + if ($this->instanceManager == null) { + $this->instanceManager = new InstanceManager(); + } + return $this->instanceManager; + } + /** * Lazy-load a class * @@ -45,14 +83,22 @@ class DependencyInjector implements DependencyInjection */ public function get($name, array $params = array()) { + /* + if ($params) { + throw new \Exception('Implementation not complete: get needs to hash params'); + } + */ + + $im = $this->getInstanceManager(); + // Cached instance - if (isset($this->instances[$name])) { - return $this->instances[$name]; + if ($im->hasSharedInstance($name, $params)) { + return $im->getSharedInstance($name, $params); } return $this->newInstance($name, $params); } - + /** * Retrieve a new instance of a class * @@ -63,197 +109,47 @@ public function get($name, array $params = array()) * @param array $params Parameters to pass to the constructor * @return object|null */ - public function newInstance($name, array $params = array()) + public function newInstance($name, array $params = array(), $isShared = true) { - // Class name provided - if (isset($this->definitions[$name])) { - $instance = $this->getInstanceFromDefinition($this->definitions[$name], $params); - if ($this->definitions[$name]->isShared()) { - $this->instances[$name] = $instance; - } - return $instance; + $this->getDefinition(); + + // check if name is alias + //$class = (array_key_exists($name, $this->aliases)) ? $this->aliases[$name] : $name; + $class = $name; + + if (!$this->definition->hasClass($class)) { + throw new Exception\InvalidArgumentException('Invalid class name or alias provided.'); } - - // Alias resolved to definition - if (false !== $definition = $this->getDefinitionFromAlias($name)) { - $class = $definition->getClass(); - if (isset($this->instances[$class])) { - return $this->instances[$class]; - } - $instance = $this->getInstanceFromDefinition($definition, $params); - if ($definition->isShared()) { - $this->instances[$name] = $instance; - $this->instances[$class] = $instance; - } - return $instance; - } - - // Test if class exists, and return instance if possible - if (!class_exists($name)) { - return null; - } - $instance = $this->getInstanceFromClassName($name, $params); - return $instance; - } - - /** - * Set many definitions at once - * - * String keys will be used as the $serviceName argument to - * {@link setDefinition()}. - * - * @param array|Traversable $definitions Iterable Definition objects - * @return DependencyInjector - */ - public function setDefinitions($definitions) - { - foreach ($definitions as $name => $definition) { - if (!is_string($name) || is_numeric($name) || empty($name)) { - $name = null; - } - $this->setDefinition($definition, $name); - } - return $this; - } - /** - * Check for Circular Dependencies - * - * @param string $class - * @param array|string $dependency - * @return boolean - */ - protected function checkCircularDependency($class, $dependency) - { - if (is_array($dependency)) { - foreach ($dependency as $dep) { - if (isset($this->dependencies[$dep][$class]) && $this->dependencies[$dep][$class]) { - throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dep and viceversa"); - } + + $instantiator = $this->definition->getInstantiator($class); + $injectionMethods = $this->definition->getInjectionMethods($class); + + if ($instantiator === '__construct') { + $object = $this->createInstanceViaConstructor($class, $params); + if (in_array('__construct', $injectionMethods)) { + unset($injectionMethods[array_search('__construct', $injectionMethods)]); } + } elseif (is_callable($instantiator)) { + $object = $this->createInstanceViaCallback($instantiator, $params); + // @todo make sure we can create via a real object factory + throw new \Exception('incomplete implementation'); } else { - if (isset($this->dependencies[$dependency][$class]) && $this->dependencies[$dependency][$class]) { - throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dependency and viceversa"); - } + throw new Exception\RuntimeException('Invalid instantiator'); } - return true; - } - /** - * Check the circular dependencies path between two definitions - * - * @param type $class - * @param type $dependency - * @return void - */ - protected function checkPathDependencies($class, $dependency) { - if (!empty($this->references[$class])) { - foreach ($this->references[$class] as $key => $value) { - if ($this->dependencies[$key][$class]) { - $this->dependencies[$key][$dependency]= true; - $this->checkCircularDependency($key, $dependency); - $this->checkPathDependencies($key,$dependency); - } + + if ($injectionMethods) { + foreach ($injectionMethods as $injectionMethod) { + $this->handleInjectionMethodForObject($object, $injectionMethod, $params); } } - } - /** - * Add a definition, optionally with a service name alias - * - * @param DependencyDefinition $definition - * @param string $serviceName - * @return DependencyInjector - */ - public function setDefinition(DependencyDefinition $definition, $serviceName = null) - { - $className = $definition->getClass(); - $this->definitions[$className] = $definition; - if (null !== $serviceName && !empty($serviceName)) { - $this->aliases[$serviceName] = $className; - } - foreach ($definition->getParams() as $param) { - if ($param instanceof Reference) { - $serviceName= $param->getServiceName(); - $this->dependencies[$className][$serviceName]= true; - $this->references[$serviceName][$className]= true; - $this->checkCircularDependency($className, $serviceName); - $this->checkPathDependencies($className, $serviceName); - } + + if ($isShared) { + $this->getInstanceManager()->addSharedInstance($object, $class, $params); } - return $this; - } - - /** - * Alias a given service/class name so that it may be referenced by another name - * - * @param string $alias - * @param string $serviceName Class name or service/alias name - * @return DependencyInjector - */ - public function setAlias($alias, $serviceName) - { - $this->aliases[$alias] = $serviceName; - return $this; - } - - /** - * Retrieve aggregated definitions - * - * @return array - */ - public function getDefinitions() - { - return $this->definitions; - } - - /** - * Retrieve defined aliases - * - * @return array - */ - public function getAliases() - { - return $this->aliases; - } - - /** - * Get an object instance based on a Definition object - * - * @param DependencyDefinition $definition - * @param array $params - * @return object - */ - protected function getInstanceFromDefinition(DependencyDefinition $definition, array $params) - { - $class = $definition->getClass(); - $params = array_merge($definition->getParams(), $params); - - if ($definition->hasConstructorCallback()) { - $object = $this->getInstanceFromCallback($definition->getConstructorCallback(), $params); - } else { - $object = $this->getInstanceFromClassName($class, $params); - } - $this->injectMethods($object, $definition); + return $object; } - - /** - * Resolve a Definition class based on the alias provided - * - * @param string $name - * @return false|DependencyDefinition - */ - protected function getDefinitionFromAlias($name) - { - if (!isset($this->aliases[$name])) { - return false; - } - - $service = $this->aliases[$name]; - if (!isset($this->definitions[$service])) { - return $this->getDefinitionFromAlias($service); - } - - return $this->definitions[$service]; - } + /** * Retrieve a class instance based on class name * @@ -265,38 +161,30 @@ protected function getDefinitionFromAlias($name) * @param array $params * @return object */ - protected function getInstanceFromClassName($class, array $params) + protected function createInstanceViaConstructor($class, $params) { + $callParameters = array(); + if ($this->definition->hasInjectionMethod($class, '__construct')) { + $callParameters = $this->resolveMethodParameters($class, '__construct', $params, true); + } + // Hack to avoid Reflection in most common use cases - switch (count($params)) { + switch (count($callParameters)) { case 0: return new $class(); case 1: - $param = array_shift($params); - if (null === $param) { - return new $class(); - } - if ($param instanceof DependencyReference) { - $param = $this->get($param->getServiceName()); - } - return new $class($param); + return new $class($callParameters[0]); case 2: - $param1 = array_shift($params); - if ($param1 instanceof DependencyReference) { - $param1 = $this->get($param1->getServiceName()); - } - $param2 = array_shift($params); - if ($param2 instanceof DependencyReference) { - $param2 = $this->get($param2->getServiceName()); - } - return new $class($param1, $param2); + return new $class($callParameters[0], $callParameters[1]); + case 3: + return new $class($callParameters[0], $callParameters[1], $callParameters[3]); default: - $params = $this->resolveReferences($params); $r = new \ReflectionClass($class); - return $r->newInstanceArgs($params); + return $r->newInstanceArgs($callParameters); } } - + + /** * Get an object instance from the defined callback * @@ -305,53 +193,115 @@ protected function getInstanceFromClassName($class, array $params) * @return object * @throws Exception\InvalidCallbackException */ - protected function getInstanceFromCallback($callback, array $params) + protected function createInstanceViaCallback($callback, $params) { if (!is_callable($callback)) { throw new Exception\InvalidCallbackException('An invalid constructor callback was provided'); } - $params = $this->resolveReferences($params); - return call_user_func_array($callback, $params); - } + + if (is_array($callback)) { + $class = (is_object($callback[0])) ? get_class($callback[0]) : $callback[0]; + $method = $callback[1]; + } + $callParameters = array(); + if ($this->definition->hasInjectionMethod($class, $method)) { + $callParameters = $this->resolveMethodParameters($class, $method, $params, true); + } + return call_user_func_array($callback, $callParameters); + } + + /** + * This parameter will handle any injection methods and resolution of + * dependencies for such methods + * + * @param object $object + * @param string $method + * @param array $params + */ + protected function handleInjectionMethodForObject($object, $method, $params) + { + // @todo make sure to resolve the supertypes for both the object & definition + $callParameters = $this->resolveMethodParameters(get_class($object), $method, $params); + call_user_func_array(array($object, $method), $callParameters); + } + /** * Resolve parameters referencing other services * * @param array $params * @return array */ - protected function resolveReferences(array $params) + protected function resolveMethodParameters($class, $method, array $params, $isInstantiator = false) + { + $resultParams = array(); + + $params = array_merge($params, $this->getInstanceManager()->getProperties($class)); + + $index = 0; + foreach ($this->definition->getInjectionMethodParameters($class, $method) as $name => $value) { + if ($value === null && !array_key_exists($name, $params)) { + throw new Exception\RuntimeException('Missing parameter named ' . $name . ' for ' . $class . '::' . $method); + } + + // circular dep check + if ($isInstantiator && $value !== null) { + $this->dependencies[$class][$value]= true; + //$this->references[$serviceName][$className]= true; + } + + if ($value === null) { + $resultParams[$index] = $params[$name]; + } else { + $resultParams[$index] = $this->get($value, $params); + } + $index++; + } + + return $resultParams; + } + + /** + * Check for Circular Dependencies + * + * @param string $class + * @param array|string $dependency + * @return boolean + */ + protected function checkCircularDependency($class, $dependency) { - foreach ($params as $key => $value) { - if ($value instanceof DependencyReference) { - $params[$key] = $this->get($value->getServiceName()); + if (is_array($dependency)) { + foreach ($dependency as $dep) { + if (isset($this->dependencies[$dep][$class]) && $this->dependencies[$dep][$class]) { + throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dep and viceversa"); + } + } + } else { + if (isset($this->dependencies[$dependency][$class]) && $this->dependencies[$dependency][$class]) { + throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dependency and viceversa"); } } - return $params; + return true; } /** - * Call setter methods in order to inject dependencies + * Check the circular dependencies path between two definitions * - * @param object $object - * @param DependencyDefinition $definition + * @param type $class + * @param type $dependency * @return void */ - protected function injectMethods($object, DependencyDefinition $definition) + protected function checkPathDependencies($class, $dependency) { - foreach ($definition->getMethodCalls() as $name => $info) - { - if (!method_exists($object, $name)) { - continue; - } - - $params = $info->getParams(); - foreach ($params as $key => $param) { - if ($param instanceof DependencyReference) { - $params[$key] = $this->get($param->getServiceName()); + if (!empty($this->references[$class])) { + foreach ($this->references[$class] as $key => $value) { + if ($this->dependencies[$key][$class]) { + $this->dependencies[$key][$dependency] = true; + $this->checkCircularDependency($key, $dependency); + $this->checkPathDependencies($key,$dependency); } } - call_user_func_array(array($object, $name), $params); } } + } diff --git a/library/Zend/Di/DependencyReference.php b/library/Zend/Di/DependencyReference.php deleted file mode 100644 index be0e72d0a5c..00000000000 --- a/library/Zend/Di/DependencyReference.php +++ /dev/null @@ -1,8 +0,0 @@ - $value) { - if (method_exists($this, 'set' . $name)) { - $this->{'set' . $name}($value); - } - } - } - - /** - * @return string $containerConfigurationPath - */ - public function getContainerConfigurationPath() - { - return $this->containerConfigurationPath; - } - - /** - * @param string $containerConfigurationPath - */ - public function setContainerConfigurationPath($containerConfigurationPath) - { - $this->containerConfigurationPath = $containerConfigurationPath; - return $this; - } - - /** - * @return string $mode - */ - public function getMode() - { - return $this->mode; - } - - /** - * @param string $mode - */ - public function setMode($mode) - { - $this->mode = $mode; - } - - /** - * @return string $developmentFileStatPath - */ - public function getDevelopmentFileStatPath() - { - return $this->developmentFileStatPath; - } - - /** - * @param string $developmentFileStatPath - */ - public function setDevelopmentFileStatPath($developmentFileStatPath) - { - $this->developmentFileStatPath = $developmentFileStatPath; - } - - /** - * @return array $managedDirectories - */ - public function getManagedDirectories() - { - return $this->managedDirectories; - } - - /** - * @param array $managedDirectories - */ - public function setManagedDirectories(array $managedDirectories) - { - $this->managedDirectories = $managedDirectories; - } - - /** - * @return array $managedNamespaces - */ - public function getManagedNamespaces() - { - return $this->managedNamespaces; - } - - /** - * @param array $managedNamespaces - */ - public function setManagedNamespaces(array $managedNamespaces) - { - $this->managedNamespaces = $managedNamespaces; - } - - public function getIntrospectors() - { - return array_keys($this->introspectors); - } - - public function setIntrospectors(array $introspectors) - { - $this->introspectors = array(); - foreach ($introspectors as $name => $value) { - if (is_int($name)) { - $this->introspectors[$value] = array(); - } elseif (is_string($name)) { - $this->introspectors[$name] = $value; - } - } - } - - public function getIntrospectionConfiguration($name) - { - return ((array_key_exists($name, $this->introspectors)) ? $this->introspectors[$name] : array()); - } - - public function setObjectConfigurations($objectConfigurations) - { - $this->objectConfigurations = $objectConfigurations; - } - - public function getObjectConfigurations() - { - return $this->objectConfigurations; - } - -} diff --git a/library/Zend/Di/Generator/DefinitionProxy.php b/library/Zend/Di/Generator/DefinitionProxy.php deleted file mode 100644 index 474a7df67e7..00000000000 --- a/library/Zend/Di/Generator/DefinitionProxy.php +++ /dev/null @@ -1,143 +0,0 @@ -definition = $definition; - } - - public function setClass($className) - { - $this->definition->setClass($className); - return $this; - } - - public function getClass() - { - return $this->definition->getClass(); - } - - public function setConstructorCallback($callback) - { - $this->definition->setConstructorCallback($callback); - return $this; - } - public function getConstructorCallback() - { - return $this->definition->getConstructorCallback(); - } - - public function hasConstructorCallback() - { - return $this->definition->hasConstructorCallback(); - } - - public function setParam($name, $value) - { - $this->definition->setParam($name, $value); - return $this; - } - - public function setParams(array $params) - { - $this->definition->setParams($params); - return $this; - } - - /** - * @param array $map Map of name => position pairs for constructor arguments - */ - public function setParamMap(array $map) - { - $this->definition->setParamMap($map); - return $this; - } - - public function getParams() - { - return $this->definition->getParams(); - } - - public function setShared($flag = true) - { - $this->definition->setShared($flag); - return $this; - } - - public function isShared() - { - return $this->definition->isShared(); - } - - - public function addTag($tag) - { - throw new \Exception('No Tags'); - } - - public function addTags(array $tags) - { - throw new \Exception('No Tags'); - } - - public function getTags() - { - throw new \Exception('No Tags'); - } - - public function hasTag($tag) - { - throw new \Exception('No Tags'); - } - - public function addMethodCall($name, array $args) - { - return $this->definition->addMethodCall($name, $args); - } - - /** - * @return InjectibleMethods - */ - public function getMethodCalls() {} - - public function toArray() - { - $params = array(); - foreach ($this->definition->constructorParams as $cParamName => $cParamValue) { - if ($cParamValue instanceof \Zend\Di\Reference) { - $cParamValue = array('__reference' => $cParamValue->getServiceName()); - } - $params[$cParamName] = $cParamValue; - } - - $methods = array(); - foreach ($this->definition->injectibleMethods as $method) { - $args = array(); - foreach ($method->getArgs() as $argKey => $arg) { - if ($arg instanceof \Zend\Di\Reference) { - $args[$argKey]['__reference'] = $arg->getServiceName(); - } else { - $args[$argKey][] = $arg; - } - } - $methods[] = array('name' => $method->getName(), 'args' => $args); - } - - return array( - 'class' => $this->definition->className, - 'methods' => $methods, - 'param_map' => ($this->definition->constructorParamMap) ?: array(), - 'params' => $params - ); - } - -} - diff --git a/library/Zend/Di/Generator/Generator.php b/library/Zend/Di/Generator/Generator.php deleted file mode 100644 index 6af94cd1b0a..00000000000 --- a/library/Zend/Di/Generator/Generator.php +++ /dev/null @@ -1,161 +0,0 @@ -configuration = ($configuration) ?: new Configuration(); - $this->di = ($di) ?: new DependencyInjector(); - } - - public function configuration() - { - return $this->configuration; - } - - public function di() - { - return $this->di; - } - - protected function validateConfiguration() - { - if ($this->configuration->getContainerConfigurationPath() == null) { - throw new \Exception('A containerConfigurationPath is required by ' . __CLASS__); - } - - if ($this->mode == Configuration::MODE_DEVELOPMENT) { - // @todo dev time required configuration stuffs - } - } - - protected function processConfiguration() - { -// if (!isset($this->generatorConfig['classes'])) { -// throw new \Exception('A values for classes must exist'); -// } -// if (!isset($this->generatorConfig['classes']['namespaces']) && !isset($this->generatorConfig['classes']['directories'])) { -// throw new \Exception('Either a namespace or a directory must be provided for the classes configuration'); -// } -// -// if (!isset($this->generatorConfig['classes']['namespaces'])) { -// $this->generatorConfig['classes']['namespaces'] = array(); -// } -// if (!isset($this->generatorConfig['classes']['directories'])) { -// $this->generatorConfig['classes']['directories'] = array(); -// } - - } - - - - public function build() - { - // validate the config object ? - $this->validateConfiguration(); - - // process the config ? - $this->processConfiguration(); - - // check to see if there is a dev time file stat cache (performance during dev) - $developmentStatFilePath = $this->configuration->getDevelopmentFileStatPath(); - - if ($developmentStatFilePath !== null) { - if (!file_exists($developmentStatFilePath)) { - // if it doesnt exist, create it with empty values - if (file_put_contents($developmentStatFilePath, 'setFileStatInformation($developmentFileStatInfo); - } - - // create class manager instance, with provided namespaces and directories to manage - $classManager = new TypeManager( - $typeRegistry, - $this->configuration->getManagedNamespaces(), - $this->configuration->getManagedDirectories() - ); - - $classManager->manage(); - - if ($developmentStatFilePath !== null && $typeRegistry->hasFileStatUpdates()) { - file_put_contents($developmentStatFilePath, 'getFileStatInformation(), true) . ';'); - } - - // load the managed di definitions - $managedDefinitions = new ManagedDefinitions(); - $containerConfigPath = $this->configuration->getContainerConfigurationPath(); - if ($containerConfigPath == null) { - throw new \Exception('A container configuration path was not found.'); - } - if (!file_exists($containerConfigPath)) { - if (file_put_contents($containerConfigPath, 'addDefinitionFromArray($containerConfigArray); - } - } - - foreach ($this->configuration->getIntrospectors() as $introspectorName) { - $introspectorClass = 'Zend\Di\Introspector\\' . ucfirst($introspectorName); - /* @var Zend\Di\Introspector $introspector */ - $introspector = new $introspectorClass; - $introspector->setConfiguration($this->configuration->getIntrospectionConfiguration($introspectorName)); - $introspector->setManagedDefinitions($managedDefinitions); - $introspector->setTypeRegistry($typeRegistry); - $introspector->introspect(); - } - - $managedDefinitions->mergeObjectConfiguration($this->configuration->getObjectConfigurations()); - - file_put_contents($containerConfigPath, 'toArray(), true) . ';'); - - } - - public function bootstrap() - { - $this->validateConfiguration(); - - $configuration = new Configuration($this->di); - - if (file_exists($this->generatorConfig['containerConfigurationPath'])) { - $containerConfiguration = include $this->generatorConfig['containerConfigurationPath']; - $configuration->fromArray($containerConfiguration); - } - - $mode = $this->configuration->getMode(); - - if ($mode == Configuration::MODE_PRODUCTION) { - //if (isset($this->config['definitionPath']) && file_exists($this->config['']))) - } - - if ($mode == Configuration::MODE_DEVELOPMENT) { - $this->build(); - } - } - -} \ No newline at end of file diff --git a/library/Zend/Di/Generator/Introspector.php b/library/Zend/Di/Generator/Introspector.php deleted file mode 100644 index 97a7f8a4584..00000000000 --- a/library/Zend/Di/Generator/Introspector.php +++ /dev/null @@ -1,11 +0,0 @@ -configuration = $configuration; - } - - public function setManagedDefinitions(\Zend\Di\Generator\ManagedDefinitions $managedDefinitions) - { - $this->managedDefinitions = $managedDefinitions; - } - - public function setTypeRegistry(\Zend\Di\Generator\TypeRegistry $classRegistry) - { - $this->typeRegistry = $classRegistry; - } - - public function introspect() - { - foreach ($this->typeRegistry as $type) { - - try { - $refClass = new \ReflectionClass($type); - //echo 'Reflecting ' . $type . PHP_EOL; - if ($refClass->hasMethod('__construct')) { - - $refConstructor = $refClass->getMethod('__construct'); - if ($refParameters = $refConstructor->getParameters()) { - - //echo ' Found injectable __construct ' . $type . PHP_EOL; - - if ($this->managedDefinitions->hasDefinition($type)) { - $definition = $this->managedDefinitions->getDefinition($type); - } else { - $definition = new \Zend\Di\Generator\DefinitionProxy(new \Zend\Di\Definition($type)); - $this->managedDefinitions->addDefinition($definition); - } - - $paramMaps = array(); - $params = array(); - foreach ($refParameters as $refParam) { - $paramMaps[$refParam->getName()] = $refParam->getPosition(); - - if ($refTypeClass = $refParam->getClass()) { - - //echo ' Param type: ' . $refTypeClass->getName() . PHP_EOL; - $params[$refParam->getName()] = new \Zend\Di\Reference($refTypeClass->getName()); - } - } - $definition->setParamMap($paramMaps); - if ($params) { - $definition->setParams($params); - } - } - } - } catch (\ReflectionException $e) { - throw new \Exception('An unmanaged type was found as a dependency'); - } - - } - } - -} diff --git a/library/Zend/Di/Generator/Introspector/InterfaceInjection.php b/library/Zend/Di/Generator/Introspector/InterfaceInjection.php deleted file mode 100644 index de96780c9a7..00000000000 --- a/library/Zend/Di/Generator/Introspector/InterfaceInjection.php +++ /dev/null @@ -1,82 +0,0 @@ -interfaces = $configuration; - } - - public function setManagedDefinitions(\Zend\Di\Generator\ManagedDefinitions $managedDefinitions) - { - $this->managedDefinitions = $managedDefinitions; - } - - public function setTypeRegistry(\Zend\Di\Generator\TypeRegistry $classRegistry) - { - $this->typeRegistry = $classRegistry; - } - - public function introspect() - { - $ifaceParams = array(); - - foreach ($this->interfaces as $interface) { - if (!interface_exists($interface, false)) { - throw new \Exception('An unmanaged interface was provided'); - } - - $refInterface = new \ReflectionClass($interface); - - $ifaceParams[$refInterface->getName()] = array(); - - foreach ($refInterface->getMethods() as $refMethod) { - $refParameters = $refMethod->getParameters(); - - //$paramMaps = array(); - $params = array(); - foreach ($refParameters as $refParam) { - //$paramMaps[$refParam->getName()] = $refParam->getPosition(); - - if ($refTypeClass = $refParam->getClass()) { - //echo ' Param type: ' . $refTypeClass->getName() . PHP_EOL; - $params[] = new \Zend\Di\Reference($refTypeClass->getName()); - } - } - } - - $ifaceParams[$refInterface->getName()][$refMethod->getName()] = $params; - - } - - - - foreach ($this->typeRegistry as $type) { - foreach (array_keys($ifaceParams) as $currentInterface) { - if (in_array($currentInterface, class_implements($type, false))) { - //echo 'FOUND INTERFACE INJECTION TYPE ' . $type . ' FOR INTERFACE ' . $currentInterface . PHP_EOL; - - if ($this->managedDefinitions->hasDefinition($type)) { - $definition = $this->managedDefinitions->getDefinition($type); - } else { - $definition = new \Zend\Di\Generator\DefinitionProxy(new \Zend\Di\Definition($type)); - $this->managedDefinitions->addDefinition($definition); - } - - foreach ($ifaceParams[$currentInterface] as $methodName => $methodParams) { - $definition->addMethodCall($methodName, $methodParams); - } - - } - } - } - - } - -} diff --git a/library/Zend/Di/Generator/Introspector/SetterInjection.php b/library/Zend/Di/Generator/Introspector/SetterInjection.php deleted file mode 100644 index 952da17be8b..00000000000 --- a/library/Zend/Di/Generator/Introspector/SetterInjection.php +++ /dev/null @@ -1,69 +0,0 @@ -configuration = $configuration; - } - - public function setManagedDefinitions(\Zend\Di\Generator\ManagedDefinitions $managedDefinitions) - { - $this->managedDefinitions = $managedDefinitions; - } - - public function setTypeRegistry(\Zend\Di\Generator\TypeRegistry $classRegistry) - { - $this->typeRegistry = $classRegistry; - } - - public function introspect() - { - foreach ($this->typeRegistry as $type) { - - try { - $refClass = new \ReflectionClass($type); - echo 'Reflecting ' . $type . PHP_EOL; - - foreach ($refClass->getMethods() as $refMethod) { - if (preg_match('#^set.*#', $refMethod->getName())) { - echo 'Found injectable method: ' . $refMethod->getName() . PHP_EOL; - - if ($this->managedDefinitions->hasDefinition($type)) { - $definition = $this->managedDefinitions->getDefinition($type); - } else { - $definition = new \Zend\Di\Generator\DefinitionProxy(new \Zend\Di\Definition($type)); - $this->managedDefinitions->addDefinition($definition); - } - - if ($refParameters = $refMethod->getParameters()) { - $params = array(); - foreach ($refParameters as $refParam) { - if ($refTypeClass = $refParam->getClass()) { - //echo ' Param type: ' . $refTypeClass->getName() . PHP_EOL; - $params[] = new \Zend\Di\Reference($refTypeClass->getName()); - } - } - $definition->addMethodCall($refMethod->getName(), $params); - } - } - } - - } catch (\ReflectionException $e) { - throw new \Exception('An unmanaged type was found as a dependency'); - } - - } - } - -} diff --git a/library/Zend/Di/Generator/ManagedDefinitions.php b/library/Zend/Di/Generator/ManagedDefinitions.php deleted file mode 100644 index a4c406ab91a..00000000000 --- a/library/Zend/Di/Generator/ManagedDefinitions.php +++ /dev/null @@ -1,106 +0,0 @@ - $value) { - switch (strtolower($key)) { - case 'class': - break; - case 'constructor_callback': - $callback = $value; - if (is_array($value) - && (isset($value['class']) && isset($value['method'])) - ) { - $callback = array($value['class'], $value['method']); - } - $definition->setConstructorCallback($callback); - break; - case 'params': - if (!is_array($value)) { - break; - } - $params = $this->resolveReferences($value); - $definition->setParams($params); - break; - case 'param_map': - $definition->setParamMap($value); - break; - case 'tags': - $definition->addTags($value); - break; - case 'shared': - $definition->setShared((bool) $value); - break; - case 'methods': - $this->buildMethods($definition, $value); - break; - default: - // ignore all other options - break; - } - } - - $this->addDefinition($definition); - } - - public function addDefinition(\Zend\Di\DependencyDefinition $definition) - { - $class = $definition->getClass(); - if ($this->hasDefinition($class)) { - throw new \InvalidArgumentException('A definition for this class already exist.'); - } - $this->definitions[$class] = $definition; - } - - public function hasDefinition($class) - { - return array_key_exists($class, $this->definitions); - } - - public function getDefinition($class) - { - return $this->definitions[$class]; - } - - public function mergeObjectConfiguration($objectConfiguration) - { - foreach ($objectConfiguration as $class => $configValues) { - if (!array_key_exists($class, $this->definitions)) { - continue; - } - $def = $this->definitions[$class]; - $def->setParams($configValues); - } - } - - public function toArray() - { - $defs = array(); - /* @var $definition \Zend\Di\Definition */ - foreach ($this->definitions as $definition) { - $defArray = $definition->toArray(); - $defs[] = $defArray; - } - return $defs; - } - -} diff --git a/library/Zend/Di/Generator/TypeManager.php b/library/Zend/Di/Generator/TypeManager.php deleted file mode 100644 index d47bf448574..00000000000 --- a/library/Zend/Di/Generator/TypeManager.php +++ /dev/null @@ -1,107 +0,0 @@ -typeRegistry = $typeRegistry; - - // create directory iterator basd on namespace - $includePaths = explode(PATH_SEPARATOR, get_include_path()); - foreach ($namespaces as $namespace) { - $this->addNamespace($namespace); - } - - // create directory iterator based on explicit directory - foreach ($directories as $directory) { - $this->addDirectory($directory); - } - } - - public function addNamespace($namespace) - { - $namespaceAsDirectory = ltrim(str_replace('\\', DIRECTORY_SEPARATOR, $namespace), DIRECTORY_SEPARATOR); - if (($directory = stream_resolve_include_path($namespaceAsDirectory)) === false) { - throw new \Exception('Namespace not located in include_path'); - } else { - $this->managedNamespaces[$directory] = $namespace; - //$this->directoryIterators[] = new \RecursiveDirectoryIterator($directory); - } - } - - - /** - * There is an interesting problem with managing directories. Classes need to be handled by autoloading, - * since when they are reflected later, will need to be autoloaded if they are not known. In some cases - * we will have outside types that are not managed by the DI system that are dependencies. We do not - * want to autoload these since they are not inside our managed namespace.. - */ - -// public function addDirectory($directory) -// { -// if (!file_exists($directory)) { -// throw new \Exception('Directory ' . $directory . ' not found'); -// } else { -// $this->directoryIterators[] = new \RecursiveDirectoryIterator($directory); -// } -// } - - - /** - * - */ - public function manage() - { - if ($this->managedNamespaces == null) { - throw new \Exception('Nothing to manage.'); - } - - $currentNamespace = null; - - // convert namespace to directory iterator, but first, create a namespace only autoloader. - $nsAutoloader = function($class) use (&$currentNamespace) { - if (strpos($class, $currentNamespace) !== 0) return; - $file = str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $class) . '.php'; - return (false !== ($file = stream_resolve_include_path($file))) ? include_once($file) : false; - }; - - spl_autoload_register($nsAutoloader); - - //foreach ($this->directoryIterators as $dirIter) { - foreach ($this->managedNamespaces as $directory => $currentNamespace) { - $directoryIterator = new \RecursiveDirectoryIterator($directory); - $rii = new \RecursiveIteratorIterator($directoryIterator); //, $mode, $flags); - foreach ($rii as $item) { - if (!$rii->isDot() && $item->getType() == 'file' && preg_match('#\.php$#', $item->getFilename())) { - - /** - * @todo Short-circuit here is fmtime has not changed and is inside the ClassRegistry - */ - - $classes = get_declared_classes(); - $interfaces = get_declared_interfaces(); - require_once $item->getPathname(); - foreach (array_values(array_diff(get_declared_classes(), $classes)) as $class) { - $this->typeRegistry->register($class, $item->getPathname(), $item->getMtime()); - } - foreach (array_values(array_diff(get_declared_interfaces(), $interfaces)) as $interface) { - $this->typeRegistry->register($interface, $item->getPathname(), $item->getMtime()); - } - } - } - } - - spl_autoload_unregister($nsAutoloader); - - } - -} diff --git a/library/Zend/Di/Generator/TypeRegistry.php b/library/Zend/Di/Generator/TypeRegistry.php deleted file mode 100644 index 814796aeed6..00000000000 --- a/library/Zend/Di/Generator/TypeRegistry.php +++ /dev/null @@ -1,119 +0,0 @@ -fileStatInformation = $fileStatInformation; - } - - public function hasFileStatUpdates() - { - return true; - } - - public function getFileStatInformation() - { - return $this->fileStatInformation; - } - - public function isFileStatFresh($file, $mtime) - { - return true; - } - - public function register($type, $file = null, $fileMtime = null) - { - if ($file == null || $fileMtime == null) { - list($file, $fileMtime) = $this->discoverClassFile($type); - } - - $this->fileStatInformation[$file] = $fileMtime; - - /** - * @todo If class is already here, and mtime has not changed, do not update entry, return - */ - $isNew = true; - - $this->types[] = array('class' => $type, 'file' => $file, 'mtime' => $fileMtime, 'isNew' => true); - } - - public function setIteratorMode($iteratorMode) - { - $this->iteratorMode = $iteratorMode; - } - - public function rewind() - { - reset($this->types); - if (count($this->types) > 0) { - $this->iteratorValid = true; - } - } - - public function valid() - { - return $this->iteratorValid; - } - - public function next() - { - $return = next($this->types); - $this->iteratorValid = ($return !== false); - } - - public function current() - { - $classInfo = current($this->types); - return $classInfo['class']; - } - - public function key() - { - return key($this->types); - } - - /** - * discoverClassFile() - * - * This should technically never run since in most cases someone else (some iterator) - * will determin the file and fileMtime. But in cases where its not provided in register(), - * this will run. - * - * @param string $class - * @return array ($file, $mtime) - */ - protected function discoverClassFile($class) - { - $classRefl = new \ReflectionClass($class); - $file = $classRefl->getFileName(); - $fmtime = filemtime($file); - unset($classRefl); - return array($file, $fmtime); - } - - public function toArray() - { - $classes = array(); - foreach ($this as $class) { - $classes[] = $class; - } - return $classes; - } - -} \ No newline at end of file diff --git a/library/Zend/Di/InjectibleMethod.php b/library/Zend/Di/InjectibleMethod.php deleted file mode 100644 index 76d4976f9b0..00000000000 --- a/library/Zend/Di/InjectibleMethod.php +++ /dev/null @@ -1,14 +0,0 @@ -sharedInstances[$class]); + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::getSharedInstance() + */ + public function getSharedInstance($class, array $params = array()) + { + return $this->sharedInstances[$class]; + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::addSharedInstance() + */ + public function addSharedInstance($object, $class, array $params = array()) + { + $this->sharedInstances[$class] = $object; + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::getClassFromAlias() + */ + public function getClassFromAlias($alias) + { + if (isset($this->aliases[$alias])) { + return $this->aliases[$alias]; + } + return $alias; // must be a class? + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::addAlias() + */ + public function addAlias($class, $alias, $params = array()) + { + // @todo impelement params for aliases + $this->aliases[$alias] = $class; + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::hasProperties() + */ + public function hasProperties($classOrAlias) + { + $class = $this->getClassFromAlias($classOrAlias); + return isset($this->properties[$class]); + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::getProperties() + */ + public function getProperties($classOrAlias) + { + // @todo better alias property management + if (isset($this->properties[$classOrAlias])) { + return $this->properties[$classOrAlias]; + } + return array(); + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::getProperty() + */ + public function getProperty($classOrAlias, $name) + { + // @todo better alias property management + if (isset($this->properties[$classOrAlias])) { + return $this->properties[$classOrAlias][$name]; + } + return null; + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::setProperty() + */ + public function setProperty($classOrAlias, $name, $value) + { + if (!isset($this->properties[$classOrAlias])) { + $this->properties[$classOrAlias] = array(); + } + $this->properties[$classOrAlias][$name] = $value; + } + + /** + * (non-PHPdoc) + * @see Zend\Di.InstanceCollection::unsetProperty() + */ + public function unsetProperty($classOrAlias, $name) + { + if (isset($this->properties[$classOrAlias])) { + unset($this->properties[$classOrAlias][$name]); + return true; + } + return false; + } + + +} \ No newline at end of file diff --git a/library/Zend/Di/Method.php b/library/Zend/Di/Method.php deleted file mode 100644 index 458806ad694..00000000000 --- a/library/Zend/Di/Method.php +++ /dev/null @@ -1,217 +0,0 @@ - argument name) - * @var array - */ - protected $paramMap; - - /** - * The parameters in argument order - * @var null|array - */ - protected $orderedParams; - - /** - * Construct the method signature - * - * @param strinb $name - * @param array $params - * @param array|null $paramMap - * @return void - */ - public function __construct($name, array $params = null, array $paramMap = null) - { - $this->name = $name; - if (is_array($params)) { - $this->setParams($params); - } - if (is_array($paramMap)) { - $this->setParamMap($paramMap); - } - } - - /** - * Retrieve the method name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * Retrieve the arguments to pass to the method - * - * If no parameter map is present, the parameter values are returned in - * the order defined. - * - * If a parameter map is found, the parameter values are returned in the - * order specified by the map. - * - * @return array - */ - public function getParams() - { - if (null !== $this->orderedParams) { - return $this->orderedParams; - } - - $map = $this->getParamMap(); - if (!$map) { - return array_values($this->params); - } - - // Sort map, and flip such that positions become keys - asort($map); - $map = array_flip($map); - - $params = array(); - foreach ($map as $index => $key) { - $value = null; - if (isset($this->params[$key])) { - $value = $this->params[$key]; - } elseif (isset($this->params[$index])) { - $value = $this->params[$index]; - } - $params[] = $value; - } - - $this->orderedParams = $params; - return $params; - } - - /** - * Set method parameters - * - * @param array $params - * @return Method - */ - public function setParams(array $params) - { - $this->params = $params; - $this->orderedParams = null; - return $this; - } - - /** - * Set class to which method belongs - * - * @param string $class - * @return Method - */ - public function setClass($class) - { - $this->class = $class; - return $this; - } - - /** - * Retrieve class to which method belongs, if set. - * - * @return null|string - */ - public function getClass() - { - return $this->class; - } - - /** - * Set parameter map (order of named arguments) - * - * Should be an ordered array, with string keys pointing to integer - * placement order. - * - * @param array $map - * @return Method - */ - public function setParamMap(array $map) - { - $this->paramMap = $map; - return $this; - } - - /** - * Get parameter map - * - * If a parameter map is present, returns it. - * - * If no map is set, and no class is set, returns false. - * - * If no map is set, but the class is set, creates a parameter map by - * reflecting on the method. - * - * @return array|false - */ - public function getParamMap() - { - if (is_array($this->paramMap)) { - return $this->paramMap; - } - - $class = $this->getClass(); - if ((null === $class) || !class_exists($class)) { - return false; - } - - $map = $this->buildParamMapFromReflection(); - $this->setParamMap($map); - return $map; - } - - /** - * Create a parameter map from Reflection - * - * @return array - */ - protected function buildParamMapFromReflection() - { - try { - $method = new ReflectionMethod($this->getClass(), $this->getName()); - } catch (ReflectionException $e) { - throw new Exception\RuntimeException(sprintf( - 'Method definition for method "%s" cannot be reflected', - $this->getName() - ), $e->getCode(), $e); - } - $parameters = $method->getParameters(); - $map = array(); - foreach ($parameters as $param) { - $map[$param->getName()] = $param->getPosition(); - } - return $map; - } -} diff --git a/library/Zend/Di/Methods.php b/library/Zend/Di/Methods.php deleted file mode 100644 index 4404cddf5b3..00000000000 --- a/library/Zend/Di/Methods.php +++ /dev/null @@ -1,75 +0,0 @@ -methods[] = $method; - } - - /** - * Return the current method object - * - * @return InjectibleMethod - */ - public function current() - { - return current($this->methods); - } - - /** - * Return the current method's name - * - * @return string - */ - public function key() - { - $method = $this->current(); - return $method->getName(); - } - - /** - * Iterator: Move to the next item in the list - * - * @return void - */ - public function next() - { - return next($this->methods); - } - - /** - * Iterator: Reset the pointer - * - * @return void - */ - public function rewind() - { - return reset($this->methods); - } - - /** - * Iterator: Is the current index valid? - * - * @return bool - */ - public function valid() - { - return (current($this->methods) !== false); - } -} diff --git a/library/Zend/Di/Reference.php b/library/Zend/Di/Reference.php deleted file mode 100644 index ca9d13d1355..00000000000 --- a/library/Zend/Di/Reference.php +++ /dev/null @@ -1,41 +0,0 @@ -name = $serviceName; - } - - /** - * Retrieve service name - * - * @return string - */ - public function getServiceName() - { - return $this->name; - } -} diff --git a/library/Zend/Di/ContainerBuilder.php b/library/Zend/Di/ServiceLocator/Generator.php similarity index 98% rename from library/Zend/Di/ContainerBuilder.php rename to library/Zend/Di/ServiceLocator/Generator.php index 37d07516a78..5309ed62578 100644 --- a/library/Zend/Di/ContainerBuilder.php +++ b/library/Zend/Di/ServiceLocator/Generator.php @@ -1,7 +1,9 @@ Date: Wed, 8 Jun 2011 16:04:05 -0500 Subject: [PATCH 35/45] Merging in refactored unit tests for Zend\Di --- library/Zend/Di/Configuration.php | 21 +- .../Di/Definition/Builder/InjectionMethod.php | 2 +- .../Zend/Di/Definition/Builder/PhpClass.php | 9 +- .../Zend/Di/Definition/BuilderDefinition.php | 3 +- library/Zend/Di/DependencyInjector.php | 24 +- .../Exception/CircularDependencyException.php | 9 + .../Di/Exception/MissingPropertyException.php | 9 + library/Zend/Di/InstanceCollection.php | 11 +- library/Zend/Di/InstanceManager.php | 13 + tests/Bootstrap.php | 10 +- tests/Zend/Di/ConfigurationTest.php | 179 +----- tests/Zend/Di/ContainerBuilderTest.php | 263 --------- .../Di/Definition/ArrayDefinitionTest.php | 13 + .../Di/Definition/BuilderDefinitionTest.php | 41 ++ tests/Zend/Di/Definition/CompilerTest.php | 13 + .../Zend/Di/Definition/ConfigurationTest.php | 13 + .../Di/Definition/RuntimeDefinitionTest.php | 13 + tests/Zend/Di/DefinitionTest.php | 250 -------- .../Di/DependencyInjectionContainerTest.php | 57 -- tests/Zend/Di/DependencyInjectorTest.php | 554 ++++++------------ tests/Zend/Di/InstanceManagerTest.php | 34 ++ tests/Zend/Di/MethodTest.php | 94 --- tests/Zend/Di/MethodsTest.php | 61 -- tests/Zend/Di/ReferenceTest.php | 16 - tests/Zend/Di/ServiceLocatorTest.php | 102 ---- .../{ComposedClass.php => BasicClass.php} | 6 +- .../Zend/Di/TestAsset/BasicClassWithParam.php | 8 + tests/Zend/Di/TestAsset/CircularClasses/A.php | 11 + tests/Zend/Di/TestAsset/CircularClasses/B.php | 8 + tests/Zend/Di/TestAsset/CircularClasses/C.php | 11 + tests/Zend/Di/TestAsset/CircularClasses/D.php | 11 + tests/Zend/Di/TestAsset/CircularClasses/E.php | 11 + tests/Zend/Di/TestAsset/CircularClasses/X.php | 11 + tests/Zend/Di/TestAsset/ClassA.php | 19 - tests/Zend/Di/TestAsset/ClassB.php | 20 - tests/Zend/Di/TestAsset/ClassC.php | 20 - tests/Zend/Di/TestAsset/ClassD.php | 20 - .../Di/TestAsset/ConstructorInjection/A.php | 7 + .../Di/TestAsset/ConstructorInjection/B.php | 12 + .../Di/TestAsset/ConstructorInjection/X.php | 14 + .../Di/TestAsset/ConstructorInjection/Y.php | 12 + .../Di/TestAsset/ConstructorInjection/Z.php | 12 + .../Zend/Di/TestAsset/ContainerExtension.php | 26 - tests/Zend/Di/TestAsset/DummyParams.php | 12 - tests/Zend/Di/TestAsset/InjectedMethod.php | 10 - tests/Zend/Di/TestAsset/InspectedClass.php | 11 - tests/Zend/Di/TestAsset/OptionalArg.php | 15 - tests/Zend/Di/TestAsset/SetterInjection/A.php | 7 + tests/Zend/Di/TestAsset/SetterInjection/B.php | 12 + tests/Zend/Di/TestAsset/SetterInjection/X.php | 17 + tests/Zend/Di/TestAsset/SetterInjection/Y.php | 12 + tests/Zend/Di/TestAsset/SetterInjection/Z.php | 12 + tests/Zend/Di/TestAsset/StaticFactory.php | 11 - tests/Zend/Di/TestAsset/Struct.php | 11 - tests/phpunit.xml | 2 +- 55 files changed, 607 insertions(+), 1578 deletions(-) create mode 100644 library/Zend/Di/Exception/CircularDependencyException.php create mode 100644 library/Zend/Di/Exception/MissingPropertyException.php delete mode 100644 tests/Zend/Di/ContainerBuilderTest.php create mode 100644 tests/Zend/Di/Definition/ArrayDefinitionTest.php create mode 100644 tests/Zend/Di/Definition/BuilderDefinitionTest.php create mode 100644 tests/Zend/Di/Definition/CompilerTest.php create mode 100644 tests/Zend/Di/Definition/ConfigurationTest.php create mode 100644 tests/Zend/Di/Definition/RuntimeDefinitionTest.php delete mode 100644 tests/Zend/Di/DefinitionTest.php delete mode 100644 tests/Zend/Di/DependencyInjectionContainerTest.php create mode 100644 tests/Zend/Di/InstanceManagerTest.php delete mode 100644 tests/Zend/Di/MethodTest.php delete mode 100644 tests/Zend/Di/MethodsTest.php delete mode 100644 tests/Zend/Di/ReferenceTest.php delete mode 100644 tests/Zend/Di/ServiceLocatorTest.php rename tests/Zend/Di/TestAsset/{ComposedClass.php => BasicClass.php} (63%) create mode 100644 tests/Zend/Di/TestAsset/BasicClassWithParam.php create mode 100644 tests/Zend/Di/TestAsset/CircularClasses/A.php create mode 100644 tests/Zend/Di/TestAsset/CircularClasses/B.php create mode 100644 tests/Zend/Di/TestAsset/CircularClasses/C.php create mode 100644 tests/Zend/Di/TestAsset/CircularClasses/D.php create mode 100644 tests/Zend/Di/TestAsset/CircularClasses/E.php create mode 100644 tests/Zend/Di/TestAsset/CircularClasses/X.php delete mode 100755 tests/Zend/Di/TestAsset/ClassA.php delete mode 100755 tests/Zend/Di/TestAsset/ClassB.php delete mode 100755 tests/Zend/Di/TestAsset/ClassC.php delete mode 100755 tests/Zend/Di/TestAsset/ClassD.php create mode 100644 tests/Zend/Di/TestAsset/ConstructorInjection/A.php create mode 100644 tests/Zend/Di/TestAsset/ConstructorInjection/B.php create mode 100644 tests/Zend/Di/TestAsset/ConstructorInjection/X.php create mode 100644 tests/Zend/Di/TestAsset/ConstructorInjection/Y.php create mode 100644 tests/Zend/Di/TestAsset/ConstructorInjection/Z.php delete mode 100644 tests/Zend/Di/TestAsset/ContainerExtension.php delete mode 100644 tests/Zend/Di/TestAsset/DummyParams.php delete mode 100644 tests/Zend/Di/TestAsset/InjectedMethod.php delete mode 100644 tests/Zend/Di/TestAsset/InspectedClass.php delete mode 100644 tests/Zend/Di/TestAsset/OptionalArg.php create mode 100644 tests/Zend/Di/TestAsset/SetterInjection/A.php create mode 100644 tests/Zend/Di/TestAsset/SetterInjection/B.php create mode 100644 tests/Zend/Di/TestAsset/SetterInjection/X.php create mode 100644 tests/Zend/Di/TestAsset/SetterInjection/Y.php create mode 100644 tests/Zend/Di/TestAsset/SetterInjection/Z.php delete mode 100644 tests/Zend/Di/TestAsset/StaticFactory.php delete mode 100644 tests/Zend/Di/TestAsset/Struct.php diff --git a/library/Zend/Di/Configuration.php b/library/Zend/Di/Configuration.php index 93f81f716c8..a5a71c200f2 100644 --- a/library/Zend/Di/Configuration.php +++ b/library/Zend/Di/Configuration.php @@ -4,6 +4,23 @@ class Configuration { - protected $definition = null; - protected $instanceManager = null; + protected $data = array(); + + public function __construct($data) + { + if ($data instanceof \Zend\Config\Config) { + $data = $data->toArray(); + } + } + + public function getDefinition() + { + + } + + public function getInstanceManager() + { + + } + } \ No newline at end of file diff --git a/library/Zend/Di/Definition/Builder/InjectionMethod.php b/library/Zend/Di/Definition/Builder/InjectionMethod.php index 9f1ac9d9b5a..5664277d747 100644 --- a/library/Zend/Di/Definition/Builder/InjectionMethod.php +++ b/library/Zend/Di/Definition/Builder/InjectionMethod.php @@ -14,7 +14,7 @@ public function setName($name) $this->name = $name; } - public function getName($name) + public function getName() { return $this->name; } diff --git a/library/Zend/Di/Definition/Builder/PhpClass.php b/library/Zend/Di/Definition/Builder/PhpClass.php index 1f1cf351704..0697eee5123 100644 --- a/library/Zend/Di/Definition/Builder/PhpClass.php +++ b/library/Zend/Di/Definition/Builder/PhpClass.php @@ -25,9 +25,15 @@ public function setInstantiator($instantiator) return $this; } + public function getInstantiator() + { + return $this->instantiator; + } + public function addSuperType($superType) { $this->superTypes[] = $superType; + return $this; } public function getSuperTypes() @@ -38,11 +44,12 @@ public function getSuperTypes() public function addInjectionMethod(InjectionMethod $injectionMethod) { $this->injectionMethods[] = $injectionMethod; + return $this; } public function getInjectionMethods() { - $this->injectionMethods; + return $this->injectionMethods; } } diff --git a/library/Zend/Di/Definition/BuilderDefinition.php b/library/Zend/Di/Definition/BuilderDefinition.php index 149c03a3ad5..a3161d57222 100644 --- a/library/Zend/Di/Definition/BuilderDefinition.php +++ b/library/Zend/Di/Definition/BuilderDefinition.php @@ -64,11 +64,12 @@ public function getInstantiator($class) public function hasInjectionMethods($class) { + /* @var $class Zend\Di\Definition\Builder\PhpClass */ $class = $this->getClass($class); if ($class === false) { throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); } - return $class->getInstantiator(); + return (count($class->getInjectionMethods()) > 0); } public function getInjectionMethods($class) diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php index cd48a536c77..6b2d5738000 100755 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -28,12 +28,14 @@ class DependencyInjector implements DependencyInjection */ protected $references = array(); + protected $circularDependencyCheck = true; + /** * @param Zend\DI\Configuration $config */ public function __construct(Configuration $config = null) { - if ($this->config) { + if ($config) { $this->setConfiguration($config); } } @@ -43,7 +45,7 @@ public function setConfiguration(Configuration $config) // @todo process this } - public function setDefinition(Definition\DefinitionInterface $definition) + public function setDefinition(Definition $definition) { $this->definition = $definition; return $this; @@ -112,13 +114,11 @@ public function get($name, array $params = array()) public function newInstance($name, array $params = array(), $isShared = true) { $this->getDefinition(); - - // check if name is alias - //$class = (array_key_exists($name, $this->aliases)) ? $this->aliases[$name] : $name; - $class = $name; + + $class = $this->getInstanceManager()->getClassFromAlias($name); if (!$this->definition->hasClass($class)) { - throw new Exception\InvalidArgumentException('Invalid class name or alias provided.'); + throw new Exception\ClassNotFoundException('Class or alias name ' . $name . ' could not be located in provided definition.'); } $instantiator = $this->definition->getInstantiator($class); @@ -241,13 +241,13 @@ protected function resolveMethodParameters($class, $method, array $params, $isIn $index = 0; foreach ($this->definition->getInjectionMethodParameters($class, $method) as $name => $value) { if ($value === null && !array_key_exists($name, $params)) { - throw new Exception\RuntimeException('Missing parameter named ' . $name . ' for ' . $class . '::' . $method); + throw new Exception\MissingPropertyException('Missing parameter named ' . $name . ' for ' . $class . '::' . $method); } // circular dep check if ($isInstantiator && $value !== null) { $this->dependencies[$class][$value]= true; - //$this->references[$serviceName][$className]= true; + $this->checkCircularDependency($class, $value); } if ($value === null) { @@ -273,12 +273,12 @@ protected function checkCircularDependency($class, $dependency) if (is_array($dependency)) { foreach ($dependency as $dep) { if (isset($this->dependencies[$dep][$class]) && $this->dependencies[$dep][$class]) { - throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dep and viceversa"); + throw new Exception\CircularDependencyException("Circular dependency detected: $class depends on $dep and viceversa"); } } } else { if (isset($this->dependencies[$dependency][$class]) && $this->dependencies[$dependency][$class]) { - throw new Exception\RuntimeException("Circular dependency detected: $class depends on $dependency and viceversa"); + throw new Exception\CircularDependencyException("Circular dependency detected: $class depends on $dependency and viceversa"); } } return true; @@ -291,6 +291,7 @@ protected function checkCircularDependency($class, $dependency) * @param type $dependency * @return void */ + /* protected function checkPathDependencies($class, $dependency) { if (!empty($this->references[$class])) { @@ -303,5 +304,6 @@ protected function checkPathDependencies($class, $dependency) } } } + */ } diff --git a/library/Zend/Di/Exception/CircularDependencyException.php b/library/Zend/Di/Exception/CircularDependencyException.php new file mode 100644 index 00000000000..16e8ab7562c --- /dev/null +++ b/library/Zend/Di/Exception/CircularDependencyException.php @@ -0,0 +1,9 @@ +properties[$classOrAlias])) { + return true; + } + return false; + } + /** * (non-PHPdoc) * @see Zend\Di.InstanceCollection::getProperty() diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index aeb25254486..eb94b4a0959 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -63,22 +63,24 @@ if (defined('TESTS_GENERATE_REPORT') && TESTS_GENERATE_REPORT === true && version_compare(PHPUnit_Runner_Version::id(), '3.1.6', '>=')) { + $codeCoverageFilter = PHP_CodeCoverage_Filter::getInstance(); + /* * Add Zend Framework library/ directory to the PHPUnit code coverage * whitelist. This has the effect that only production code source files * appear in the code coverage report and that all production code source * files, even those that are not covered by a test yet, are processed. */ - PHPUnit_Util_Filter::addDirectoryToWhitelist($zfCoreLibrary); + //$codeCoverageFilter->addDirectoryToWhitelist($zfCoreLibrary); /* * Omit from code coverage reports the contents of the tests directory */ foreach (array('.php', '.phtml', '.csv', '.inc') as $suffix) { - PHPUnit_Util_Filter::addDirectoryToFilter($zfCoreTests, $suffix); + $codeCoverageFilter->addDirectoryToBlacklist($zfCoreTests, $suffix); } - PHPUnit_Util_Filter::addDirectoryToFilter(PEAR_INSTALL_DIR); - PHPUnit_Util_Filter::addDirectoryToFilter(PHP_LIBDIR); + $codeCoverageFilter->addDirectoryToBlacklist(PEAR_INSTALL_DIR); + $codeCoverageFilter->addDirectoryToBlacklist(PHP_LIBDIR); } diff --git a/tests/Zend/Di/ConfigurationTest.php b/tests/Zend/Di/ConfigurationTest.php index bd2336ae067..28867d64267 100644 --- a/tests/Zend/Di/ConfigurationTest.php +++ b/tests/Zend/Di/ConfigurationTest.php @@ -1,180 +1,13 @@ getConfig(); - $di = new DependencyInjector(); - $dibuilder = new Configuration($di); - $dibuilder->fromArray($config); - $this->assertObjectGraph($di); - } - - public function testCanCreateObjectGraphFromZendConfig() - { - $config = new Config($this->getConfig()); - $di = new DependencyInjector(); - $dibuilder = new Configuration($di); - $dibuilder->fromConfig($config); - $this->assertObjectGraph($di); - } - - public function testCanCreateObjectGraphFromIniConfig() - { - $config = new IniConfig(__DIR__ . '/_files/config.ini', 'testing'); - $di = new DependencyInjector(); - $dibuilder = new Configuration($di); - $dibuilder->fromConfig($config); - $this->assertObjectGraph($di); - } - - public function testCanCreateObjectGraphFromXmlConfig() - { - $config = new XmlConfig(__DIR__ . '/_files/config.xml', 'testing'); - $di = new DependencyInjector(); - $dibuilder = new Configuration($di); - $dibuilder->fromConfig($config); - $this->assertObjectGraph($di); - } - - public function testCanCreateObjectGraphFromYamlConfig() - { - $config = new YamlConfig(__DIR__ . '/_files/config.yml', 'testing'); - $di = new DependencyInjector(); - $dibuilder = new Configuration($di); - $dibuilder->fromConfig($config); - $this->assertObjectGraph($di); - } - - public function testCanCreateObjectGraphFromJsonConfig() - { - $config = new JsonConfig(__DIR__ . '/_files/config.json', 'testing'); - $di = new DependencyInjector(); - $dibuilder = new Configuration($di); - $dibuilder->fromConfig($config); - $this->assertObjectGraph($di); - } - - public function assertObjectGraph($di) - { - $inspected = $di->get('inspected'); - $injected = $di->get('injected'); - $struct = $di->get('struct'); - $params = $di->get('params'); - - $this->assertInstanceOf('ZendTest\Di\TestAsset\InspectedClass', $inspected); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectedMethod', $injected); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $struct); - $this->assertInstanceOf('ZendTest\Di\TestAsset\DummyParams', $params); - - $this->assertEquals('FOO', $inspected->foo); - $this->assertEquals('BAZ', $inspected->baz); - - $this->assertEquals(array('params' => array('param1' => 'foo', 'param2' => 'bar', 'foo' => 'bar')), (array) $params); - - $this->assertEquals(array('param1' => 'foo', 'param2' => 'bar'), (array) $struct); - $this->assertSame($params, $injected->object, sprintf('Params: %s; Injected: %s', var_export($params, 1), var_export($injected, 1))); - } - - public function getConfig() + public function testTrue() { - return array( - 'definitions' => array( - array( - 'class' => 'ZendTest\Di\TestAsset\Struct', - 'params' => array( - 'param1' => 'foo', - 'param2' => 'bar', - ), - 'param_map' => array( - 'param1' => 0, - 'param2' => 1, - ), - ), - array( - 'class' => 'ZendTest\Di\TestAsset\DummyParams', - 'constructor_callback' => array( - 'class' => 'ZendTest\Di\TestAsset\StaticFactory', - 'method' => 'factory', - ), - 'params' => array( - 'struct' => array('__reference' => 'struct'), - 'params' => array('foo' => 'bar'), - ), - 'param_map' => array( - 'struct' => 0, - 'params' => 1, - ), - ), - array( - 'class' => 'ZendTest\Di\TestAsset\InjectedMethod', - 'methods' => array( - array( - 'name' => 'setObject', - 'params' => array( - array('__reference' => 'params'), - ), - ), - ), - ), - array( - 'class' => 'ZendTest\Di\TestAsset\InspectedClass', - 'params' => array( - 'baz' => 'BAZ', - 'foo' => 'FOO', - ), - ), - ), - 'aliases' => array( - 'struct' => 'ZendTest\Di\TestAsset\Struct', - 'params' => 'ZendTest\Di\TestAsset\DummyParams', - 'injected' => 'ZendTest\Di\TestAsset\InjectedMethod', - 'inspected' => 'ZendTest\Di\TestAsset\InspectedClass', - ), - ); - /* - return array( - 'definitions' => array( - array( - 'class' => 'className', - 'constructor_callback' => false, - // or string, or array; if array, 'class' and 'method' - // strings - 'params' => array( - 'name' => 'value', - // if value is an array, look for '__reference' key, - // and, if found, create a Reference object - ), - 'param_map' => array( - ), - 'tags' => array(), - 'shared' => true, - 'methods' => array( - array( - 'name' => 'method_name', - 'params' => array( /* ... * / ), - // if value is an array, look for '__reference' - // key, and, if found, create a Reference object - ), - ), - ), - ), - 'aliases' => array( - 'alias' => 'target', - ), - ); - */ + $this->assertTrue(true); // placeholder } -} +} \ No newline at end of file diff --git a/tests/Zend/Di/ContainerBuilderTest.php b/tests/Zend/Di/ContainerBuilderTest.php deleted file mode 100644 index 229956a3d03..00000000000 --- a/tests/Zend/Di/ContainerBuilderTest.php +++ /dev/null @@ -1,263 +0,0 @@ -tmpFile = false; - $this->di = new DependencyInjector; - } - - public function tearDown() - { - if ($this->tmpFile) { - unlink($this->tmpFile); - $this->tmpFile = false; - } - } - - public function getTmpFile() - { - $this->tmpFile = tempnam(sys_get_temp_dir(), 'zdi'); - return $this->tmpFile; - } - - public function createDefinitions() - { - $inspect = new Definition('ZendTest\Di\TestAsset\InspectedClass'); - $inspect->setParam('foo', new Reference('composed')) - ->setParam('baz', 'BAZ'); - $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setParam('param1', 'foo') - ->setParam('param2', new Reference('inspect')); - $this->di->setDefinition($composed, 'composed') - ->setDefinition($inspect, 'inspect') - ->setDefinition($struct, 'struct'); - } - - public function buildContainerClass($name = 'Application') - { - $this->createDefinitions(); - $builder = new ContainerBuilder($this->di); - $builder->setContainerClass($name); - $builder->getCodeGenerator($this->getTmpFile())->write(); - $this->assertFileExists($this->tmpFile); - } - - public function testCreatesContainerClassFromConfiguredDependencyInjector() - { - $this->buildContainerClass(); - - $tokens = token_get_all(file_get_contents($this->tmpFile)); - $count = count($tokens); - $found = false; - $value = false; - for ($i = 0; $i < $count; $i++) { - $token = $tokens[$i]; - if (is_string($token)) { - continue; - } - if ("T_CLASS" == token_name($token[0])) { - $found = true; - $value = false; - do { - $i++; - $token = $tokens[$i]; - if (is_string($token)) { - $id = null; - } else { - list($id, $value) = $token; - } - } while (($i < $count) && (token_name($id) != 'T_STRING')); - break; - } - } - $this->assertTrue($found, "Class token not found"); - $this->assertContains('Application', $value); - } - - public function testCreatesContainerClassWithCasesForEachService() - { - $this->buildContainerClass(); - - $tokens = token_get_all(file_get_contents($this->tmpFile)); - $count = count($tokens); - $services = array(); - for ($i = 0; $i < $count; $i++) { - $token = $tokens[$i]; - if (is_string($token)) { - continue; - } - if ('T_CASE' == token_name($token[0])) { - do { - $i++; - if ($i >= $count) { - break; - } - $token = $tokens[$i]; - if (is_string($token)) { - $id = $token; - } else { - $id = $token[0]; - } - } while (($i < $count) && ($id != T_CONSTANT_ENCAPSED_STRING)); - if (is_array($token)) { - $services[] = preg_replace('/\\\'/', '', $token[1]); - } - } - } - $expected = array( - 'composed', - 'ZendTest\Di\TestAsset\ComposedClass', - 'inspect', - 'ZendTest\Di\TestAsset\InspectedClass', - 'struct', - 'ZendTest\Di\TestAsset\Struct', - ); - $this->assertEquals(count($expected), count($services), var_export($services, 1)); - foreach ($expected as $service) { - $this->assertContains($service, $services); - } - } - - public function testCreatesContainerClassWithMethodsForEachServiceAndAlias() - { - $this->buildContainerClass(); - $tokens = token_get_all(file_get_contents($this->tmpFile)); - $count = count($tokens); - $methods = array(); - for ($i = 0; $i < $count; $i++) { - $token = $tokens[$i]; - if (is_string($token)) { - continue; - } - if ("T_FUNCTION" == token_name($token[0])) { - $value = false; - do { - $i++; - $token = $tokens[$i]; - if (is_string($token)) { - $id = null; - } else { - list($id, $value) = $token; - } - } while (($i < $count) && (token_name($id) != 'T_STRING')); - if ($value) { - $methods[] = $value; - } - } - } - $expected = array( - 'get', - 'getZendTestDiTestAssetComposedClass', - 'getComposed', - 'getZendTestDiTestAssetInspectedClass', - 'getInspect', - 'getZendTestDiTestAssetStruct', - 'getStruct', - ); - $this->assertEquals(count($expected), count($methods), var_export($methods, 1)); - foreach ($expected as $method) { - $this->assertContains($method, $methods); - } - } - - public function testAllowsRetrievingClassFileCodeGenerationObject() - { - $this->createDefinitions(); - $builder = new ContainerBuilder($this->di); - $builder->setContainerClass('Application'); - $codegen = $builder->getCodeGenerator(); - $this->assertInstanceOf('Zend\CodeGenerator\Php\PhpFile', $codegen); - } - - public function testCanSpecifyNamespaceForGeneratedPhpClassfile() - { - $this->createDefinitions(); - $builder = new ContainerBuilder($this->di); - $builder->setContainerClass('Context') - ->setNamespace('Application'); - $codegen = $builder->getCodeGenerator(); - $this->assertEquals('Application', $codegen->getNamespace()); - } - - /** - * @group nullargs - */ - public function testNullAsOnlyArgumentResultsInEmptyParameterList() - { - $def = new Definition('ZendTest\Di\TestAsset\OptionalArg'); - $def->setParamMap(array('param' => 0)) - ->setParam('param', null); - $this->di->setDefinition($def, 'optional'); - - $builder = new ContainerBuilder($this->di); - $builder->setContainerClass('Container'); - $codeGen = $builder->getCodeGenerator(); - $classBody = $codeGen->generate(); - $this->assertNotContains('NULL)', $classBody, $classBody); - } - - /** - * @group nullargs - */ - public function testNullAsLastArgumentsResultsInTruncatedParameterList() - { - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $dummy = new Definition('ZendTest\Di\TestAsset\DummyParams'); - $dummy->setConstructorCallback(array('ZendTest\Di\TestAsset\StaticFactory', 'factory')) - ->setParam('struct', new Reference('struct')) - ->setParam('params', null) - ->setParamMap(array('struct' => 0, 'params' => 1)); - $this->di->setDefinition($struct, 'struct') - ->setDefinition($dummy, 'dummy'); - - $builder = new ContainerBuilder($this->di); - $builder->setContainerClass('Container'); - $codeGen = $builder->getCodeGenerator(); - $classBody = $codeGen->generate(); - $this->assertNotContains('NULL)', $classBody, $classBody); - } - - /** - * @group nullargs - */ - public function testNullArgumentsResultInEmptyMethodParameterList() - { - $def = new Definition('ZendTest\Di\TestAsset\OptionalArg'); - $def->setParamMap(array('param' => 0)) - ->addMethodCall('inject', array(null, null)); - $this->di->setDefinition($def, 'optional'); - - $builder = new ContainerBuilder($this->di); - $builder->setContainerClass('Container'); - $codeGen = $builder->getCodeGenerator(); - $classBody = $codeGen->generate(); - $this->assertNotContains('NULL)', $classBody, $classBody); - } - - public function testClassNamesInstantiatedDirectlyShouldBeFullyQualified() - { - $this->createDefinitions(); - $builder = new ContainerBuilder($this->di); - $builder->setContainerClass('Context') - ->setNamespace('Application'); - $content = $builder->getCodeGenerator()->generate(); - $count = substr_count($content, '\ZendTest\Di\TestAsset\\'); - $this->assertEquals(3, $count, $content); - $this->assertNotContains('\\\\', $content); - } -} diff --git a/tests/Zend/Di/Definition/ArrayDefinitionTest.php b/tests/Zend/Di/Definition/ArrayDefinitionTest.php new file mode 100644 index 00000000000..b95a7015e5d --- /dev/null +++ b/tests/Zend/Di/Definition/ArrayDefinitionTest.php @@ -0,0 +1,13 @@ +assertTrue(true); // placeholder + } +} \ No newline at end of file diff --git a/tests/Zend/Di/Definition/BuilderDefinitionTest.php b/tests/Zend/Di/Definition/BuilderDefinitionTest.php new file mode 100644 index 00000000000..dadeadb1624 --- /dev/null +++ b/tests/Zend/Di/Definition/BuilderDefinitionTest.php @@ -0,0 +1,41 @@ +assertInstanceOf('Zend\Di\Definition', $builder); + } + + public function testBuilderCanBuildClassWithMethods() + { + $class = new Builder\PhpClass(); + $class->setName('Foo'); + $class->addSuperType('Parent'); + + $injectionMethod = new Builder\InjectionMethod(); + $injectionMethod->setName('injectBar'); + $injectionMethod->addParameter('bar', 'Bar'); + + $class->addInjectionMethod($injectionMethod); + + $definition = new BuilderDefinition(); + $definition->addClass($class); + + $this->assertTrue($definition->hasClass('Foo')); + $this->assertEquals('__construct', $definition->getInstantiator('Foo')); + $this->assertContains('Parent', $definition->getClassSupertypes('Foo')); + $this->assertTrue($definition->hasInjectionMethods('Foo')); + $this->assertTrue($definition->hasInjectionMethod('Foo', 'injectBar')); + $this->assertContains('injectBar', $definition->getInjectionMethods('Foo')); + $this->assertEquals(array('bar' => 'Bar'), $definition->getInjectionMethodParameters('Foo', 'injectBar')); + } + +} \ No newline at end of file diff --git a/tests/Zend/Di/Definition/CompilerTest.php b/tests/Zend/Di/Definition/CompilerTest.php new file mode 100644 index 00000000000..97be65d558f --- /dev/null +++ b/tests/Zend/Di/Definition/CompilerTest.php @@ -0,0 +1,13 @@ +assertTrue(true); // placeholder + } +} diff --git a/tests/Zend/Di/Definition/ConfigurationTest.php b/tests/Zend/Di/Definition/ConfigurationTest.php new file mode 100644 index 00000000000..c5bbb92872d --- /dev/null +++ b/tests/Zend/Di/Definition/ConfigurationTest.php @@ -0,0 +1,13 @@ +assertTrue(true); // placeholder + } +} \ No newline at end of file diff --git a/tests/Zend/Di/Definition/RuntimeDefinitionTest.php b/tests/Zend/Di/Definition/RuntimeDefinitionTest.php new file mode 100644 index 00000000000..86378a7af8b --- /dev/null +++ b/tests/Zend/Di/Definition/RuntimeDefinitionTest.php @@ -0,0 +1,13 @@ +assertTrue(true); // placeholder + } +} \ No newline at end of file diff --git a/tests/Zend/Di/DefinitionTest.php b/tests/Zend/Di/DefinitionTest.php deleted file mode 100644 index 19559d3800b..00000000000 --- a/tests/Zend/Di/DefinitionTest.php +++ /dev/null @@ -1,250 +0,0 @@ -definition = new Definition(__CLASS__); - } - - /** - * Stub for testing - */ - public function foo() - { - } - - /** - * Stub for testing - */ - public function bar($message) - { - } - - public function testCanRetrieveConfiguredClassName() - { - $this->assertEquals(__CLASS__, $this->definition->getClass()); - } - - public function testClassNameIsMutable() - { - $this->definition->setClass('foo'); - $this->assertEquals('foo', $this->definition->getClass()); - } - - public function testParamsAreEmptyByDefault() - { - foreach ($this->definition->getParams() as $param) { - $this->assertNull($param); - } - } - - public function testParamsAreReturnedInConstructorOrderWhenNoParamMapProvided() - { - $def = new Definition('ZendTest\Di\TestAsset\InspectedClass'); - $def->setParam('baz', 'BAZ'); - $def->setParam('foo', 'FOO'); - $expected = array( - 'FOO', - 'BAZ', - ); - $this->assertEquals($expected, $def->getParams()); - } - - public function testParamsAreReturnedInParamMapOrderIfSpecified() - { - $def = new Definition('ZendTest\Di\TestAsset\InspectedClass'); - $def->setParam('baz', 'BAZ'); - $def->setParam('foo', 'FOO'); - $def->setParamMap(array( - 'baz' => 0, - 'foo' => 1, - )); - $expected = array( - 'BAZ', - 'FOO', - ); - $this->assertEquals($expected, $def->getParams()); - } - - public function testSpecifyingAParamMultipleTimesOverwrites() - { - $def = new Definition('ZendTest\Di\TestAsset\InspectedClass'); - $def->setParam('baz', 'BAZ'); - $def->setParam('foo', 'FOO'); - $def->setParam('baz', 'baz'); - $def->setParam('foo', 'foo'); - $def->setParamMap(array( - 'baz' => 0, - 'foo' => 1, - )); - $expected = array( - 'baz', - 'foo', - ); - $this->assertEquals($expected, $def->getParams()); - } - - public function testCanSpecifyManyParamsAtOnce() - { - $params = array( - 'foo' => 'FOO', - 'bar' => 'BAR', - ); - $map = array('foo' => 0, 'bar' => 1); - $this->definition->setParams($params) - ->setParamMap($map); - $this->assertEquals(array_values($params), $this->definition->getParams()); - } - - public function testSettingParamMapWithNonNumericPositionsRaisesException() - { - $this->setExpectedException('Zend\Di\Exception\InvalidPositionException'); - $this->definition->setParamMap(array( - 'foo' => 0, - 'bar' => 'bar', - 'baz' => 2, - )); - } - - public function testSettingParamMapWithNonStringNameRaisesException() - { - $this->setExpectedException('Zend\Di\Exception\InvalidParamNameException'); - $this->definition->setParamMap(array( - 'foo' => 0, - 1 => 1, - 'baz' => 2, - )); - } - - public function testSettingParamMapWithInvalidPositionsRaisesException() - { - $this->setExpectedException('Zend\Di\Exception\InvalidPositionException', 'non-sequential'); - $this->definition->setParamMap(array( - 'foo' => 0, - 'bar' => 3, - 'baz' => 2, - )); - } - - public function testSharedByDefault() - { - $this->assertTrue($this->definition->isShared()); - } - - public function testCanOverrideSharedFlag() - { - $this->definition->setShared(false); - $this->assertFalse($this->definition->isShared()); - } - - /** - * @group fml - */ - public function testAddingMethodCallsAggregates() - { - $this->definition->addMethodCall('foo', array()); - $this->definition->addMethodCall('bar', array('bar')); - $methods = $this->definition->getMethodCalls(); - $this->assertInstanceOf('Zend\Di\InjectibleMethods', $methods); - foreach ($methods as $name => $method) { - switch ($name) { - case 'foo': - $this->assertSame(array(), $method->getParams()); - break; - case 'bar': - $this->assertSame(array('bar'), $method->getParams()); - break; - default: - $this->fail('Unexpected method encountered'); - } - } - } - - public function testCanPassInjectibleMethodObjectToAddMethodCall() - { - $definition = new Definition('Foo'); - $method = new Method('bar', array()); - $definition->addMethodCall($method); - $this->assertEquals('Foo', $method->getClass()); - } - - public function testCanPassParameterMapWhenCallingAddMethodCallWithStringMethod() - { - $definition = new Definition('Foo'); - $definition->addMethodCall('bar', array('message' => 'BAR'), array('message' => 0)); - foreach ($definition->getMethodCalls() as $method) { - $this->assertEquals(array('message' => 0), $method->getParamMap()); - } - } - - public function testCanPassParametersAndParameterMapWhenCallingAddMethodCallWithInjectibleMethod() - { - $definition = new Definition('Foo'); - $method = new Method('bar', array()); - $definition->addMethodCall($method, array('message' => 'BAR'), array('message' => 0)); - $this->assertEquals(array('message' => 0), $method->getParamMap()); - $this->assertEquals(array('BAR'), $method->getParams()); - } - - public function testCanSerializeDefinitionToArray() - { - $definition = new Definition('Foo'); - $definition->setParams(array( - 'name' => 'foo', - 'class' => 'Foo', - 'object' => array('__reference' => 'Baz'), - )) - ->setParamMap(array( - 'name' => 1, - 'class' => 0, - 'object' => 2, - )) - ->addMethodCall('bar', array('one', 'two')) - ->addMethodCall('baz', array(array('__reference' => 'Bar'))); - $expected = array( - 'class' => 'Foo', - 'methods' => array( - array('name' => 'bar', 'params' => array('one', 'two')), - array('name' => 'baz', 'params' => array(array('__reference' => 'Bar'))), - ), - 'param_map' => array( - 'name' => 1, - 'class' => 0, - 'object' => 2, - ), - 'params' => array( - 'Foo', - 'foo', - array('__reference' => 'Baz'), - ), - ); - $this->assertEquals($expected, $definition->toArray()); - } - - public function testNoConstructorCallbackByDefault() - { - $this->assertFalse($this->definition->hasConstructorCallback()); - } - - public function testReturnsTrueForHasConstructorCallbackWhenOneProvided() - { - $callback = function () {}; - $this->definition->setConstructorCallback($callback); - $this->assertTrue($this->definition->hasConstructorCallback()); - } - - public function testCanSetConstructorCallback() - { - $callback = function () {}; - $this->definition->setConstructorCallback($callback); - $this->assertSame($callback, $this->definition->getConstructorCallback()); - } -} diff --git a/tests/Zend/Di/DependencyInjectionContainerTest.php b/tests/Zend/Di/DependencyInjectionContainerTest.php deleted file mode 100644 index ea6dea55d53..00000000000 --- a/tests/Zend/Di/DependencyInjectionContainerTest.php +++ /dev/null @@ -1,57 +0,0 @@ -di = new DependencyInjectionContainer(); - } - - public function testComposesDependencyInjectorInstanceByDefault() - { - $this->assertInstanceOf('Zend\Di\DependencyInjector', $this->di->getInjector()); - } - - public function testCanComposeInCustomDiInstance() - { - $di = new DependencyInjector(); - $this->di->setInjector($di); - $this->assertSame($di, $this->di->getInjector()); - } - - public function testGetFallsBacktoInjectorWhenUnableToFindService() - { - $di = $this->di->getInjector(); - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $di->setDefinition($def, 'struct'); - - $test = $this->di->get('struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); - $this->assertEquals('foo', $test->param1); - $this->assertEquals('bar', $test->param2); - } - - public function testRegisteredServicesArePreferredOverInjectorProvidedServices() - { - $di = $this->di->getInjector(); - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $di->setDefinition($def, 'struct'); - - $explicit = new TestAsset\Struct('FOO', 'BAR'); - $this->di->set('struct', $explicit); - - $test = $this->di->get('struct'); - $this->assertSame($explicit, $test); - } -} diff --git a/tests/Zend/Di/DependencyInjectorTest.php b/tests/Zend/Di/DependencyInjectorTest.php index fb43b83b3ac..1737e30d124 100755 --- a/tests/Zend/Di/DependencyInjectorTest.php +++ b/tests/Zend/Di/DependencyInjectorTest.php @@ -1,427 +1,263 @@ di = new DependencyInjector; + $di = new DependencyInjector(); + $this->assertInstanceOf('Zend\Di\InstanceCollection', $di->getInstanceManager()); + $this->assertInstanceOf('Zend\Di\InstanceManager', $di->getInstanceManager()); } - + + public function testDependencyInjectorWillUsePokeYokeRuntimeDefinition() + { + $di = new DependencyInjector(); + $this->assertInstanceOf('Zend\Di\Definition', $di->getDefinition()); + $this->assertInstanceOf('Zend\Di\Definition\RuntimeDefinition', $di->getDefinition()); + } + public function testPassingInvalidDefinitionRaisesException() { - $definitions = array('foo'); + $di = new DependencyInjector(); + $this->setExpectedException('PHPUnit_Framework_Error'); - $this->di->setDefinitions($definitions); + $di->setDefinition(array('foo')); } - + public function testGetRetrievesObjectWithMatchingClassDefinition() { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def); - $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); - $this->assertEquals('foo', $test->param1); - $this->assertEquals('bar', $test->param2); + $di = new DependencyInjector(); + $obj = $di->get('ZendTest\Di\TestAsset\BasicClass'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj); } - + public function testGetRetrievesSameInstanceOnSubsequentCalls() { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def); - $first = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $second = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $first); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $second); - $this->assertSame($first, $second); - } - - public function testGetCanRetrieveByProvidedServiceName() - { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def, 'struct'); - $test = $this->di->get('struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); - $this->assertEquals('foo', $test->param1); - $this->assertEquals('bar', $test->param2); - } - - public function testGetCanRetrieveByClassNameWhenServiceNameIsAlsoProvided() - { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def, 'struct'); - $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); - $this->assertEquals('foo', $test->param1); - $this->assertEquals('bar', $test->param2); + $di = new DependencyInjector(); + $obj1 = $di->get('ZendTest\Di\TestAsset\BasicClass'); + $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClass'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); + $this->assertSame($obj1, $obj2); } - - public function testGetReturnsNewInstanceIfDefinitionSharedFlagIsFalse() + + public function testGetThrowsExceptionWhenUnknownClassIsUsed() { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar') - ->setShared(false); - $this->di->setDefinition($def); - $first = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $second = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $first); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $second); - $this->assertNotSame($first, $second); - } - - public function testNewInstanceForcesNewObjectInstanceEvenWhenSharedFlagIsTrue() - { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar') - ->setShared(true); - $this->di->setDefinition($def); - $first = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $second = $this->di->newInstance('ZendTest\Di\TestAsset\Struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $first); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $second); - $this->assertNotSame($first, $second); - } - - public function testGetNewInstanceByServiceName() - { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def, 'struct'); - $test = $this->di->newInstance('struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); - } - - public function testGetNewInstanceByAlias() - { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def); - $this->di->setAlias('struct', 'ZendTest\Di\TestAsset\Struct'); + $di = new DependencyInjector(); - $test = $this->di->newInstance('struct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + $this->setExpectedException('Zend\Di\Exception\ClassNotFoundException', 'could not be located in'); + $obj1 = $di->get('ZendTest\Di\TestAsset\NonExistentClass'); } - - public function testCanAliasToServiceName() + + public function testGetThrowsExceptionWhenMissingParametersAreEncountered() { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def, 'struct'); - $this->di->setAlias('mystruct', 'struct'); + $di = new DependencyInjector(); - $test = $this->di->newInstance('mystruct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test); + $this->setExpectedException('Zend\Di\Exception\MissingPropertyException', 'Missing parameter named '); + $obj1 = $di->get('ZendTest\Di\TestAsset\BasicClassWithParam'); } - - public function testCanApplyMultipleAliasesPerDefinition() + + public function testNewInstanceReturnsDifferentInstances() { - $def = new Definition('ZendTest\Di\TestAsset\Struct'); - $def->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $this->di->setDefinition($def); - $this->di->setAlias('mystruct', 'ZendTest\Di\TestAsset\Struct'); - $this->di->setAlias('struct', 'ZendTest\Di\TestAsset\Struct'); - - $test1 = $this->di->newInstance('struct'); - $test2 = $this->di->newInstance('mystruct'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test1); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test2); - $this->assertSame($test1, $test2); + $di = new DependencyInjector(); + $obj1 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass'); + $obj2 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); + $this->assertNotSame($obj1, $obj2); } - - public function testGetReturnsNullIfNoMatchingClassOrDefinitionFound() + + public function testNewInstanceReturnsInstanceThatIsSharedWithGet() { - $classes = get_declared_classes(); - $class = array_pop($classes) . uniqid(); - while (in_array($class, $classes)) { - $class .= uniqid(); - } - - $this->assertNull($this->di->get($class)); + $di = new DependencyInjector(); + $obj1 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass'); + $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClass'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); + $this->assertSame($obj1, $obj2); } - - public function testNewInstanceReturnsNullIfNoMatchingClassOrDefinitionFound() + + public function testNewInstanceReturnsInstanceThatIsNotSharedWithGet() { - $classes = get_declared_classes(); - $class = array_pop($classes) . uniqid(); - while (in_array($class, $classes)) { - $class .= uniqid(); - } - - $this->assertNull($this->di->newInstance($class)); + $di = new DependencyInjector(); + $obj1 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass', array(), false); + $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClass'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); + $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); + $this->assertNotSame($obj1, $obj2); } - - public function testUnmatchedReferenceInDefinitionParametersResultsInNullInjection() - { - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setParam('param1', 'foo') - ->setParam('param2', new Reference('voodoo')); - $this->di->setDefinition($struct); - $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $this->assertNull($test->param2); - } - - public function testReferenceInDefinitionParametersCausesInjection() - { - $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setParam('param1', 'foo') - ->setParam('param2', new Reference('ZendTest\Di\TestAsset\ComposedClass')); - $this->di->setDefinition($composed) - ->setDefinition($struct); - - $diStruct = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $diCompose = $this->di->get('ZendTest\Di\TestAsset\ComposedClass'); - $this->assertSame($diCompose, $diStruct->param2); - } - - public function testReferenceToServiceNameInDefinitionParametersCausesInjection() - { - $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setParam('param1', 'foo') - ->setParam('param2', new Reference('composed')); - $this->di->setDefinition($composed, 'composed') - ->setDefinition($struct); - - $diStruct = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $diCompose = $this->di->get('ZendTest\Di\TestAsset\ComposedClass'); - $this->assertSame($diCompose, $diStruct->param2); - } - - public function testCanInjectNestedItems() - { - $inspect = new Definition('ZendTest\Di\TestAsset\InspectedClass'); - $inspect->setParam('foo', new Reference('composed')) - ->setParam('baz', 'BAZ'); - $composed = new Definition('ZendTest\Di\TestAsset\ComposedClass'); - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setParam('param1', 'foo') - ->setParam('param2', new Reference('inspect')); - $this->di->setDefinition($composed, 'composed') - ->setDefinition($inspect, 'inspect') - ->setDefinition($struct, 'struct'); - - $diStruct = $this->di->get('struct'); - $diInspect = $this->di->get('inspect'); - $diCompose = $this->di->get('composed'); - $this->assertSame($diCompose, $diInspect->foo); - $this->assertSame($diInspect, $diStruct->param2); - } - - public function testLastDefinitionOfSameClassNameWins() - { - $struct1 = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct1->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $struct2 = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct2->setParam('param1', 'FOO') - ->setParam('param2', 'BAR'); - $this->di->setDefinition($struct1) - ->setDefinition($struct2); - $test = $this->di->get('ZendTest\Di\TestAsset\Struct'); - $this->assertEquals('FOO', $test->param1); - $this->assertEquals('BAR', $test->param2); - } - - public function testLastDefinitionOfSameClassNameWinsEvenWhenAddedWithDifferentServiceNames() + + /** + * @group ConstructorInjection + */ + public function testGetWillResolveConstructorInjectionDependencies() { - $struct1 = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct1->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $struct2 = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct2->setParam('param1', 'FOO') - ->setParam('param2', 'BAR'); - $this->di->setDefinition($struct1, 'struct1') - ->setDefinition($struct2, 'struct2'); - $test = $this->di->get('struct1'); - $this->assertEquals('FOO', $test->param1); - $this->assertEquals('BAR', $test->param2); + $di = new DependencyInjector(); + $b = $di->get('ZendTest\Di\TestAsset\ConstructorInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b->a); } - - public function testCanInjectSpecificMethods() + + /** + * @group ConstructorInjection + */ + public function testGetWillResolveConstructorInjectionDependenciesAndInstanceAreTheSame() { - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setParam('param1', 'foo') - ->setParam('param2', 'bar'); - $def = new Definition('ZendTest\Di\TestAsset\InjectedMethod'); - $def->addMethodCall('setObject', array(new Reference('struct'))); - $this->di->setDefinition($def) - ->setDefinition($struct, 'struct'); - - $test = $this->di->get('ZendTest\Di\TestAsset\InjectedMethod'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectedMethod', $test); - $this->assertInstanceOf('ZendTest\Di\TestAsset\Struct', $test->object); - $this->assertSame($test->object, $this->di->get('struct')); + $di = new DependencyInjector(); + $b = $di->get('ZendTest\Di\TestAsset\ConstructorInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b->a); + + $b2 = $di->get('ZendTest\Di\TestAsset\ConstructorInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b2); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b2->a); + + $this->assertSame($b, $b2); + $this->assertSame($b->a, $b2->a); } - + /** - * @dataProvider constructorCallbacks + * @group ConstructorInjection */ - public function testUsesConstructorCallbackIfDefinedInDefinition($callback) + public function testNewInstanceWillResolveConstructorInjectionDependencies() { - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setConstructorCallback($callback); - $struct->setParam('params', array('foo' => 'bar')); - $struct->setParamMap(array('params' => 0)); - $this->di->setDefinition($struct, 'struct'); - $test = $this->di->get('struct'); - $this->assertInstanceOf('stdClass', $test); - $this->assertNotInstanceOf('ZendTest\Di\TestAsset\Struct', $test); - $this->assertEquals('bar', $test->foo); + $di = new DependencyInjector(); + $b = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b->a); } - - public function constructorCallbacks() + + /** + * @group ConstructorInjection + */ + public function testNewInstanceWillResolveConstructorInjectionDependenciesWithProperties() { - return array( - array(__CLASS__ . '::structFactory'), - array(array($this, 'structFactory')), - array(function (array $params) { - $o = (object) $params; - return $o; - }), - ); + $di = new DependencyInjector(); + + $im = $di->getInstanceManager(); + $im->setProperty('ZendTest\Di\TestAsset\ConstructorInjection\X', 'one', 1); + $im->setProperty('ZendTest\Di\TestAsset\ConstructorInjection\X', 'two', 2); + + $y = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\Y'); + $this->assertEquals(1, $y->x->one); + $this->assertEquals(2, $y->x->two); } - - public static function structFactory(array $params) + + /** + * @group ConstructorInjection + */ + public function testNewInstanceWillThrowExceptionOnConstructorInjectionDependencyWithMissingParameter() { - $o = (object) $params; - return $o; + $di = new DependencyInjector(); + + $this->setExpectedException('Zend\Di\Exception\MissingPropertyException', 'Missing parameter named one'); + $b = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\X'); } - - public function testRaisesExceptionForInvalidConstructorCallback() + + /** + * @group ConstructorInjection + */ + public function testNewInstanceWillResolveConstructorInjectionDependenciesWithMoreThan2Dependencies() { - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setConstructorCallback(array('foo' => 'bar')); - $struct->setParam('params', array('foo' => 'bar')); - $struct->setParamMap(array('params' => 0)); - $this->di->setDefinition($struct, 'struct'); - - $this->setExpectedException('Zend\Di\Exception\InvalidCallbackException'); - $test = $this->di->get('struct'); + $di = new DependencyInjector(); + + $im = $di->getInstanceManager(); + $im->setProperty('ZendTest\Di\TestAsset\ConstructorInjection\X', 'one', 1); + $im->setProperty('ZendTest\Di\TestAsset\ConstructorInjection\X', 'two', 2); + + $z = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\Z'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\Y', $z->y); + $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\X', $z->y->x); } - - public function testConstructorCallbackAllowsPassingReferences() + + /** + * @group SetterInjection + */ + public function testGetWillResolveSetterInjectionDependencies() { - $struct = new Definition('ZendTest\Di\TestAsset\Struct'); - $struct->setConstructorCallback(function ($params) { - $o = new \stdClass; - $o->params = $params; - return $o; - }); - $struct->setParam('params', new Reference('params')); - $struct->setParamMap(array('params' => 0)); - $this->di->setDefinition($struct, 'struct'); - - $params = new Definition('ZendTest\Di\TestAsset\DummyParams'); - $params->setParam('params', array('bar' => 'baz')) - ->setParamMap(array('params' => 0)); - $this->di->setDefinition($params, 'params'); - - $testStruct = $this->di->get('struct'); - $testParams = $this->di->get('params'); - $this->assertSame($testParams, $testStruct->params, sprintf('Params: %s; struct: %s', var_export($testParams, 1), var_export($testStruct, 1))); + $di = new DependencyInjector(); + $b = $di->get('ZendTest\Di\TestAsset\SetterInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); } - + /** - * Test for Circular Dependencies (case 1) - * - * A->B, B->A + * @group SetterInjection */ - public function testCircularDependencies1() + public function testGetWillResolveSetterInjectionDependenciesAndInstanceAreTheSame() { - $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); - $classA->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); - $this->di->setDefinition($classA, 'A'); + $di = new DependencyInjector(); + $b = $di->get('ZendTest\Di\TestAsset\SetterInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); - $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); - $classB->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassA')); - $this->setExpectedException('Zend\Di\Exception\RuntimeException'); - $this->di->setDefinition($classB, 'B'); + $b2 = $di->get('ZendTest\Di\TestAsset\SetterInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b2); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b2->a); + + $this->assertSame($b, $b2); + $this->assertSame($b->a, $b2->a); } + /** - * Test for Circular Dependencies (case 2) - * - * A->B, B->C, C->A + * @group SetterInjection */ - public function testCircularDependencies2() + public function testNewInstanceWillResolveSetterInjectionDependencies() { - $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); - $classA->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); - $this->di->setDefinition($classA, 'A'); - - $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); - $classB->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassC')); - $this->di->setDefinition($classB, 'B'); - - $classC= new Definition('ZendTest\Di\TestAsset\ClassC'); - $classC->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassA')); - $this->setExpectedException('Zend\Di\Exception\RuntimeException'); - $this->di->setDefinition($classC, 'C'); + $di = new DependencyInjector(); + $b = $di->newInstance('ZendTest\Di\TestAsset\SetterInjection\B'); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b); + $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); } + /** - * Test for Circular Dependencies (case 3) - * - * A->B, B->C, C->D, D->B + * @group SetterInjection */ - public function testCircularDependencies3() + public function testNewInstanceWillResolveSetterInjectionDependenciesWithProperties() { - $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); - $classA->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); - $this->di->setDefinition($classA, 'A'); + $di = new DependencyInjector(); - $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); - $classB->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassC')); - $this->di->setDefinition($classB, 'B'); + $im = $di->getInstanceManager(); + $im->setProperty('ZendTest\Di\TestAsset\SetterInjection\X', 'one', 1); + $im->setProperty('ZendTest\Di\TestAsset\SetterInjection\X', 'two', 2); - $classC= new Definition('ZendTest\Di\TestAsset\ClassC'); - $classC->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassD')); - $this->di->setDefinition($classC, 'C'); - - $classD= new Definition('ZendTest\Di\TestAsset\ClassD'); - $classD->setParam('param', new Reference('ZendTest\Di\TestAsset\ClassB')); - $this->setExpectedException('Zend\Di\Exception\RuntimeException'); - $this->di->setDefinition($classD, 'D'); + $y = $di->newInstance('ZendTest\Di\TestAsset\SetterInjection\Y'); + $this->assertEquals(1, $y->x->one); + $this->assertEquals(2, $y->x->two); } + /** - * Test for NO Circular Dependencies + * Test for Circular Dependencies (case 1) + * + * A->B, B->A */ - public function testNoCircularDependencies() + public function testNewInstanceThrowsExceptionOnBasicCircularDependency() { - $classA= new Definition('ZendTest\Di\TestAsset\ClassA'); - $this->di->setDefinition($classA, 'A'); - - $classB= new Definition('ZendTest\Di\TestAsset\ClassB'); - $this->di->setDefinition($classB, 'B'); + $di = new DependencyInjector(); - $a= $this->di->get('A'); - $b= $this->di->get('B'); - - $a->setParam($b); - $b->setParam($a); - $this->assertEquals($a->getParam(),$b); - $this->assertEquals($b->getParam(),$a); + $this->setExpectedException('Zend\Di\Exception\CircularDependencyException'); + $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\A'); } + /** - * @todo tests for recursive DI calls + * Test for Circular Dependencies (case 2) + * + * C->D, D->E, E->C */ + public function testNewInstanceThrowsExceptionOnThreeLevelCircularDependency() + { + $this->markTestSkipped('This currently does not work'); + $di = new DependencyInjector(); + + $this->setExpectedException( + 'Zend\Di\Exception\CircularDependencyException', + 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\B depends on ZendTest\Di\TestAsset\CircularClasses\A and viceversa' + ); + $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\C'); + } + } diff --git a/tests/Zend/Di/InstanceManagerTest.php b/tests/Zend/Di/InstanceManagerTest.php new file mode 100644 index 00000000000..d0ce02ecfdc --- /dev/null +++ b/tests/Zend/Di/InstanceManagerTest.php @@ -0,0 +1,34 @@ +assertInstanceOf('Zend\Di\InstanceCollection', $im); + } + + public function testInstanceManagerCanPersistInstances() + { + $im = new InstanceManager(); + $obj = new TestAsset\BasicClass(); + $im->addSharedInstance($obj, 'ZendTest\Di\TestAsset\BasicClass'); + $this->assertTrue($im->hasSharedInstance('ZendTest\Di\TestAsset\BasicClass')); + $this->assertSame($obj, $im->getSharedInstance('ZendTest\Di\TestAsset\BasicClass')); + } + + public function testInstanceManagerCanPersistProperties() + { + $im = new InstanceManager(); + $im->setProperty('ZendTest\Di\TestAsset\BasicClass', 'foo', 'bar'); + $this->assertTrue($im->hasProperties('ZendTest\Di\TestAsset\BasicClass')); + $this->assertTrue($im->hasProperty('ZendTest\Di\TestAsset\BasicClass', 'foo')); + $this->assertEquals('bar', $im->getProperty('ZendTest\Di\TestAsset\BasicClass', 'foo')); + } + +} \ No newline at end of file diff --git a/tests/Zend/Di/MethodTest.php b/tests/Zend/Di/MethodTest.php deleted file mode 100644 index 3a2cd9f6211..00000000000 --- a/tests/Zend/Di/MethodTest.php +++ /dev/null @@ -1,94 +0,0 @@ -assertEquals($name, $method->getName()); - } - - public function testMethodReturnsParamsPassedToConstructor() - { - $name = uniqid(); - $params = array( - 'foo', - new \stdClass, - true, - null, - 0, - 0.00, - array('bar'), - ); - $method = new Method($name, $params); - $this->assertEquals($params, $method->getParams()); - } - - public function testNoClassSetByDefault() - { - $name = uniqid(); - $method = new Method($name, array()); - $this->assertNull($method->getClass()); - } - - public function testClassIsMutable() - { - $name = uniqid(); - $method = new Method($name, array()); - $method->setClass(__CLASS__); - $this->assertEquals(__CLASS__, $method->getClass()); - } - - public function testParamsAreReturnedInOrderProvidedWhenNoParamMapGivenAndNoClassProvided() - { - $name = uniqid(); - $method = new Method($name, array('bar' => 'BAR', 'foo' => 'FOO')); - - $params = $method->getParams(); - $this->assertEquals(array('BAR', 'FOO'), $params); - } - - public function testParamsAreReturnedInArgOrderWhenNoParamMapGivenAndClassIsProvided() - { - $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO')); - $method->setClass($this); - $params = $method->getParams(); - $this->assertEquals(array('FOO', 'BAR'), $params); - } - - public function testParamsAreReturnedInParamMapOrderWhenNoClassProvided() - { - $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO'), array('foo' => 0, 'bar' => 1)); - $params = $method->getParams(); - $this->assertEquals(array('FOO', 'BAR'), $params); - } - - public function testParamsAreReturnedInParamMapOrderWhenClassProvided() - { - $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO'), array('bar' => 0, 'foo' => 1)); - $method->setClass($this); - $params = $method->getParams(); - $this->assertEquals(array('BAR', 'FOO'), $params); - } - - public function testParamMapIsMutable() - { - $map = array('bar' => 0, 'foo' => 1); - $method = new Method('methodWithArgs', array('bar' => 'BAR', 'foo' => 'FOO'), $map); - $this->assertEquals($map, $method->getParamMap()); - - $map = array('foo' => 0, 'bar' => 1); - $method->setParamMap($map); - $this->assertEquals($map, $method->getParamMap()); - } - - public function methodWithArgs($foo, $bar) - { - } -} diff --git a/tests/Zend/Di/MethodsTest.php b/tests/Zend/Di/MethodsTest.php deleted file mode 100644 index 0a202e0e0f8..00000000000 --- a/tests/Zend/Di/MethodsTest.php +++ /dev/null @@ -1,61 +0,0 @@ -methods = new Methods(); - } - - public function testInsertAcceptsMethodObject() - { - $this->methods->insert(new Method('foo', array())); - // No test; simply ensuring no exceptions/errors - } - - public function invalidMethodArguments() - { - return array( - array(null), - array(true), - array(false), - array(1), - array(1.0), - array(array()), - array(new \stdClass()), - ); - } - - /** - * @dataProvider invalidMethodArguments - */ - public function testInsertRaisesExceptionIfInvalidArgumentProvidedForMethodObject($arg) - { - $this->setExpectedException('PHPUnit_Framework_Error'); - $this->methods->insert($arg); - } - - public function testIterationReturnsMethodNameForKey() - { - $method = new Method('foo', array()); - $this->methods->insert($method); - foreach ($this->methods as $key => $value) { - } - $this->assertEquals('foo', $key); - } - - public function testIterationReturnsMethodObjectForValue() - { - $method = new Method('foo', array()); - $this->methods->insert($method); - foreach ($this->methods as $key => $value) { - } - $this->assertEquals($method, $value); - } -} diff --git a/tests/Zend/Di/ReferenceTest.php b/tests/Zend/Di/ReferenceTest.php deleted file mode 100644 index 3f45d64b22e..00000000000 --- a/tests/Zend/Di/ReferenceTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertEquals($name, $ref->getServiceName()); - } -} diff --git a/tests/Zend/Di/ServiceLocatorTest.php b/tests/Zend/Di/ServiceLocatorTest.php deleted file mode 100644 index 8631c766d27..00000000000 --- a/tests/Zend/Di/ServiceLocatorTest.php +++ /dev/null @@ -1,102 +0,0 @@ -services = new ServiceLocator(); - } - - public function testRetrievingUnknownServiceResultsInNullValue() - { - $this->assertNull($this->services->get('foo')); - } - - public function testCanRetrievePreviouslyRegisteredServices() - { - $s = new \stdClass; - $this->services->set('foo', $s); - $test = $this->services->get('foo'); - $this->assertSame($s, $test); - } - - public function testRegisteringAServiceUnderAnExistingNameOverwrites() - { - $s = new \stdClass(); - $t = new \stdClass(); - $this->services->set('foo', $s); - $this->services->set('foo', $t); - $test = $this->services->get('foo'); - $this->assertSame($t, $test); - } - - public function testRetrievingAServiceMultipleTimesReturnsSameInstance() - { - $s = new \stdClass(); - $this->services->set('foo', $s); - $test1 = $this->services->get('foo'); - $test2 = $this->services->get('foo'); - $this->assertSame($s, $test1); - $this->assertSame($s, $test2); - $this->assertSame($test1, $test2); - } - - public function testRegisteringCallbacksReturnsReturnValueWhenServiceRequested() - { - $this->services->set('foo', function() { - $object = new \stdClass(); - $object->foo = 'FOO'; - return $object; - }); - $test = $this->services->get('foo'); - $this->assertInstanceOf('stdClass', $test); - $this->assertEquals('FOO', $test->foo); - } - - public function testReturnValueOfCallbackIsCachedBetweenRequestsToService() - { - $this->services->set('foo', function() { - $object = new \stdClass(); - $object->foo = 'FOO'; - return $object; - }); - $test1 = $this->services->get('foo'); - $test2 = $this->services->get('foo'); - $this->assertEquals('FOO', $test1->foo); - $this->assertSame($test1, $test2); - } - - public function testParametersArePassedToCallbacks() - { - $this->services->set('foo', function() { - $object = new \stdClass(); - $object->params = func_get_args(); - return $object; - }); - - $params = array('foo', 'bar'); - $test = $this->services->get('foo', $params); - $this->assertEquals($params, $test->params); - } - - public function testGetProxiesToMappedMethods() - { - $sc = new TestAsset\ContainerExtension(); - $sc->foo = 'FOO'; - $this->assertEquals('FOO', $sc->get('foo')); - } - - public function testProxiedMethodsReceiveParametersPassedToGet() - { - $sc = new TestAsset\ContainerExtension(); - $params = array('foo' => 'FOO'); - $test = $sc->get('params', $params); - $this->assertEquals($params, $test); - $this->assertEquals($params, $sc->params); - } -} diff --git a/tests/Zend/Di/TestAsset/ComposedClass.php b/tests/Zend/Di/TestAsset/BasicClass.php similarity index 63% rename from tests/Zend/Di/TestAsset/ComposedClass.php rename to tests/Zend/Di/TestAsset/BasicClass.php index f0d97aadfbb..348e30fe534 100644 --- a/tests/Zend/Di/TestAsset/ComposedClass.php +++ b/tests/Zend/Di/TestAsset/BasicClass.php @@ -1,6 +1,8 @@ param = $param; - } - public function getParam() - { - return $this->param; - } - public function setParam($param) { - $this->param= $param; - } -} diff --git a/tests/Zend/Di/TestAsset/ClassB.php b/tests/Zend/Di/TestAsset/ClassB.php deleted file mode 100755 index 5789b09b40f..00000000000 --- a/tests/Zend/Di/TestAsset/ClassB.php +++ /dev/null @@ -1,20 +0,0 @@ -param = $param; - } - public function getParam() - { - return $this->param; - } - public function setParam($param) - { - $this->param= $param; - } -} diff --git a/tests/Zend/Di/TestAsset/ClassC.php b/tests/Zend/Di/TestAsset/ClassC.php deleted file mode 100755 index 493a365acbe..00000000000 --- a/tests/Zend/Di/TestAsset/ClassC.php +++ /dev/null @@ -1,20 +0,0 @@ -param = $param; - } - public function getParam() - { - return $this->param; - } - public function setParam($param) - { - $this->param= $param; - } -} diff --git a/tests/Zend/Di/TestAsset/ClassD.php b/tests/Zend/Di/TestAsset/ClassD.php deleted file mode 100755 index d05a79aff08..00000000000 --- a/tests/Zend/Di/TestAsset/ClassD.php +++ /dev/null @@ -1,20 +0,0 @@ -param = $param; - } - public function getParam() - { - return $this->param; - } - public function setParam($param) - { - $this->param= $param; - } -} diff --git a/tests/Zend/Di/TestAsset/ConstructorInjection/A.php b/tests/Zend/Di/TestAsset/ConstructorInjection/A.php new file mode 100644 index 00000000000..fb8043f9daf --- /dev/null +++ b/tests/Zend/Di/TestAsset/ConstructorInjection/A.php @@ -0,0 +1,7 @@ +a = $a; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/ConstructorInjection/X.php b/tests/Zend/Di/TestAsset/ConstructorInjection/X.php new file mode 100644 index 00000000000..53a7e87f9d0 --- /dev/null +++ b/tests/Zend/Di/TestAsset/ConstructorInjection/X.php @@ -0,0 +1,14 @@ +one = $one; + $this->two = $two; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/ConstructorInjection/Y.php b/tests/Zend/Di/TestAsset/ConstructorInjection/Y.php new file mode 100644 index 00000000000..7c2cef45761 --- /dev/null +++ b/tests/Zend/Di/TestAsset/ConstructorInjection/Y.php @@ -0,0 +1,12 @@ +x = $x; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/ConstructorInjection/Z.php b/tests/Zend/Di/TestAsset/ConstructorInjection/Z.php new file mode 100644 index 00000000000..2fb9d5771da --- /dev/null +++ b/tests/Zend/Di/TestAsset/ConstructorInjection/Z.php @@ -0,0 +1,12 @@ +y = $y; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/ContainerExtension.php b/tests/Zend/Di/TestAsset/ContainerExtension.php deleted file mode 100644 index e443bfaf74b..00000000000 --- a/tests/Zend/Di/TestAsset/ContainerExtension.php +++ /dev/null @@ -1,26 +0,0 @@ - 'getFoo', - 'params' => 'getParams', - ); - - public function getFoo() - { - return $this->foo; - } - - public function getParams(array $params) - { - $this->params = $params; - return $this->params; - } -} diff --git a/tests/Zend/Di/TestAsset/DummyParams.php b/tests/Zend/Di/TestAsset/DummyParams.php deleted file mode 100644 index 362cd6cc87d..00000000000 --- a/tests/Zend/Di/TestAsset/DummyParams.php +++ /dev/null @@ -1,12 +0,0 @@ -params = $params; - } -} diff --git a/tests/Zend/Di/TestAsset/InjectedMethod.php b/tests/Zend/Di/TestAsset/InjectedMethod.php deleted file mode 100644 index 60a1c6e6d0e..00000000000 --- a/tests/Zend/Di/TestAsset/InjectedMethod.php +++ /dev/null @@ -1,10 +0,0 @@ -object = $o; - } -} diff --git a/tests/Zend/Di/TestAsset/InspectedClass.php b/tests/Zend/Di/TestAsset/InspectedClass.php deleted file mode 100644 index c00d5e94000..00000000000 --- a/tests/Zend/Di/TestAsset/InspectedClass.php +++ /dev/null @@ -1,11 +0,0 @@ -foo = $foo; - $this->baz = $baz; - } -} diff --git a/tests/Zend/Di/TestAsset/OptionalArg.php b/tests/Zend/Di/TestAsset/OptionalArg.php deleted file mode 100644 index 35beb2451bc..00000000000 --- a/tests/Zend/Di/TestAsset/OptionalArg.php +++ /dev/null @@ -1,15 +0,0 @@ -param = $param; - } - - public function inject($param1 = null, $param2 = null) - { - } -} - diff --git a/tests/Zend/Di/TestAsset/SetterInjection/A.php b/tests/Zend/Di/TestAsset/SetterInjection/A.php new file mode 100644 index 00000000000..983ef1a69e4 --- /dev/null +++ b/tests/Zend/Di/TestAsset/SetterInjection/A.php @@ -0,0 +1,7 @@ +a = $a; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/SetterInjection/X.php b/tests/Zend/Di/TestAsset/SetterInjection/X.php new file mode 100644 index 00000000000..25566ffd1e9 --- /dev/null +++ b/tests/Zend/Di/TestAsset/SetterInjection/X.php @@ -0,0 +1,17 @@ +one = $one; + } + public function setTwo($two) + { + $this->two = $two; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/SetterInjection/Y.php b/tests/Zend/Di/TestAsset/SetterInjection/Y.php new file mode 100644 index 00000000000..bbc6b99265d --- /dev/null +++ b/tests/Zend/Di/TestAsset/SetterInjection/Y.php @@ -0,0 +1,12 @@ +x = $x; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/SetterInjection/Z.php b/tests/Zend/Di/TestAsset/SetterInjection/Z.php new file mode 100644 index 00000000000..eb35dfe83fe --- /dev/null +++ b/tests/Zend/Di/TestAsset/SetterInjection/Z.php @@ -0,0 +1,12 @@ +y = $y; + } +} \ No newline at end of file diff --git a/tests/Zend/Di/TestAsset/StaticFactory.php b/tests/Zend/Di/TestAsset/StaticFactory.php deleted file mode 100644 index b2af52c44a3..00000000000 --- a/tests/Zend/Di/TestAsset/StaticFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -param1 = $param1; - $this->param2 = $param2; - } -} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 8dd2376962c..2f1c8270ac5 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -13,7 +13,7 @@ - ../library/ + From 29f38d067021f06ca00d05892901fd93de96a9af Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Wed, 8 Jun 2011 16:48:30 -0500 Subject: [PATCH 36/45] Improved tests for Zend\Di\Definition\Compiler --- .../Zend/Di/Definition/ArrayDefinition.php | 4 +- tests/Zend/Di/Definition/CompilerTest.php | 38 +++++++++++++++++-- tests/Zend/Di/TestAsset/CompilerClasses/A.php | 7 ++++ tests/Zend/Di/TestAsset/CompilerClasses/B.php | 11 ++++++ tests/Zend/Di/TestAsset/CompilerClasses/C.php | 11 ++++++ tests/Zend/Di/TestAsset/CompilerClasses/D.php | 8 ++++ 6 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 tests/Zend/Di/TestAsset/CompilerClasses/A.php create mode 100644 tests/Zend/Di/TestAsset/CompilerClasses/B.php create mode 100644 tests/Zend/Di/TestAsset/CompilerClasses/C.php create mode 100644 tests/Zend/Di/TestAsset/CompilerClasses/D.php diff --git a/library/Zend/Di/Definition/ArrayDefinition.php b/library/Zend/Di/Definition/ArrayDefinition.php index 04353fa7af5..6f2afb5b1c5 100644 --- a/library/Zend/Di/Definition/ArrayDefinition.php +++ b/library/Zend/Di/Definition/ArrayDefinition.php @@ -40,11 +40,11 @@ public function getClassSupertypes($class) public function getInstantiator($class) { if (!isset($this->dataArray[$class])) { - return array(); + return null; } if (!isset($this->dataArray[$class]['instantiator'])) { - return array(); + return '__construct'; } return $this->dataArray[$class]['instantiator']; diff --git a/tests/Zend/Di/Definition/CompilerTest.php b/tests/Zend/Di/Definition/CompilerTest.php index 97be65d558f..126dca52c2e 100644 --- a/tests/Zend/Di/Definition/CompilerTest.php +++ b/tests/Zend/Di/Definition/CompilerTest.php @@ -2,12 +2,44 @@ namespace ZendTest\Di\Definition; -use Zend\Di\Definition\Compiler; +use Zend\Di\Definition\Compiler, + Zend\Code\Scanner\ScannerDirectory; class CompilerTest extends \PHPUnit_Framework_TestCase { - public function testTrue() + + + + public function testCompilerCompilesAgainstConstructorInjectionAssets() { - $this->assertTrue(true); // placeholder + $compiler = new Compiler; + $compiler->addCodeScannerDirectory(new ScannerDirectory(__DIR__ . '/../TestAsset/CompilerClasses')); + $definition = $compiler->compile(); + $this->assertInstanceOf('Zend\Di\Definition\ArrayDefinition', $definition); + + $this->assertTrue($definition->hasClass('ZendTest\Di\TestAsset\CompilerClasses\A')); + + $assertClasses = array( + 'ZendTest\Di\TestAsset\CompilerClasses\A', + 'ZendTest\Di\TestAsset\CompilerClasses\B', + 'ZendTest\Di\TestAsset\CompilerClasses\C', + 'ZendTest\Di\TestAsset\CompilerClasses\D' + ); + $classes = $definition->getClasses(); + foreach ($assertClasses as $assertClass) { + $this->assertContains($assertClass, $classes); + } + + // @todo this needs to be resolved, not the short name + // $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\D')); + + $this->assertEquals('__construct', $definition->getInstantiator('ZendTest\Di\TestAsset\CompilerClasses\A')); + $this->assertTrue($definition->hasInjectionMethods('ZendTest\Di\TestAsset\CompilerClasses\C')); + + + $this->assertContains('setB', $definition->getInjectionMethods('ZendTest\Di\TestAsset\CompilerClasses\C')); + $this->assertTrue($definition->hasInjectionMethod('ZendTest\Di\TestAsset\CompilerClasses\C', 'setB')); + + $this->assertEquals(array('b' => 'ZendTest\Di\TestAsset\CompilerClasses\B'), $definition->getInjectionMethodParameters('ZendTest\Di\TestAsset\CompilerClasses\C', 'setB')); } } diff --git a/tests/Zend/Di/TestAsset/CompilerClasses/A.php b/tests/Zend/Di/TestAsset/CompilerClasses/A.php new file mode 100644 index 00000000000..7aa064b3f11 --- /dev/null +++ b/tests/Zend/Di/TestAsset/CompilerClasses/A.php @@ -0,0 +1,7 @@ + Date: Wed, 8 Jun 2011 14:27:09 -0500 Subject: [PATCH 37/45] Coding standards review - Created base Code\Exception type and hierarchy - Updated code to throw component exceptions instead of SPL - Minor formatting tweaks to conform with CS recommendations --- library/Zend/Code/Exception.php | 7 ++ .../Exception/InvalidArgumentException.php | 11 ++ .../Zend/Code/Exception/RuntimeException.php | 9 ++ library/Zend/Code/Scanner.php | 9 ++ library/Zend/Code/Scanner/ScannerClass.php | 98 +++++++++------- .../Zend/Code/Scanner/ScannerDirectory.php | 20 ++-- library/Zend/Code/Scanner/ScannerFile.php | 13 ++- library/Zend/Code/Scanner/ScannerMethod.php | 72 ++++++------ .../Zend/Code/Scanner/ScannerParameter.php | 58 ++++++---- .../Zend/Code/Scanner/ScannerTokenArray.php | 109 +++++++++++------- 10 files changed, 253 insertions(+), 153 deletions(-) create mode 100644 library/Zend/Code/Exception.php create mode 100644 library/Zend/Code/Exception/InvalidArgumentException.php create mode 100644 library/Zend/Code/Exception/RuntimeException.php create mode 100644 library/Zend/Code/Scanner.php diff --git a/library/Zend/Code/Exception.php b/library/Zend/Code/Exception.php new file mode 100644 index 00000000000..cc63ecebe2e --- /dev/null +++ b/library/Zend/Code/Exception.php @@ -0,0 +1,7 @@ +tokens = $classTokens; + $this->tokens = $classTokens; $this->namespace = $namespace; - $this->uses = $uses; + $this->uses = $uses; } protected function scan() { if (!$this->tokens) { - throw new \RuntimeException('No tokens were provided'); + throw new Exception\RuntimeException('No tokens were provided'); } for ($tokenIndex = 0; $tokenIndex < count($this->tokens); $tokenIndex++) { @@ -54,6 +57,7 @@ protected function scan() case T_CONST: $this->scanConstant($tokenIndex, $fastForward); break; + case T_FINAL: case T_ABSTRACT: if (!$this->name) { @@ -68,7 +72,9 @@ protected function scan() $subTokenIndex = $tokenIndex; do { $subToken = $this->tokens[$subTokenIndex++]; - } while (!(is_array($subToken) && $subToken[0] == T_FUNCTION) && !(is_string($subToken) && $subToken == '=')); + } while (!(is_array($subToken) && $subToken[0] == T_FUNCTION) + && !(is_string($subToken) && $subToken == '=') + ); if (is_array($subToken)) { $this->scanMethod($tokenIndex, $fastForward); @@ -93,17 +99,19 @@ protected function scanClassInfo($tokenIndex, &$fastForward) $tokenTwoBack = $this->tokens[$tokenIndex-2]; } - // T_ABSTRACT & T_FINAL will have been bypassed if no class name, and will alwasy be 2 tokens behind T_CLASS + // T_ABSTRACT & T_FINAL will have been bypassed if no class name, and + // will alwasy be 2 tokens behind T_CLASS $this->isAbstract = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_ABSTRACT)); - $this->isFinal = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_FINAL)); + $this->isFinal = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_FINAL)); $this->isInterface = (is_array($this->tokens[$tokenIndex]) && $this->tokens[$tokenIndex][0] == T_INTERFACE); - $this->shortName = $this->tokens[$tokenIndex+2][1]; - $this->name = (($this->namespace) ? $this->namespace . '\\' : '') . $this->shortName; + $this->shortName = $this->tokens[$tokenIndex+2][1]; + $this->name = (($this->namespace) ? $this->namespace . '\\' : '') . $this->shortName; - $context = null; + $context = null; $interfaceIndex = 0; + while (true) { $fastForward++; $tokenIndex++; @@ -142,7 +150,8 @@ protected function scanClassInfo($tokenIndex, &$fastForward) // create function to resolve short names with uses $namespace = $this->namespace; - $uses = $this->uses; + $uses = $this->uses; + $resolveUseFunc = function (&$value, $key = null) use (&$namespace, &$uses) { if (!$uses || strlen($value) <= 0 || $value{0} == '\\') { $value = ltrim($value, '\\'); @@ -151,7 +160,7 @@ protected function scanClassInfo($tokenIndex, &$fastForward) if ($namespace || $uses) { $firstPartEnd = (strpos($value, '\\')) ?: strlen($value-1); - $firstPart = substr($value, 0, $firstPartEnd); + $firstPart = substr($value, 0, $firstPartEnd); if (array_key_exists($firstPart, $uses)) { $value = substr_replace($value, $uses[$firstPart], 0, $firstPartEnd); return; @@ -184,8 +193,8 @@ protected function scanConstant($tokenIndex, &$fastForward) 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'name' => null, - 'value' => null - ); + 'value' => null, + ); while (true) { $fastForward++; @@ -197,7 +206,9 @@ protected function scanConstant($tokenIndex, &$fastForward) break; } - if ((is_array($token) && $token[0] == T_WHITESPACE) || (is_string($token) && $token == '=')) { + if ((is_array($token) && $token[0] == T_WHITESPACE) + || (is_string($token) && $token == '=') + ) { continue; } @@ -221,8 +232,8 @@ protected function scanMethod($tokenIndex, &$fastForward) 'tokenEnd' => null, 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, - 'name' => null - ); + 'name' => null, + ); $braceCount = 0; while (true) { @@ -258,7 +269,7 @@ protected function scanMethod($tokenIndex, &$fastForward) } $info['tokenEnd'] = $tokenIndex; - $this->infos[] = $info; + $this->infos[] = $info; } protected function scanProperty($tokenIndex, &$fastForward) @@ -269,8 +280,8 @@ protected function scanProperty($tokenIndex, &$fastForward) 'tokenEnd' => null, 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, - 'name' => null - ); + 'name' => null, + ); $index = $tokenIndex; @@ -297,7 +308,7 @@ protected function scanProperty($tokenIndex, &$fastForward) } $info['tokenEnd'] = $index; - $this->infos[] = $info; + $this->infos[] = $info; } public function getName() @@ -407,21 +418,26 @@ public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Co $this->scan(); // process the class requested + // Static for performance reasons static $baseScannerClass = 'Zend\Code\Scanner\ScannerMethod'; if ($returnScannerClass !== $baseScannerClass) { if (!is_string($returnScannerClass)) { $returnScannerClass = $baseScannerClass; } $returnScannerClass = ltrim($returnScannerClass, '\\'); - if ($returnScannerClass !== $baseScannerClass && !is_subclass_of($returnScannerClass, $baseScannerClass)) { - throw new \RuntimeException('Class must be or extend ' . $baseScannerClass); + if ($returnScannerClass !== $baseScannerClass + && !is_subclass_of($returnScannerClass, $baseScannerClass) + ) { + throw new Exception\RuntimeException(sprintf( + 'Class must be or extend "%s"', $baseScannerClass + )); } } if (is_int($methodNameOrInfoIndex)) { $info = $this->infos[$methodNameOrInfoIndex]; if ($info['type'] != 'method') { - throw new \InvalidArgumentException('Index of info offset is not about a method'); + throw new Exception\InvalidArgumentException('Index of info offset is not about a method'); } } elseif (is_string($methodNameOrInfoIndex)) { $methodFound = false; diff --git a/library/Zend/Code/Scanner/ScannerDirectory.php b/library/Zend/Code/Scanner/ScannerDirectory.php index 89a196b1c09..a4d82e76739 100644 --- a/library/Zend/Code/Scanner/ScannerDirectory.php +++ b/library/Zend/Code/Scanner/ScannerDirectory.php @@ -2,12 +2,16 @@ namespace Zend\Code\Scanner; -class ScannerDirectory implements ScannerInterface +use Zend\Code\Scanner, + Zend\Code\Exception, + RecursiveDirectoryIterator; + +class ScannerDirectory implements Scanner { - protected $isScanned = false; - protected $directories = array(); + protected $isScanned = false; + protected $directories = array(); protected $fileScannerFileClass = 'Zend\Code\Scanner\ScannerFile'; - protected $scannerFiles = array(); + protected $scannerFiles = array(); public function __construct($directory = null) { @@ -33,7 +37,9 @@ public function addDirectory($directory) { $realDir = realpath($directory); if (!$realDir) { - throw new \InvalidArgumentException('Directory does not exist'); + throw new Exception\InvalidArgumentException(sprintf( + 'Directory "%s" does not exist', $realDir + )); } $this->directories[] = $realDir; } @@ -48,9 +54,9 @@ protected function scan() // iterate directories creating file scanners foreach ($this->directories as $directory) { - foreach (new \RecursiveDirectoryIterator($directory) as $item) { + foreach (new RecursiveDirectoryIterator($directory) as $item) { if ($item->isFile() && preg_match('#\.php$#', $item->getRealPath())) { - $this->scannerFiles[] = new ScannerFile($item->getRealpath()); + $this->scannerFiles[] = new ScannerFile($item->getRealPath()); } } } diff --git a/library/Zend/Code/Scanner/ScannerFile.php b/library/Zend/Code/Scanner/ScannerFile.php index 24598a195aa..b61e67ec22b 100644 --- a/library/Zend/Code/Scanner/ScannerFile.php +++ b/library/Zend/Code/Scanner/ScannerFile.php @@ -2,11 +2,14 @@ namespace Zend\Code\Scanner; -class ScannerFile extends ScannerTokenArray implements ScannerInterface +use Zend\Code\Scanner, + Zend\Code\Exception; + +class ScannerFile extends ScannerTokenArray implements Scanner { protected $isScanned = false; - protected $file = null; + protected $file = null; public function __construct($file = null, $options = null) { @@ -19,7 +22,9 @@ public function setFile($file) { $this->file = $file; if (!file_exists($file)) { - throw new \InvalidArgumentException('File not found'); + throw new Exception\InvalidArgumentException(sprintf( + 'File "%s" not found', $file + )); } $this->reset(); } @@ -32,7 +37,7 @@ public function getFile() protected function scan() { if (!$this->file) { - throw new \RuntimeException('File was not provided'); + throw new Exception\RuntimeException('File was not provided'); } $this->setTokens(token_get_all(file_get_contents($this->file))); parent::scan(); diff --git a/library/Zend/Code/Scanner/ScannerMethod.php b/library/Zend/Code/Scanner/ScannerMethod.php index d211ce7069c..bb431406992 100644 --- a/library/Zend/Code/Scanner/ScannerMethod.php +++ b/library/Zend/Code/Scanner/ScannerMethod.php @@ -2,23 +2,26 @@ namespace Zend\Code\Scanner; -class ScannerMethod implements ScannerInterface +use Zend\Code\Scanner, + Zend\Code\Exception; + +class ScannerMethod implements Scanner { - protected $isScanned = false; - + protected $isScanned = false; + protected $scannerClass = null; - protected $class = null; - protected $uses = array(); - protected $name = null; - protected $isFinal = false; - protected $isAbstract = false; - protected $isPublic = true; - protected $isProtected = false; - protected $isPrivate = false; - protected $isStatic = false; - - protected $tokens = array(); - protected $infos = array(); + protected $class = null; + protected $uses = array(); + protected $name = null; + protected $isFinal = false; + protected $isAbstract = false; + protected $isPublic = true; + protected $isProtected = false; + protected $isPrivate = false; + protected $isStatic = false; + + protected $tokens = array(); + protected $infos = array(); public function __construct(array $methodTokens, array $uses = array()) { @@ -48,11 +51,11 @@ protected function scan() } if (!$this->tokens) { - throw new \RuntimeException('No tokens were provided'); + throw new Exception\RuntimeException('No tokens were provided'); } $fastForward = 0; - $tokenIndex = 0; + $tokenIndex = 0; $this->scanMethodInfo($tokenIndex, $fastForward); @@ -68,14 +71,6 @@ protected function scan() $this->scanParameters($tokenIndex, $fastForward); - /* - if ($this->tokens[$tokenIndex] != '{') { - return; - } - */ - - //$this->scanBody($tokenIndex); - $this->isScanned = true; } @@ -98,22 +93,28 @@ protected function scanMethodInfo($tokenIndex, &$fastForward) case T_FINAL: $this->isFinal = true; continue; + case T_ABSTRACT: $this->isAbstract = true; continue; + case T_PUBLIC: continue; + case T_PROTECTED: $this->isProtected = true; $this->isPublic = false; continue; + case T_PRIVATE: $this->isPrivate = true; $this->isPublic = false; continue; + case T_STATIC: $this->isStatic = true; continue; + case T_STRING: $this->name = $token[1]; continue; @@ -128,8 +129,8 @@ protected function scanParameters($tokenIndex, &$fastForward) { // first token is paren let loop increase $parenCount = 1; - $info = null; - $position = 0; + $info = null; + $position = 0; while (true) { $tokenIndex++; @@ -156,7 +157,7 @@ protected function scanParameters($tokenIndex, &$fastForward) 'lineStart' => $token[2], 'lineEnd' => $token[2], 'name' => null, - 'position' => ++$position + 'position' => ++$position, ); } @@ -184,7 +185,7 @@ protected function scanParameters($tokenIndex, &$fastForward) if (isset($info) && is_string($token) && $token == ',') { $info['tokenEnd'] = $tokenIndex - 1; - $this->infos[] = $info; + $this->infos[] = $info; unset($info); } @@ -263,21 +264,26 @@ public function getParameter($parameterNameOrInfoIndex, $returnScanner = 'Zend\C $this->scan(); // process the class requested + // Static for performance reasons static $baseScannerClass = 'Zend\Code\Scanner\ScannerParameter'; if ($returnScanner !== $baseScannerClass) { if (!is_string($returnScanner)) { $returnScanner = $baseScannerClass; } $returnScanner = ltrim($returnScanner, '\\'); - if ($returnScanner !== $baseScannerClass && !is_subclass_of($returnScanner, $baseScannerClass)) { - throw new \RuntimeException('Class must be or extend ' . $baseScannerClass); + if ($returnScanner !== $baseScannerClass + && !is_subclass_of($returnScanner, $baseScannerClass) + ) { + throw new Exception\RuntimeException(sprintf( + 'Class must be or extend "%s"', $baseScannerClass + )); } } if (is_int($parameterNameOrInfoIndex)) { $info = $this->infos[$parameterNameOrInfoIndex]; if ($info['type'] != 'parameter') { - throw new \InvalidArgumentException('Index of info offset is not about a parameter'); + throw new Exception\InvalidArgumentException('Index of info offset is not about a parameter'); } } elseif (is_string($parameterNameOrInfoIndex)) { $methodFound = false; @@ -315,4 +321,4 @@ public function __toString() return var_export($this, true); } -} \ No newline at end of file +} diff --git a/library/Zend/Code/Scanner/ScannerParameter.php b/library/Zend/Code/Scanner/ScannerParameter.php index ec53dc12f39..70e78433c90 100644 --- a/library/Zend/Code/Scanner/ScannerParameter.php +++ b/library/Zend/Code/Scanner/ScannerParameter.php @@ -4,28 +4,28 @@ class ScannerParameter { - protected $isScanned = false; - - protected $declaringScannerClass = null; - protected $declaringClass = null; + protected $isScanned = false; + + protected $declaringScannerClass = null; + protected $declaringClass = null; protected $declaringScannerFunction = null; - protected $declaringFunction = null; - protected $defaultValue = null; - protected $class = null; - protected $name = null; - protected $position = null; - protected $isArray = false; - protected $isDefaultValueAvailable = false; - protected $isOptional = false; - protected $isPassedByReference = false; - - protected $tokens = null; - protected $uses = array(); + protected $declaringFunction = null; + protected $defaultValue = null; + protected $class = null; + protected $name = null; + protected $position = null; + protected $isArray = false; + protected $isDefaultValueAvailable = false; + protected $isOptional = false; + protected $isPassedByReference = false; + + protected $tokens = null; + protected $uses = array(); public function __construct(array $parameterTokens, array $uses = array()) { $this->tokens = $parameterTokens; - $this->uses = $uses; + $this->uses = $uses; } public function setDeclaringClass($class) @@ -74,7 +74,8 @@ protected function scan() if ($this->class) { $namespace = (($decClassLastSlash = strrpos($this->declaringClass, '\\')) !== false) - ? substr($this->declaringClass, 0, $decClassLastSlash) : null; + ? substr($this->declaringClass, 0, $decClassLastSlash) + : null; if ((!$this->uses && !$namespace) || strlen($this->class) <= 0 || $this->class{0} == '\\') { $this->class = ltrim($this->class, '\\'); } else { @@ -101,7 +102,9 @@ protected function scan() // next token is sure a T_VARIABLE $this->name = ltrim($token[1], '$'); - $token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : null; + $token = (isset($this->tokens[++$tokenIndex])) + ? $this->tokens[$tokenIndex] + : null; if (!$token) { $this->isScanned = true; @@ -110,7 +113,9 @@ protected function scan() // move past whitespace if it exist if ($token[0] == T_WHITESPACE) { - $token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : null; + $token = (isset($this->tokens[++$tokenIndex])) + ? $this->tokens[$tokenIndex] + : null; } if (!$token) { @@ -128,15 +133,20 @@ protected function scan() // move past whitespace if it exist if ($token[0] == T_WHITESPACE) { - $token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : null; + $token = (isset($this->tokens[++$tokenIndex])) + ? $this->tokens[$tokenIndex] + : null; } - $this->isOptional = true; + $this->isOptional = true; $this->isDefaultValueAvailable = true; do { $this->defaultValue .= ((is_array($token)) ? $token[1] : $token); - } while (($token = (isset($this->tokens[++$tokenIndex])) ? $this->tokens[$tokenIndex] : false)); + $token = (isset($this->tokens[++$tokenIndex])) + ? $this->tokens[$tokenIndex] + : false; + } while ($token); if ($this->class) { // create function to resolve short names with uses @@ -275,4 +285,4 @@ public function isPassedByReference() } -} \ No newline at end of file +} diff --git a/library/Zend/Code/Scanner/ScannerTokenArray.php b/library/Zend/Code/Scanner/ScannerTokenArray.php index bbd6e80cb65..5542c2d9917 100644 --- a/library/Zend/Code/Scanner/ScannerTokenArray.php +++ b/library/Zend/Code/Scanner/ScannerTokenArray.php @@ -2,13 +2,16 @@ namespace Zend\Code\Scanner; -class ScannerTokenArray implements ScannerInterface +use Zend\Code\Scanner, + Zend\Code\Exception; + +class ScannerTokenArray implements Scanner { protected $isScanned = false; - - protected $tokens = array(); - - protected $infos = array(); + + protected $tokens = array(); + + protected $infos = array(); public function __construct($tokens = null, $options = null) { @@ -20,7 +23,7 @@ public function __construct($tokens = null, $options = null) public function reset() { $this->isScanned = false; - $this->infos = array(); + $this->infos = array(); } public function setTokens(array $tokens) @@ -36,7 +39,7 @@ protected function scan() } if (!$this->tokens) { - throw new \RuntimeException('No tokens were provided'); + throw new Exception\RuntimeException('No tokens were provided'); } $currentNamespace = null; @@ -50,26 +53,31 @@ protected function scan() case T_DOC_COMMENT: echo 'Found Doc Comment' . PHP_EOL; break; + case T_NAMESPACE: $currentNamespace = $this->scanNamespace($tokenIndex, $fastForward); break; + case T_USE: - $this->scanUse($tokenIndex, $fastForward); // process uses + $this->scanUse($tokenIndex, $fastForward); break; + case T_INCLUDE: case T_INCLUDE_ONCE: case T_REQUIRE: case T_REQUIRE_ONCE: - $this->scanInclude($tokenIndex, $fastForward); // process include + $this->scanInclude($tokenIndex, $fastForward); break; + case T_FINAL: case T_ABSTRACT: case T_CLASS: case T_INTERFACE: $this->scanClass($tokenIndex, $fastForward, $currentNamespace); break; + case T_FUNCTION: $this->scanFunction($tokenIndex, $fastForward, $currentNamespace); break; @@ -90,7 +98,7 @@ protected function scanNamespace($tokenIndex, &$fastForward) 'tokenEnd' => null, 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, - 'namespace' => null + 'namespace' => null, ); // move past current T_NAMESPACE & following T_WHITESPACE @@ -120,7 +128,7 @@ protected function scanNamespace($tokenIndex, &$fastForward) } $info['tokenEnd'] = $tokenIndex; - $this->infos[] = $info; + $this->infos[] = $info; return $info['namespace']; } @@ -133,22 +141,23 @@ protected function scanUse($tokenIndex, &$fastForward) 'tokenEnd' => null, 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, - 'statements' => array() + 'statements' => array(), ); + // Static for performance purposes static $statementTemplate = array( - 'use' => null, - 'as' => null, - 'asComputed' => null + 'use' => null, + 'as' => null, + 'asComputed' => null, ); // skip current token T_USE and following T_WHITESPACE $tokenIndex++; $fastForward++; - $sCount = 0; + $hasAs = false; + $sCount = 0; $info['statements'][$sCount] = $statementTemplate; - $hasAs = false; while (true) { $tokenIndex++; @@ -192,12 +201,19 @@ protected function scanUse($tokenIndex, &$fastForward) } $info['tokenEnd'] = $tokenIndex; - $this->infos[] = $info; + $this->infos[] = $info; } protected function scanInclude($tokenIndex, &$fastForward) { - static $types = array(T_INCLUDE => 'include', T_INCLUDE_ONCE => 'include_once', T_REQUIRE => 'require', T_REQUIRE_ONCE => 'require_once'); + // Static for performance purposes + static $types = array( + T_INCLUDE => 'include', + T_INCLUDE_ONCE => 'include_once', + T_REQUIRE => 'require', + T_REQUIRE_ONCE => 'require_once', + ); + $info = array( 'type' => 'include', 'tokenStart' => $tokenIndex, @@ -205,10 +221,10 @@ protected function scanInclude($tokenIndex, &$fastForward) 'lineStart' => $this->tokens[$tokenIndex][2], 'lineEnd' => null, 'includeType' => $types[$this->tokens[$tokenIndex][0]], - 'path' => '' - ); + 'path' => '', + ); - $path = ''; + $path = ''; $index = $tokenIndex; // move past include & the required whitespace @@ -247,11 +263,13 @@ protected function scanClass($tokenIndex, &$fastForward, $namespace = null) 'lineEnd' => null, 'namespace' => $namespace, 'name' => null, - 'shortName' => null - ); + 'shortName' => null, + ); // if FINAL or ABSTRACT marker is found, find name accordingly - if ($this->tokens[$tokenIndex][0] === T_FINAL || $this->tokens[$tokenIndex][0] === T_ABSTRACT) { + if ($this->tokens[$tokenIndex][0] === T_FINAL + || $this->tokens[$tokenIndex][0] === T_ABSTRACT + ) { $info['shortName'] = $this->tokens[$tokenIndex+4][1]; } else { $info['shortName'] = $this->tokens[$tokenIndex+2][1]; @@ -288,7 +306,7 @@ protected function scanClass($tokenIndex, &$fastForward, $namespace = null) } $info['tokenEnd'] = $tokenIndex; - $this->infos[] = $info; + $this->infos[] = $info; } protected function scanFunction($tokenIndex, &$fastForward, $namespace = null, $usesComputed = array()) @@ -302,7 +320,7 @@ protected function scanFunction($tokenIndex, &$fastForward, $namespace = null, $ 'name' => $namespace . '\\' . $this->tokens[$tokenIndex+2][1], 'shortName' => $this->tokens[$tokenIndex+2][1], 'namespace' => $namespace, - ); + ); $braceCount = 0; while (true) { @@ -331,7 +349,7 @@ protected function scanFunction($tokenIndex, &$fastForward, $namespace = null, $ } $info['tokenEnd'] = $tokenIndex; - $this->infos[] = $info; + $this->infos[] = $info; } @@ -349,7 +367,7 @@ public function getNamespaces($returnScannerClass = false) return $namespaces; } else { if ($returnScannerClass === true) { - $returnScannerClass = '\Zend\Code\Scanner\ScannerNamespace'; + $returnScannerClass = 'Zend\Code\Scanner\ScannerNamespace'; } $scannerClass = new $returnScannerClass; // @todo @@ -368,13 +386,7 @@ public function getUses($returnScannerClass = false) } } return $namespaces; - } /*else { - if ($returnScannerClass === true) { - $returnScannerClass = '\Zend\Code\Scanner\ScannerClasss'; - } - $scannerClass = new $returnScannerClass; - // @todo - } */ + } } public function getIncludes($returnScannerClass = false) @@ -414,22 +426,27 @@ public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code { $this->scan(); - // process the class requested + // Process the class requested + // Static for performance reasons static $baseScannerClass = 'Zend\Code\Scanner\ScannerClass'; if ($returnScannerClass !== $baseScannerClass) { if (!is_string($returnScannerClass)) { $returnScannerClass = $baseScannerClass; } $returnScannerClass = ltrim($returnScannerClass, '\\'); - if ($returnScannerClass !== $baseScannerClass && !is_subclass_of($returnScannerClass, $baseScannerClass)) { - throw new \RuntimeException('Class must be or extend ' . $baseScannerClass); + if ($returnScannerClass !== $baseScannerClass + && !is_subclass_of($returnScannerClass, $baseScannerClass) + ) { + throw new Exception\RuntimeException(sprintf( + 'Class must be or extend "%s"', $baseScannerClass + )); } } if (is_int($classNameOrInfoIndex)) { $info = $this->infos[$classNameOrInfoIndex]; if ($info['type'] != 'class') { - throw new \InvalidArgumentException('Index of info offset is not about a class'); + throw new Exception\InvalidArgumentException('Index of info offset is not about a class'); } } elseif (is_string($classNameOrInfoIndex)) { $classFound = false; @@ -448,17 +465,21 @@ public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code for ($u = 0; $u < count($this->infos); $u++) { if ($this->infos[$u]['type'] == 'use') { foreach ($this->infos[$u]['statements'] as $useStatement) { - $useKey = ($useStatement['as']) ?: $useStatement['asComputed']; + $useKey = ($useStatement['as']) ?: $useStatement['asComputed']; $uses[$useKey] = $useStatement['use']; } } } return new $returnScannerClass( - array_slice($this->tokens, $info['tokenStart'], ($info['tokenEnd'] - $info['tokenStart'] + 1)), // zero indexed array + array_slice( + $this->tokens, + $info['tokenStart'], + ($info['tokenEnd'] - $info['tokenStart'] + 1) + ), // zero indexed array $info['namespace'], $uses - ); + ); } public function getFunctions($returnScannerClass = false) @@ -475,7 +496,7 @@ public function getFunctions($returnScannerClass = false) return $functions; } else { if ($returnScannerClass === true) { - $returnScannerClass = '\Zend\Code\Scanner\ScannerFunction'; + $returnScannerClass = 'Zend\Code\Scanner\ScannerFunction'; } $scannerClass = new $returnScannerClass; // @todo From fa6f1df3b2d60380ac2bf6e5a4c0b39688ba8210 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 8 Jun 2011 15:50:39 -0500 Subject: [PATCH 38/45] Rename from ScannerFoo to FooScanner - Renamed to be consistent with other similar usage elsewhere - Reflection API is exempted from this renaming, as it mimics an internal API (which uses pseudo-namespaces) - CodeGenerator may require similar refactoring --- .../{ScannerClass.php => ClassScanner.php} | 8 ++-- ...nnerDirectory.php => DirectoryScanner.php} | 14 +++--- ...erDocComment.php => DocCommentScanner.php} | 4 +- ...ommentTag.php => DocCommentTagScanner.php} | 2 +- .../{ScannerFile.php => FileScanner.php} | 2 +- ...cannerFunction.php => FunctionScanner.php} | 4 +- .../{ScannerMethod.php => MethodScanner.php} | 8 ++-- ...nnerParameter.php => ParameterScanner.php} | 6 +-- ...cannerProperty.php => PropertyScanner.php} | 0 .../Zend/Code/Scanner/ScannerInterface.php | 9 ---- ...erTokenArray.php => TokenArrayScanner.php} | 14 +++--- .../{ScannerValue.php => ValueScanner.php} | 2 +- ...cannerVariable.php => VariableScanner.php} | 2 +- ...nnerClassTest.php => ClassScannerTest.php} | 37 ++++++++-------- tests/Zend/Code/Scanner/FileScannerTest.php | 43 ++++++++++++++++++ tests/Zend/Code/Scanner/MethodScannerTest.php | 43 ++++++++++++++++++ ...meterTest.php => ParameterScannerTest.php} | 17 ++++--- tests/Zend/Code/Scanner/ScannerFileTest.php | 44 ------------------- tests/Zend/Code/Scanner/ScannerMethodTest.php | 44 ------------------- 19 files changed, 145 insertions(+), 158 deletions(-) rename library/Zend/Code/Scanner/{ScannerClass.php => ClassScanner.php} (98%) rename library/Zend/Code/Scanner/{ScannerDirectory.php => DirectoryScanner.php} (91%) rename library/Zend/Code/Scanner/{ScannerDocComment.php => DocCommentScanner.php} (83%) rename library/Zend/Code/Scanner/{ScannerDocCommentTag.php => DocCommentTagScanner.php} (66%) rename library/Zend/Code/Scanner/{ScannerFile.php => FileScanner.php} (94%) rename library/Zend/Code/Scanner/{ScannerFunction.php => FunctionScanner.php} (51%) rename library/Zend/Code/Scanner/{ScannerMethod.php => MethodScanner.php} (97%) rename library/Zend/Code/Scanner/{ScannerParameter.php => ParameterScanner.php} (98%) rename library/Zend/Code/Scanner/{ScannerProperty.php => PropertyScanner.php} (100%) delete mode 100644 library/Zend/Code/Scanner/ScannerInterface.php rename library/Zend/Code/Scanner/{ScannerTokenArray.php => TokenArrayScanner.php} (97%) rename library/Zend/Code/Scanner/{ScannerValue.php => ValueScanner.php} (73%) rename library/Zend/Code/Scanner/{ScannerVariable.php => VariableScanner.php} (71%) rename tests/Zend/Code/Scanner/{ScannerClassTest.php => ClassScannerTest.php} (59%) create mode 100644 tests/Zend/Code/Scanner/FileScannerTest.php create mode 100644 tests/Zend/Code/Scanner/MethodScannerTest.php rename tests/Zend/Code/Scanner/{ScannerParameterTest.php => ParameterScannerTest.php} (63%) delete mode 100644 tests/Zend/Code/Scanner/ScannerFileTest.php delete mode 100644 tests/Zend/Code/Scanner/ScannerMethodTest.php diff --git a/library/Zend/Code/Scanner/ScannerClass.php b/library/Zend/Code/Scanner/ClassScanner.php similarity index 98% rename from library/Zend/Code/Scanner/ScannerClass.php rename to library/Zend/Code/Scanner/ClassScanner.php index 46b262c3cdc..4c049e90125 100644 --- a/library/Zend/Code/Scanner/ScannerClass.php +++ b/library/Zend/Code/Scanner/ClassScanner.php @@ -5,7 +5,7 @@ use Zend\Code\Scanner, Zend\Code\Exception; -class ScannerClass implements Scanner +class ClassScanner implements Scanner { protected $isScanned = false; @@ -411,15 +411,15 @@ public function getMethods($returnScannerMethod = false) /** * @param string|int $methodNameOrInfoIndex * @param string $returnScannerClass - * @return Zend\Code\Scanner\ScannerMethod + * @return Zend\Code\Scanner\MethodScanner */ - public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerMethod') + public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\MethodScanner') { $this->scan(); // process the class requested // Static for performance reasons - static $baseScannerClass = 'Zend\Code\Scanner\ScannerMethod'; + static $baseScannerClass = 'Zend\Code\Scanner\MethodScanner'; if ($returnScannerClass !== $baseScannerClass) { if (!is_string($returnScannerClass)) { $returnScannerClass = $baseScannerClass; diff --git a/library/Zend/Code/Scanner/ScannerDirectory.php b/library/Zend/Code/Scanner/DirectoryScanner.php similarity index 91% rename from library/Zend/Code/Scanner/ScannerDirectory.php rename to library/Zend/Code/Scanner/DirectoryScanner.php index a4d82e76739..5047597c117 100644 --- a/library/Zend/Code/Scanner/ScannerDirectory.php +++ b/library/Zend/Code/Scanner/DirectoryScanner.php @@ -6,11 +6,11 @@ Zend\Code\Exception, RecursiveDirectoryIterator; -class ScannerDirectory implements Scanner +class DirectoryScanner implements Scanner { protected $isScanned = false; protected $directories = array(); - protected $fileScannerFileClass = 'Zend\Code\Scanner\ScannerFile'; + protected $fileScannerFileClass = 'Zend\Code\Scanner\FileScanner'; protected $scannerFiles = array(); public function __construct($directory = null) @@ -56,7 +56,7 @@ protected function scan() foreach ($this->directories as $directory) { foreach (new RecursiveDirectoryIterator($directory) as $item) { if ($item->isFile() && preg_match('#\.php$#', $item->getRealPath())) { - $this->scannerFiles[] = new ScannerFile($item->getRealPath()); + $this->scannerFiles[] = new FileScanner($item->getRealPath()); } } } @@ -71,7 +71,7 @@ public function getClasses($returnScannerClass = false) $classes = array(); foreach ($this->scannerFiles as $scannerFile) { - /* @var $scannerFile Zend\Code\Scanner\ScannerFile */ + /* @var $scannerFile Zend\Code\Scanner\FileScanner */ $classes = array_merge($classes, $scannerFile->getClasses($returnScannerClass)); } @@ -84,15 +84,15 @@ public function getClasses($returnScannerClass = false) * Enter description here ... * @param string|int $classNameOrInfoIndex * @param string $returnScannerClass - * @return Zend\Code\Scanner\ScannerClass + * @return Zend\Code\Scanner\ClassScanner */ /* - public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerClass') + public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ClassScanner') { $this->scan(); // process the class requested - static $baseScannerClass = 'Zend\Code\Scanner\ScannerClass'; + static $baseScannerClass = 'Zend\Code\Scanner\ClassScanner'; if ($returnScannerClass !== $baseScannerClass) { if (!is_string($returnScannerClass)) { $returnScannerClass = $baseScannerClass; diff --git a/library/Zend/Code/Scanner/ScannerDocComment.php b/library/Zend/Code/Scanner/DocCommentScanner.php similarity index 83% rename from library/Zend/Code/Scanner/ScannerDocComment.php rename to library/Zend/Code/Scanner/DocCommentScanner.php index 7ce38a50804..f90aaef94b8 100644 --- a/library/Zend/Code/Scanner/ScannerDocComment.php +++ b/library/Zend/Code/Scanner/DocCommentScanner.php @@ -2,8 +2,8 @@ namespace Zend\Code\Scanner; -class ScannerDocComment +class DocCommentScanner { // @todo // This should be able to use the doccomment extension, or tokenize itself -} \ No newline at end of file +} diff --git a/library/Zend/Code/Scanner/ScannerDocCommentTag.php b/library/Zend/Code/Scanner/DocCommentTagScanner.php similarity index 66% rename from library/Zend/Code/Scanner/ScannerDocCommentTag.php rename to library/Zend/Code/Scanner/DocCommentTagScanner.php index b12caf4b7c8..7f8fbbf2552 100644 --- a/library/Zend/Code/Scanner/ScannerDocCommentTag.php +++ b/library/Zend/Code/Scanner/DocCommentTagScanner.php @@ -2,7 +2,7 @@ namespace Zend\Code\Scanner; -class ScannerDocCommentTag +class DocCommentTagScanner { // @todo } diff --git a/library/Zend/Code/Scanner/ScannerFile.php b/library/Zend/Code/Scanner/FileScanner.php similarity index 94% rename from library/Zend/Code/Scanner/ScannerFile.php rename to library/Zend/Code/Scanner/FileScanner.php index b61e67ec22b..40312d39af1 100644 --- a/library/Zend/Code/Scanner/ScannerFile.php +++ b/library/Zend/Code/Scanner/FileScanner.php @@ -5,7 +5,7 @@ use Zend\Code\Scanner, Zend\Code\Exception; -class ScannerFile extends ScannerTokenArray implements Scanner +class FileScanner extends TokenArrayScanner implements Scanner { protected $isScanned = false; diff --git a/library/Zend/Code/Scanner/ScannerFunction.php b/library/Zend/Code/Scanner/FunctionScanner.php similarity index 51% rename from library/Zend/Code/Scanner/ScannerFunction.php rename to library/Zend/Code/Scanner/FunctionScanner.php index a3f86f15cfe..b6663b89d05 100644 --- a/library/Zend/Code/Scanner/ScannerFunction.php +++ b/library/Zend/Code/Scanner/FunctionScanner.php @@ -2,8 +2,8 @@ namespace Zend\Code\Scanner; -class ScannerFunction +class FunctionScanner { // @todo - // Should this extend something similar to ScannerMethod? Similar to ReflectionFunctionAbstract + // Should this extend something similar to MethodScanner? Similar to ReflectionFunctionAbstract } diff --git a/library/Zend/Code/Scanner/ScannerMethod.php b/library/Zend/Code/Scanner/MethodScanner.php similarity index 97% rename from library/Zend/Code/Scanner/ScannerMethod.php rename to library/Zend/Code/Scanner/MethodScanner.php index bb431406992..a7939717aa7 100644 --- a/library/Zend/Code/Scanner/ScannerMethod.php +++ b/library/Zend/Code/Scanner/MethodScanner.php @@ -5,7 +5,7 @@ use Zend\Code\Scanner, Zend\Code\Exception; -class ScannerMethod implements Scanner +class MethodScanner implements Scanner { protected $isScanned = false; @@ -34,7 +34,7 @@ public function setClass($class) $this->class = $class; } - public function setScannerClass(ScannerClass $scannerClass) + public function setScannerClass(ClassScanner $scannerClass) { $this->scannerClass = $scannerClass; } @@ -259,13 +259,13 @@ public function getParameters($returnScanner = false) return $return; } - public function getParameter($parameterNameOrInfoIndex, $returnScanner = 'Zend\Code\Scanner\ScannerParameter') + public function getParameter($parameterNameOrInfoIndex, $returnScanner = 'Zend\Code\Scanner\ParameterScanner') { $this->scan(); // process the class requested // Static for performance reasons - static $baseScannerClass = 'Zend\Code\Scanner\ScannerParameter'; + static $baseScannerClass = 'Zend\Code\Scanner\ParameterScanner'; if ($returnScanner !== $baseScannerClass) { if (!is_string($returnScanner)) { $returnScanner = $baseScannerClass; diff --git a/library/Zend/Code/Scanner/ScannerParameter.php b/library/Zend/Code/Scanner/ParameterScanner.php similarity index 98% rename from library/Zend/Code/Scanner/ScannerParameter.php rename to library/Zend/Code/Scanner/ParameterScanner.php index 70e78433c90..dca2065ce33 100644 --- a/library/Zend/Code/Scanner/ScannerParameter.php +++ b/library/Zend/Code/Scanner/ParameterScanner.php @@ -2,7 +2,7 @@ namespace Zend\Code\Scanner; -class ScannerParameter +class ParameterScanner { protected $isScanned = false; @@ -33,7 +33,7 @@ public function setDeclaringClass($class) $this->declaringClass = $class; } - public function setDeclaringScannerClass(ScannerClass $scannerClass) + public function setDeclaringScannerClass(ClassScanner $scannerClass) { $this->declaringScannerClass = $scannerClass; } @@ -43,7 +43,7 @@ public function setDeclaringFunction($function) $this->declaringFunction = $function; } - public function setDeclaringScannerFunction(ScannerMethod $scannerFunction) + public function setDeclaringScannerFunction(MethodScanner $scannerFunction) { $this->declaringScannerFunction = $scannerFunction; } diff --git a/library/Zend/Code/Scanner/ScannerProperty.php b/library/Zend/Code/Scanner/PropertyScanner.php similarity index 100% rename from library/Zend/Code/Scanner/ScannerProperty.php rename to library/Zend/Code/Scanner/PropertyScanner.php diff --git a/library/Zend/Code/Scanner/ScannerInterface.php b/library/Zend/Code/Scanner/ScannerInterface.php deleted file mode 100644 index 575ed92f7cc..00000000000 --- a/library/Zend/Code/Scanner/ScannerInterface.php +++ /dev/null @@ -1,9 +0,0 @@ -scan(); - // @todo Implement getIncludes() in ScannerTokenArray + // @todo Implement getIncludes() in TokenArrayScanner } public function getClasses($returnScannerClass = false) @@ -420,15 +420,15 @@ public function getClasses($returnScannerClass = false) * Enter description here ... * @param string|int $classNameOrInfoIndex * @param string $returnScannerClass - * @return Zend\Code\Scanner\ScannerClass + * @return Zend\Code\Scanner\ClassScanner */ - public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ScannerClass') + public function getClass($classNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\ClassScanner') { $this->scan(); // Process the class requested // Static for performance reasons - static $baseScannerClass = 'Zend\Code\Scanner\ScannerClass'; + static $baseScannerClass = 'Zend\Code\Scanner\ClassScanner'; if ($returnScannerClass !== $baseScannerClass) { if (!is_string($returnScannerClass)) { $returnScannerClass = $baseScannerClass; @@ -496,7 +496,7 @@ public function getFunctions($returnScannerClass = false) return $functions; } else { if ($returnScannerClass === true) { - $returnScannerClass = 'Zend\Code\Scanner\ScannerFunction'; + $returnScannerClass = 'Zend\Code\Scanner\FunctionScanner'; } $scannerClass = new $returnScannerClass; // @todo diff --git a/library/Zend/Code/Scanner/ScannerValue.php b/library/Zend/Code/Scanner/ValueScanner.php similarity index 73% rename from library/Zend/Code/Scanner/ScannerValue.php rename to library/Zend/Code/Scanner/ValueScanner.php index 0c63484784c..0389c6f5d10 100644 --- a/library/Zend/Code/Scanner/ScannerValue.php +++ b/library/Zend/Code/Scanner/ValueScanner.php @@ -2,7 +2,7 @@ namespace Zend\Code\Scanner; -class ScannerValue +class ValueScanner { // @todo } diff --git a/library/Zend/Code/Scanner/ScannerVariable.php b/library/Zend/Code/Scanner/VariableScanner.php similarity index 71% rename from library/Zend/Code/Scanner/ScannerVariable.php rename to library/Zend/Code/Scanner/VariableScanner.php index 889f95a79a9..3ace08f88a6 100644 --- a/library/Zend/Code/Scanner/ScannerVariable.php +++ b/library/Zend/Code/Scanner/VariableScanner.php @@ -2,7 +2,7 @@ namespace Zend\Code\Scanner; -class ScannerVariable +class VariableScanner { // @todo } diff --git a/tests/Zend/Code/Scanner/ScannerClassTest.php b/tests/Zend/Code/Scanner/ClassScannerTest.php similarity index 59% rename from tests/Zend/Code/Scanner/ScannerClassTest.php rename to tests/Zend/Code/Scanner/ClassScannerTest.php index 20d5a0045dc..00492a1edbb 100644 --- a/tests/Zend/Code/Scanner/ScannerClassTest.php +++ b/tests/Zend/Code/Scanner/ClassScannerTest.php @@ -2,14 +2,14 @@ namespace ZendTest\Code\Scanner; -use Zend\Code\Scanner\ScannerFile; +use Zend\Code\Scanner\FileScanner, + PHPUnit_Framework_TestCase as TestCase; -class ScannerClassTest extends \PHPUnit_Framework_TestCase +class ClassScannerTest extends TestCase { - - public function testScannerClassHasClassInformation() + public function testClassScannerHasClassInformation() { - $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $file = new FileScanner(__DIR__ . '/../TestAsset/FooClass.php'); $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertEquals('ZendTest\Code\TestAsset\FooClass', $class->getName()); $this->assertEquals('FooClass', $class->getShortName()); @@ -25,36 +25,35 @@ public function testScannerClassHasClassInformation() $this->assertContains('fooBarBaz', $methods); } - public function testScannerClassHasConstant() + public function testClassScannerHasConstant() { - $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $file = new FileScanner(__DIR__ . '/../TestAsset/FooClass.php'); $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertInternalType('array', $class->getConstants()); - // } - public function testScannerClassHasProperties() + public function testClassScannerHasProperties() { - $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $file = new FileScanner(__DIR__ . '/../TestAsset/FooClass.php'); $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertInternalType('array', $class->getProperties()); $this->assertContains('bar', $class->getProperties()); } - public function testScannerClassHasMethods() + public function testClassScannerHasMethods() { - $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); + $file = new FileScanner(__DIR__ . '/../TestAsset/FooClass.php'); $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $this->assertContains('fooBarBaz', $class->getMethods()); } - public function testScannerClassReturnsMethodsWithScannerMethod() + public function testClassScannerReturnsMethodsWithMethodScanners() { - $file = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); + $file = new FileScanner(__DIR__ . '/../TestAsset/FooClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\FooClass'); $methods = $class->getMethods(true); - $method = array_shift($methods); - $this->assertInstanceOf('Zend\Code\Scanner\ScannerMethod', $method); + foreach ($methods as $method) { + $this->assertInstanceOf('Zend\Code\Scanner\MethodScanner', $method); + } } - -} \ No newline at end of file +} diff --git a/tests/Zend/Code/Scanner/FileScannerTest.php b/tests/Zend/Code/Scanner/FileScannerTest.php new file mode 100644 index 00000000000..d49a7899735 --- /dev/null +++ b/tests/Zend/Code/Scanner/FileScannerTest.php @@ -0,0 +1,43 @@ +getNamespaces(); + $this->assertInternalType('array', $namespaces); + $this->assertContains('ZendTest\Code\TestAsset', $namespaces); + } + + public function testFileScannerReturnsClassesWithoutClassScanner() + { + $fileScanner = new FileScanner(__DIR__ . '/../TestAsset/FooClass.php'); + $classes = $fileScanner->getClasses(); + $this->assertInternalType('array', $classes); + $this->assertContains('ZendTest\Code\TestAsset\FooClass', $classes); + } + + public function testFileScannerReturnsFunctionsWithoutClassScanner() + { + $fileScanner = new FileScanner(__DIR__ . '/../TestAsset/functions.php'); + $functions = $fileScanner->getFunctions(); + $this->assertInternalType('array', $functions); + $this->assertContains('ZendTest\Code\TestAsset\foo_bar', $functions); + } + + public function testFileScannerReturnsClassesWithClassScanner() + { + $fileScanner = new FileScanner(__DIR__ . '/../TestAsset/FooClass.php'); + $classes = $fileScanner->getClasses(true); + $this->assertInternalType('array', $classes); + foreach ($classes as $class) { + $this->assertInstanceOf('Zend\Code\Scanner\ClassScanner', $class); + } + } +} diff --git a/tests/Zend/Code/Scanner/MethodScannerTest.php b/tests/Zend/Code/Scanner/MethodScannerTest.php new file mode 100644 index 00000000000..3d65cac8739 --- /dev/null +++ b/tests/Zend/Code/Scanner/MethodScannerTest.php @@ -0,0 +1,43 @@ +getClass('ZendTest\Code\TestAsset\FooClass'); + $method = $class->getMethod('fooBarBaz'); + $this->assertEquals('fooBarBaz', $method->getName()); + $this->assertFalse($method->isAbstract()); + $this->assertTrue($method->isFinal()); + $this->assertTrue($method->isPublic()); + $this->assertFalse($method->isProtected()); + $this->assertFalse($method->isPrivate()); + $this->assertFalse($method->isStatic()); + } + + public function testMethodScannerReturnsParameters() + { + $file = new FileScanner(__DIR__ . '/../TestAsset/BarClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); + $method = $class->getMethod('three'); + $parameters = $method->getParameters(); + $this->assertInternalType('array', $parameters); + } + + public function testMethodScannerReturnsParameterScanner() + { + $file = new FileScanner(__DIR__ . '/../TestAsset/BarClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); + $method = $class->getMethod('three'); + $this->assertEquals(array('o', 't', 'bbf'), $method->getParameters()); + $parameter = $method->getParameter('t'); + $this->assertInstanceOf('Zend\Code\Scanner\ParameterScanner', $parameter); + $this->assertEquals('t', $parameter->getName()); + } +} diff --git a/tests/Zend/Code/Scanner/ScannerParameterTest.php b/tests/Zend/Code/Scanner/ParameterScannerTest.php similarity index 63% rename from tests/Zend/Code/Scanner/ScannerParameterTest.php rename to tests/Zend/Code/Scanner/ParameterScannerTest.php index c0ace896228..d4543339ce5 100644 --- a/tests/Zend/Code/Scanner/ScannerParameterTest.php +++ b/tests/Zend/Code/Scanner/ParameterScannerTest.php @@ -2,16 +2,16 @@ namespace ZendTest\Code\Scanner; -use Zend\Code\Scanner\ScannerFile; +use Zend\Code\Scanner\FileScanner, + PHPUnit_Framework_TestCase as TestCase; -class ScannerParameterTest extends \PHPUnit_Framework_TestCase +class ParameterScannerTest extends TestCase { - - public function testScannerParamterHasParameterInformation() + public function testParameterScannerHasParameterInformation() { - $file = new ScannerFile(__DIR__ . '/../TestAsset/BarClass.php'); - $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); - $method = $class->getMethod('three'); + $file = new FileScanner(__DIR__ . '/../TestAsset/BarClass.php'); + $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); + $method = $class->getMethod('three'); $parameter = $method->getParameter('t'); $this->assertEquals('ZendTest\Code\TestAsset\BarClass', $parameter->getDeclaringClass()); $this->assertEquals('three', $parameter->getDeclaringFunction()); @@ -23,5 +23,4 @@ public function testScannerParamterHasParameterInformation() $this->assertTrue($parameter->isOptional()); $this->assertTrue($parameter->isPassedByReference()); } - -} \ No newline at end of file +} diff --git a/tests/Zend/Code/Scanner/ScannerFileTest.php b/tests/Zend/Code/Scanner/ScannerFileTest.php deleted file mode 100644 index 6e3890fe051..00000000000 --- a/tests/Zend/Code/Scanner/ScannerFileTest.php +++ /dev/null @@ -1,44 +0,0 @@ -getNamespaces(); - $this->assertInternalType('array', $namespaces); - $this->assertContains('ZendTest\Code\TestAsset', $namespaces); - } - - public function testScannerFileReturnsClassesWithoutScannerClass() - { - $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $classes = $fileScanner->getClasses(); - $this->assertInternalType('array', $classes); - $this->assertContains('ZendTest\Code\TestAsset\FooClass', $classes); - } - - public function testScannerFileReturnsFunctionsWithoutScannerClass() - { - $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/functions.php'); - $functions = $fileScanner->getFunctions(); - $this->assertInternalType('array', $functions); - $this->assertContains('ZendTest\Code\TestAsset\foo_bar', $functions); - } - - public function testScannerFileReturnsClassesWithScannerClass() - { - $fileScanner = new ScannerFile(__DIR__ . '/../TestAsset/FooClass.php'); - $classes = $fileScanner->getClasses(true); - $this->assertInternalType('array', $classes); - $class = array_shift($classes); - $this->assertInstanceOf('Zend\Code\Scanner\ScannerClass', $class); - } - -} - diff --git a/tests/Zend/Code/Scanner/ScannerMethodTest.php b/tests/Zend/Code/Scanner/ScannerMethodTest.php deleted file mode 100644 index fb1448909fa..00000000000 --- a/tests/Zend/Code/Scanner/ScannerMethodTest.php +++ /dev/null @@ -1,44 +0,0 @@ -getClass('ZendTest\Code\TestAsset\FooClass'); - $method = $class->getMethod('fooBarBaz'); - $this->assertEquals('fooBarBaz', $method->getName()); - $this->assertFalse($method->isAbstract()); - $this->assertTrue($method->isFinal()); - $this->assertTrue($method->isPublic()); - $this->assertFalse($method->isProtected()); - $this->assertFalse($method->isPrivate()); - $this->assertFalse($method->isStatic()); - } - - public function testScannerMethodReturnsParameters() - { - $file = new ScannerFile(__DIR__ . '/../TestAsset/BarClass.php'); - $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); - $method = $class->getMethod('three'); - $parameters = $method->getParameters(); - $this->assertInternalType('array', $parameters); - } - - public function testScannerMethodReturnsParameterScanner() - { - $file = new ScannerFile(__DIR__ . '/../TestAsset/BarClass.php'); - $class = $file->getClass('ZendTest\Code\TestAsset\BarClass'); - $method = $class->getMethod('three'); - $this->assertEquals(array('o', 't', 'bbf'), $method->getParameters()); - $parameter = $method->getParameter('t'); - $this->assertInstanceOf('Zend\Code\Scanner\ScannerParameter', $parameter); - $this->assertEquals('t', $parameter->getName()); - } - -} From acc16c9cdde3dce04650c410e91ae48c4db48bad Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 8 Jun 2011 16:15:02 -0500 Subject: [PATCH 39/45] Updated to s/(Scanner)(Foo)/$2$1/ - Changed to follow class renaming in Zend\Code\Scanner component - Minor CS formatting fixes --- library/Zend/Di/Definition/Compiler.php | 34 ++++++++++++------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/library/Zend/Di/Definition/Compiler.php b/library/Zend/Di/Definition/Compiler.php index 9cc78326c8a..09fef5d8194 100644 --- a/library/Zend/Di/Definition/Compiler.php +++ b/library/Zend/Di/Definition/Compiler.php @@ -2,18 +2,21 @@ namespace Zend\Di\Definition; +use Zend\Code\Scanner\ClassScanner, + Zend\Code\Scanner\DirectoryScanner, + Zend\Code\Scanner\FileScanner; + class Compiler { - protected $codeScanners = array(); protected $codeReflectors = array(); - public function addCodeScannerDirectory(\Zend\Code\Scanner\ScannerDirectory $scannerDirectory) + public function addCodeScannerDirectory(DirectoryScanner $scannerDirectory) { $this->codeScanners[] = $scannerDirectory; } - public function addCodeScannerFile(\Zend\Code\Scanner\ScannerFile $scannerFile) + public function addCodeScannerFile(FileScanner $scannerFile) { $this->codeScanners[] = $scannerFile; } @@ -31,16 +34,13 @@ public function compile() $scannerClasses = $codeScanner->getClasses(true); - /* @var $class Zend\Code\Scanner\ScannerClass */ + /* @var $class Zend\Code\Scanner\ClassScanner */ foreach ($scannerClasses as $scannerClass) { if ($scannerClass->isAbstract() || $scannerClass->isInterface()) { continue; } - $className = $scannerClass->getName(); - $data[$className] = array(); - // determine supertypes $superTypes = array(); if (($parentClass = $scannerClass->getParentClass()) !== null) { @@ -50,19 +50,19 @@ public function compile() $superTypes = array_merge($superTypes, $interfaces); } - $data[$className]['superTypes'] = $superTypes; - - $data[$className]['instantiator'] = $this->compileScannerInstantiator($scannerClass); - $data[$className]['injectionMethods'] = $this->compileScannerInjectionMethods($scannerClass); - - + $className = $scannerClass->getName(); + $data[$className] = array( + 'superTypes' => $superTypes, + 'instantiator' => $this->compileScannerInstantiator($scannerClass), + 'injectionMethods' => $this->compileScannerInjectionMethods($scannerClass), + ); } } return new ArrayDefinition($data); } - public function compileScannerInstantiator(\Zend\Code\Scanner\ScannerClass $scannerClass) + public function compileScannerInstantiator(ClassScanner $scannerClass) { if ($scannerClass->hasMethod('__construct')) { $construct = $scannerClass->getMethod('__construct'); @@ -74,12 +74,11 @@ public function compileScannerInstantiator(\Zend\Code\Scanner\ScannerClass $scan return null; // @todo scan parent classes for instantiator - //$scannerClass->getParentClass(); } - public function compileScannerInjectionMethods(\Zend\Code\Scanner\ScannerClass $scannerClass) + public function compileScannerInjectionMethods(ClassScanner $scannerClass) { - $data = array(); + $data = array(); $className = $scannerClass->getName(); foreach ($scannerClass->getMethods(true) as $scannerMethod) { $methodName = $scannerMethod->getName(); @@ -118,5 +117,4 @@ public function hasInjectionMethod($class, $method); public function getInjectionMethods($class); public function getInjectionMethodParameters($class, $method); */ - } From ff07b554be7f01a873803e79bcf1cb214e99d672 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 8 Jun 2011 16:22:46 -0500 Subject: [PATCH 40/45] CS review of tests - Explicit dependencies via imports --- tests/Zend/Di/ConfigurationTest.php | 11 ++++++----- tests/Zend/Di/Definition/ArrayDefinitionTest.php | 11 ++++++----- tests/Zend/Di/Definition/BuilderDefinitionTest.php | 7 ++++--- tests/Zend/Di/Definition/CompilerTest.php | 14 ++++++-------- tests/Zend/Di/Definition/ConfigurationTest.php | 11 ++++++----- tests/Zend/Di/Definition/RuntimeDefinitionTest.php | 11 ++++++----- tests/Zend/Di/DependencyInjectorTest.php | 5 +++-- tests/Zend/Di/InstanceManagerTest.php | 7 ++++--- 8 files changed, 41 insertions(+), 36 deletions(-) diff --git a/tests/Zend/Di/ConfigurationTest.php b/tests/Zend/Di/ConfigurationTest.php index 28867d64267..e0ae932083e 100644 --- a/tests/Zend/Di/ConfigurationTest.php +++ b/tests/Zend/Di/ConfigurationTest.php @@ -2,12 +2,13 @@ namespace ZendTest\Di; -use Zend\Di\Configuration; +use Zend\Di\Configuration, + PHPUnit_Framework_TestCase as TestCase; -class ConfigurationTest extends \PHPUnit_Framework_TestCase +class ConfigurationTest extends TestCase { - public function testTrue() + public function testStub() { - $this->assertTrue(true); // placeholder + $this->markTestIncomplete(); } -} \ No newline at end of file +} diff --git a/tests/Zend/Di/Definition/ArrayDefinitionTest.php b/tests/Zend/Di/Definition/ArrayDefinitionTest.php index b95a7015e5d..a8683a5d521 100644 --- a/tests/Zend/Di/Definition/ArrayDefinitionTest.php +++ b/tests/Zend/Di/Definition/ArrayDefinitionTest.php @@ -2,12 +2,13 @@ namespace ZendTest\Di\Definition; -use Zend\Di\Definition\ArrayDefinition; +use Zend\Di\Definition\ArrayDefinition, + PHPUnit_Framework_TestCase as TestCase; -class ArrayDefinitionTest extends \PHPUnit_Framework_TestCase +class ArrayDefinitionTest extends TestCase { - public function testTrue() + public function testStub() { - $this->assertTrue(true); // placeholder + $this->markTestIncomplete(); } -} \ No newline at end of file +} diff --git a/tests/Zend/Di/Definition/BuilderDefinitionTest.php b/tests/Zend/Di/Definition/BuilderDefinitionTest.php index dadeadb1624..45cea551800 100644 --- a/tests/Zend/Di/Definition/BuilderDefinitionTest.php +++ b/tests/Zend/Di/Definition/BuilderDefinitionTest.php @@ -3,9 +3,10 @@ namespace ZendTest\Di\Definition; use Zend\Di\Definition\BuilderDefinition, - Zend\Di\Definition\Builder; + Zend\Di\Definition\Builder, + PHPUnit_Framework_TestCase as TestCase; -class BuilderDefinitionTest extends \PHPUnit_Framework_TestCase +class BuilderDefinitionTest extends TestCase { public function testBuilderImplementsDefinition() @@ -38,4 +39,4 @@ public function testBuilderCanBuildClassWithMethods() $this->assertEquals(array('bar' => 'Bar'), $definition->getInjectionMethodParameters('Foo', 'injectBar')); } -} \ No newline at end of file +} diff --git a/tests/Zend/Di/Definition/CompilerTest.php b/tests/Zend/Di/Definition/CompilerTest.php index 126dca52c2e..8d7da1b2a68 100644 --- a/tests/Zend/Di/Definition/CompilerTest.php +++ b/tests/Zend/Di/Definition/CompilerTest.php @@ -3,17 +3,15 @@ namespace ZendTest\Di\Definition; use Zend\Di\Definition\Compiler, - Zend\Code\Scanner\ScannerDirectory; + Zend\Code\Scanner\DirectoryScanner, + PHPUnit_Framework_TestCase as TestCase; -class CompilerTest extends \PHPUnit_Framework_TestCase +class CompilerTest extends TestCase { - - - public function testCompilerCompilesAgainstConstructorInjectionAssets() { $compiler = new Compiler; - $compiler->addCodeScannerDirectory(new ScannerDirectory(__DIR__ . '/../TestAsset/CompilerClasses')); + $compiler->addCodeScannerDirectory(new DirectoryScanner(__DIR__ . '/../TestAsset/CompilerClasses')); $definition = $compiler->compile(); $this->assertInstanceOf('Zend\Di\Definition\ArrayDefinition', $definition); @@ -23,8 +21,8 @@ public function testCompilerCompilesAgainstConstructorInjectionAssets() 'ZendTest\Di\TestAsset\CompilerClasses\A', 'ZendTest\Di\TestAsset\CompilerClasses\B', 'ZendTest\Di\TestAsset\CompilerClasses\C', - 'ZendTest\Di\TestAsset\CompilerClasses\D' - ); + 'ZendTest\Di\TestAsset\CompilerClasses\D', + ); $classes = $definition->getClasses(); foreach ($assertClasses as $assertClass) { $this->assertContains($assertClass, $classes); diff --git a/tests/Zend/Di/Definition/ConfigurationTest.php b/tests/Zend/Di/Definition/ConfigurationTest.php index c5bbb92872d..711aa19dab8 100644 --- a/tests/Zend/Di/Definition/ConfigurationTest.php +++ b/tests/Zend/Di/Definition/ConfigurationTest.php @@ -2,12 +2,13 @@ namespace ZendTest\Di\Definition; -use Zend\Di\Definition\Configuration; +use Zend\Di\Definition\Configuration, + PHPUnit_Framework_TestCase as TestCase; -class ConfigurationTest extends \PHPUnit_Framework_TestCase +class ConfigurationTest extends TestCase { - public function testTrue() + public function testStub() { - $this->assertTrue(true); // placeholder + $this->markTestIncomplete(); } -} \ No newline at end of file +} diff --git a/tests/Zend/Di/Definition/RuntimeDefinitionTest.php b/tests/Zend/Di/Definition/RuntimeDefinitionTest.php index 86378a7af8b..23bb73ebbd1 100644 --- a/tests/Zend/Di/Definition/RuntimeDefinitionTest.php +++ b/tests/Zend/Di/Definition/RuntimeDefinitionTest.php @@ -2,12 +2,13 @@ namespace ZendTest\Di\Definition; -use Zend\Di\Definition\RuntimeDefinition; +use Zend\Di\Definition\RuntimeDefinition, + PHPUnit_Framework_TestCase as TestCase; -class RuntimeDefinitionTest extends \PHPUnit_Framework_TestCase +class RuntimeDefinitionTest extends TestCase { - public function testTrue() + public function testStub() { - $this->assertTrue(true); // placeholder + $this->markTestIncomplete(); } -} \ No newline at end of file +} diff --git a/tests/Zend/Di/DependencyInjectorTest.php b/tests/Zend/Di/DependencyInjectorTest.php index 1737e30d124..40dc6347e3a 100755 --- a/tests/Zend/Di/DependencyInjectorTest.php +++ b/tests/Zend/Di/DependencyInjectorTest.php @@ -2,9 +2,10 @@ namespace ZendTest\Di; -use Zend\Di\DependencyInjector; +use Zend\Di\DependencyInjector, + PHPUnit_Framework_TestCase as TestCase; -class DependencyInjectorTest extends \PHPUnit_Framework_TestCase +class DependencyInjectorTest extends TestCase { public function testDependencyInjectorWillUsePokeYokeInstanceManager() { diff --git a/tests/Zend/Di/InstanceManagerTest.php b/tests/Zend/Di/InstanceManagerTest.php index d0ce02ecfdc..132b3927d8b 100644 --- a/tests/Zend/Di/InstanceManagerTest.php +++ b/tests/Zend/Di/InstanceManagerTest.php @@ -2,9 +2,10 @@ namespace ZendTest\Di; -use Zend\Di\InstanceManager; +use Zend\Di\InstanceManager, + PHPUnit_Framework_TestCase as TestCase; -class InstanceManagerTest extends \PHPUnit_Framework_TestCase +class InstanceManagerTest extends TestCase { public function testInstanceManagerImplementsInterface() @@ -31,4 +32,4 @@ public function testInstanceManagerCanPersistProperties() $this->assertEquals('bar', $im->getProperty('ZendTest\Di\TestAsset\BasicClass', 'foo')); } -} \ No newline at end of file +} From bccb02e67f62fcc33b54588fbf9007596c6a0c06 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 8 Jun 2011 16:45:20 -0500 Subject: [PATCH 41/45] Moved resolveUseFunc closures to common static method - Created "Util" static class with "resolveImports" static method - Updated ClassScanner and ParameterScanner to use this static method for resolving imports --- library/Zend/Code/Scanner/ClassScanner.php | 22 +------ .../Zend/Code/Scanner/ParameterScanner.php | 25 +------- library/Zend/Code/Scanner/Util.php | 62 +++++++++++++++++++ 3 files changed, 66 insertions(+), 43 deletions(-) create mode 100644 library/Zend/Code/Scanner/Util.php diff --git a/library/Zend/Code/Scanner/ClassScanner.php b/library/Zend/Code/Scanner/ClassScanner.php index 4c049e90125..8d88b7afa93 100644 --- a/library/Zend/Code/Scanner/ClassScanner.php +++ b/library/Zend/Code/Scanner/ClassScanner.php @@ -152,29 +152,11 @@ protected function scanClassInfo($tokenIndex, &$fastForward) $namespace = $this->namespace; $uses = $this->uses; - $resolveUseFunc = function (&$value, $key = null) use (&$namespace, &$uses) { - if (!$uses || strlen($value) <= 0 || $value{0} == '\\') { - $value = ltrim($value, '\\'); - return; - } - - if ($namespace || $uses) { - $firstPartEnd = (strpos($value, '\\')) ?: strlen($value-1); - $firstPart = substr($value, 0, $firstPartEnd); - if (array_key_exists($firstPart, $uses)) { - $value = substr_replace($value, $uses[$firstPart], 0, $firstPartEnd); - return; - } - if ($namespace) { - $value = $namespace . '\\' . $value; - return; - } - } - }; if ($this->shortInterfaces) { $this->interfaces = $this->shortInterfaces; - array_walk($this->interfaces, $resolveUseFunc); + $data = (object) array('namespace' => $namespace, 'uses' => $uses); + array_walk($this->interfaces, array('Zend\Code\Scanner\Util', 'resolveImports'), $data); } if ($this->shortParentClass) { diff --git a/library/Zend/Code/Scanner/ParameterScanner.php b/library/Zend/Code/Scanner/ParameterScanner.php index dca2065ce33..3813423c8e2 100644 --- a/library/Zend/Code/Scanner/ParameterScanner.php +++ b/library/Zend/Code/Scanner/ParameterScanner.php @@ -149,32 +149,11 @@ protected function scan() } while ($token); if ($this->class) { - // create function to resolve short names with uses - $uses = $this->uses; - $resolveUseFunc = function (&$value, $key = null) use (&$namespace, &$uses) { - if (!$uses || strlen($value) <= 0 || $value{0} == '\\') { - $value = ltrim($value, '\\'); - return; - } - - if ($namespace || $uses) { - $firstPartEnd = (strpos($value, '\\')) ?: strlen($value-1); - $firstPart = substr($value, 0, $firstPartEnd); - if (array_key_exists($firstPart, $uses)) { - $value = substr_replace($value, $uses[$firstPart], 0, $firstPartEnd); - return; - } - if ($namespace) { - $value = $namespace . '\\' . $value; - return; - } - } - }; - if ($this->shortInterfaces) { $this->interfaces = $this->shortInterfaces; - array_walk($this->interfaces, $resolveUseFunc); + $data = (object) array('namespace' => $namespace, 'uses' => $uses); + array_walk($this->interfaces, array('Zend\Code\Scanner\Util', 'resolveImports'), $data); } } diff --git a/library/Zend/Code/Scanner/Util.php b/library/Zend/Code/Scanner/Util.php new file mode 100644 index 00000000000..ba02ff8cf8b --- /dev/null +++ b/library/Zend/Code/Scanner/Util.php @@ -0,0 +1,62 @@ +uses || strlen($value) <= 0 || $value{0} == '\\') { + $value = ltrim($value, '\\'); + return; + } + + if ($data->namespace || $data->uses) { + $firstPartEnd = (strpos($value, '\\')) ?: strlen($value-1); + $firstPart = substr($value, 0, $firstPartEnd); + if (array_key_exists($firstPart, $data->uses)) { + $value = substr_replace($value, $data->uses[$firstPart], 0, $firstPartEnd); + return; + } + if ($data->namespace) { + $value = $data->namespace . '\\' . $value; + return; + } + } + } +} From 091960615cace07ae2ed39185727db4cb5add5aa Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Wed, 8 Jun 2011 16:58:37 -0500 Subject: [PATCH 42/45] Fixed reference to former closure - changed call to $resolveUseFunc to Util::resolveImports() --- library/Zend/Code/Scanner/ClassScanner.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/library/Zend/Code/Scanner/ClassScanner.php b/library/Zend/Code/Scanner/ClassScanner.php index 8d88b7afa93..fd7e177193d 100644 --- a/library/Zend/Code/Scanner/ClassScanner.php +++ b/library/Zend/Code/Scanner/ClassScanner.php @@ -148,20 +148,19 @@ protected function scanClassInfo($tokenIndex, &$fastForward) } - // create function to resolve short names with uses - $namespace = $this->namespace; - $uses = $this->uses; + $data = (object) array( + 'namespace' => $this->namespace, + 'uses' => $this->uses, + ); - if ($this->shortInterfaces) { $this->interfaces = $this->shortInterfaces; - $data = (object) array('namespace' => $namespace, 'uses' => $uses); array_walk($this->interfaces, array('Zend\Code\Scanner\Util', 'resolveImports'), $data); } if ($this->shortParentClass) { $this->parentClass = $this->shortParentClass; - $resolveUseFunc($this->parentClass); + Util::resolveImports($this->parentClass, null, $data); } } From fd1d68378a0a575cb931a60685bbf33e3079e9ae Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Thu, 9 Jun 2011 13:59:53 -0500 Subject: [PATCH 43/45] Improved dependency injection detection based on ezimuels original implementation pushed into runtime in turn based on weierophinneys Di prototype --- library/Zend/Di/DependencyInjector.php | 63 +++++------------------- tests/Zend/Di/DependencyInjectorTest.php | 22 ++++++++- 2 files changed, 31 insertions(+), 54 deletions(-) diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php index 6b2d5738000..46727a63767 100755 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -19,7 +19,7 @@ class DependencyInjector implements DependencyInjection * * @var array */ - protected $dependencies = array(); + protected $currentDependencies = array(); /** * All the class references [dependency][source] @@ -113,16 +113,16 @@ public function get($name, array $params = array()) */ public function newInstance($name, array $params = array(), $isShared = true) { - $this->getDefinition(); + $definition = $this->getDefinition(); $class = $this->getInstanceManager()->getClassFromAlias($name); - if (!$this->definition->hasClass($class)) { + if (!$definition->hasClass($class)) { throw new Exception\ClassNotFoundException('Class or alias name ' . $name . ' could not be located in provided definition.'); } - $instantiator = $this->definition->getInstantiator($class); - $injectionMethods = $this->definition->getInjectionMethods($class); + $instantiator = $definition->getInstantiator($class); + $injectionMethods = $definition->getInjectionMethods($class); if ($instantiator === '__construct') { $object = $this->createInstanceViaConstructor($class, $params); @@ -246,64 +246,23 @@ protected function resolveMethodParameters($class, $method, array $params, $isIn // circular dep check if ($isInstantiator && $value !== null) { - $this->dependencies[$class][$value]= true; - $this->checkCircularDependency($class, $value); + if (in_array($value, $this->currentDependencies)) { + throw new Exception\CircularDependencyException("Circular dependency detected: $class depends on $value and viceversa"); + } + //$this->checkCircularDependency($class, $value); } if ($value === null) { $resultParams[$index] = $params[$name]; } else { + array_push($this->currentDependencies, $class); $resultParams[$index] = $this->get($value, $params); + array_pop($this->currentDependencies); } $index++; } return $resultParams; } - - /** - * Check for Circular Dependencies - * - * @param string $class - * @param array|string $dependency - * @return boolean - */ - protected function checkCircularDependency($class, $dependency) - { - if (is_array($dependency)) { - foreach ($dependency as $dep) { - if (isset($this->dependencies[$dep][$class]) && $this->dependencies[$dep][$class]) { - throw new Exception\CircularDependencyException("Circular dependency detected: $class depends on $dep and viceversa"); - } - } - } else { - if (isset($this->dependencies[$dependency][$class]) && $this->dependencies[$dependency][$class]) { - throw new Exception\CircularDependencyException("Circular dependency detected: $class depends on $dependency and viceversa"); - } - } - return true; - } - - /** - * Check the circular dependencies path between two definitions - * - * @param type $class - * @param type $dependency - * @return void - */ - /* - protected function checkPathDependencies($class, $dependency) - { - if (!empty($this->references[$class])) { - foreach ($this->references[$class] as $key => $value) { - if ($this->dependencies[$key][$class]) { - $this->dependencies[$key][$dependency] = true; - $this->checkCircularDependency($key, $dependency); - $this->checkPathDependencies($key,$dependency); - } - } - } - } - */ } diff --git a/tests/Zend/Di/DependencyInjectorTest.php b/tests/Zend/Di/DependencyInjectorTest.php index 40dc6347e3a..2ef6cf7d27d 100755 --- a/tests/Zend/Di/DependencyInjectorTest.php +++ b/tests/Zend/Di/DependencyInjectorTest.php @@ -235,6 +235,7 @@ public function testNewInstanceWillResolveSetterInjectionDependenciesWithPropert * Test for Circular Dependencies (case 1) * * A->B, B->A + * @group CircurlarDependencyCheck */ public function testNewInstanceThrowsExceptionOnBasicCircularDependency() { @@ -248,17 +249,34 @@ public function testNewInstanceThrowsExceptionOnBasicCircularDependency() * Test for Circular Dependencies (case 2) * * C->D, D->E, E->C + * @group CircurlarDependencyCheck */ public function testNewInstanceThrowsExceptionOnThreeLevelCircularDependency() { - $this->markTestSkipped('This currently does not work'); $di = new DependencyInjector(); $this->setExpectedException( 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\B depends on ZendTest\Di\TestAsset\CircularClasses\A and viceversa' + 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\E depends on ZendTest\Di\TestAsset\CircularClasses\C and viceversa' ); $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\C'); } + /** + * Test for Circular Dependencies (case 2) + * + * C->D, D->E, E->C + * @group CircurlarDependencyCheck + */ + public function testNewInstanceThrowsExceptionWhenEnteringInMiddleOfCircularDependency() + { + $di = new DependencyInjector(); + + $this->setExpectedException( + 'Zend\Di\Exception\CircularDependencyException', + 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\C depends on ZendTest\Di\TestAsset\CircularClasses\D and viceversa' + ); + $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\D'); + } + } From b19a43f3d2c6d76e10c531b4be06add434e977d5 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Mon, 13 Jun 2011 00:36:54 -0500 Subject: [PATCH 44/45] Altered Zend\Di with additional tests, ability to hash parameter calls, and ability to configure the component --- .../Zend/Code/Scanner/TokenArrayScanner.php | 2 +- library/Zend/Di/Configuration.php | 104 +++++++- .../Di/Definition/AggregateDefinition.php | 10 +- .../Zend/Di/Definition/BuilderDefinition.php | 34 +++ .../Zend/Di/Definition/RuntimeDefinition.php | 15 +- library/Zend/Di/DependencyInjector.php | 205 +++++++++++---- library/Zend/Di/InstanceCollection.php | 9 +- library/Zend/Di/InstanceManager.php | 247 +++++++++++++++--- tests/Zend/Di/ConfigurationTest.php | 66 ++++- .../Di/Definition/ArrayDefinitionTest.php | 72 ++++- .../Di/Definition/BuilderDefinitionTest.php | 33 +++ .../Zend/Di/Definition/ConfigurationTest.php | 14 - tests/Zend/Di/DependencyInjectorTest.php | 8 +- tests/Zend/Di/InstanceManagerTest.php | 18 ++ tests/Zend/Di/_files/definition-array.php | 67 +++++ tests/Zend/Di/_files/sample.ini | 22 ++ 16 files changed, 804 insertions(+), 122 deletions(-) delete mode 100644 tests/Zend/Di/Definition/ConfigurationTest.php create mode 100644 tests/Zend/Di/_files/definition-array.php create mode 100644 tests/Zend/Di/_files/sample.ini diff --git a/library/Zend/Code/Scanner/TokenArrayScanner.php b/library/Zend/Code/Scanner/TokenArrayScanner.php index c7dad68ea56..f142dd7a5af 100644 --- a/library/Zend/Code/Scanner/TokenArrayScanner.php +++ b/library/Zend/Code/Scanner/TokenArrayScanner.php @@ -51,7 +51,7 @@ protected function scan() $fastForward = 0; switch ($token[0]) { case T_DOC_COMMENT: - echo 'Found Doc Comment' . PHP_EOL; + // @todo Implement docblock scanner break; case T_NAMESPACE: diff --git a/library/Zend/Di/Configuration.php b/library/Zend/Di/Configuration.php index a5a71c200f2..0b2d8ae1314 100644 --- a/library/Zend/Di/Configuration.php +++ b/library/Zend/Di/Configuration.php @@ -6,21 +6,121 @@ class Configuration { protected $data = array(); + /** + * @var Zend\Di\DependencyInjector + */ + protected $di = null; + public function __construct($data) { if ($data instanceof \Zend\Config\Config) { $data = $data->toArray(); + } elseif (!is_array($data)) { + throw new Exception\InvalidArgumentException('Configuration data must be of type Zend\Config\Config or an array'); + } + $this->data = $data; + } + + public function configure(DependencyInjector $di) + { + if (isset($this->data['definition'])) { + $this->configureDefinition($di, $this->data['definition']); + } + + if (isset($this->data['definitions'])) { + $this->configureDefinitions($di, $this->data['definitions']); + } + + /* + if (isset($this->data['compiler'])) { + $this->configureCompiler($di, $this->data['compiler']); + } + */ + + if (isset($this->data['instance'])) { + $this->configureInstance($di, $this->data['instance']); } + } - public function getDefinition() + public function configureDefinitions(DependencyInjector $di, $definitionsData) { + if ($di->hasDefinition()) { + if (!$di->getDefinition() instanceof Definition\AggregateDefinition) { + throw new Exception\InvalidArgumentException('In order to configure multiple definitions, the primary definition must not be set, or must be of type AggregateDefintion'); + } + } else { + $di->setDefinition($di->createDefinition('Zend\Di\Definition\AggregateDefinition')); + } + + foreach ($definitionsData as $definitionData) { + $this->configureDefinition($di, $definitionData); + } + } + + public function configureDefinition(DependencyInjector $di, $definitionData) + { + if ($di->hasDefinition()) { + $aggregateDef = $di->getDefinition(); + if (!$aggregateDef instanceof Definition\AggregateDefinition) { + throw new Exception\InvalidArgumentException('In order to configure multiple definitions, the primary definition must not be set, or must be of type AggregateDefintion'); + } + } /* else { + $aggregateDef = $di->createDefinition('Zend\Di\Definition\AggregateDefinition'); + $di->setDefinition($aggregateDef); + } */ + + if (isset($definitionData['class'])) { + $definition = $di->createDefinition($definitionData['class']); + unset($definitionData['class']); + if ($definition instanceof Definition\BuilderDefinition) { + $definition->createClassesFromArray($definitionData); + } else { + // @todo other types + } + } + if (isset($aggregateDef)) { + $aggregateDef->addDefinition($definition); + } else { + $di->setDefinition($definition); + } } - public function getInstanceManager() + public function configureInstance(DependencyInjector $di, $instanceData) { + $im = $di->getInstanceManager(); + foreach ($instanceData as $target => $data) { + switch (strtolower($target)) { + case 'aliases': + case 'alias': + foreach ($data as $aliasName => $className) { + $im->addAlias($aliasName, $className); + } + break; + case 'properties': + case 'property': + foreach ($data as $classOrAlias => $properties) { + foreach ($properties as $propName => $propValue) { + $im->setProperty($classOrAlias, $propName, $propValue); + } + } + break; + case 'preferences': + case 'preferredinstances': + case 'preferredinstance': + foreach ($data as $classOrAlias => $preferredValueOrValues) { + if (is_array($preferredValueOrValues)) { + foreach ($preferredValueOrValues as $preferredValue) { + $im->addPreferredInstance($classOrAlias, $preferredValue); + } + } else { + $im->addPreferredInstance($classOrAlias, $preferredValueOrValues); + } + } + } + } } } \ No newline at end of file diff --git a/library/Zend/Di/Definition/AggregateDefinition.php b/library/Zend/Di/Definition/AggregateDefinition.php index 4a5a18de219..a81926a6b77 100644 --- a/library/Zend/Di/Definition/AggregateDefinition.php +++ b/library/Zend/Di/Definition/AggregateDefinition.php @@ -10,7 +10,7 @@ class AggregateDefinition implements Definition protected $definitions = array(); - public function addDefinition(DefinitionInterface $definition) + public function addDefinition(Definition $definition) { $this->definitions[] = $definition; } @@ -38,7 +38,7 @@ public function getClassSupertypes($class) { $superTypes = array(); foreach ($this->definitions as $definition) { - $superTypes = array_merge($superTypes, $definition->getSuperTypes()); + $superTypes = array_merge($superTypes, $definition->getClassSupertypes()); } return $superTypes; } @@ -67,7 +67,7 @@ public function hasInjectionMethod($class, $method) { foreach ($this->definitions as $definition) { if ($definition->hasClass($class)) { - return $definition->hasInjectionMethod($class); + return $definition->hasInjectionMethod($class, $method); } } return false; @@ -86,8 +86,8 @@ public function getInjectionMethods($class) public function getInjectionMethodParameters($class, $method) { foreach ($this->definitions as $definition) { - if ($definition->hasClass($class)) { - return $definition->getInjectionMethodParameters($class); + if ($definition->hasClass($class) && $definition->hasInjectionMethod($class, $method)) { + return $definition->getInjectionMethodParameters($class, $method); } } return false; diff --git a/library/Zend/Di/Definition/BuilderDefinition.php b/library/Zend/Di/Definition/BuilderDefinition.php index a3161d57222..24f90ec5140 100644 --- a/library/Zend/Di/Definition/BuilderDefinition.php +++ b/library/Zend/Di/Definition/BuilderDefinition.php @@ -9,6 +9,40 @@ class BuilderDefinition implements Definition { protected $classes = array(); + + public function createClassesFromArray(array $builderData) + { + foreach ($builderData as $className => $classInfo) { + $class = new Builder\PhpClass(); + $class->setName($className); + foreach ($classInfo as $type => $typeData) { + switch (strtolower($type)) { + case 'supertypes': + foreach ($typeData as $superType) { + $class->addSuperType($superType); + } + break; + case 'instantiator': + $class->setInstantiator($typeData); + break; + case 'injectionmethods': + case 'injectionmethod': + foreach ($typeData as $injectionMethodName => $injectionMethodData) { + $injectionMethod = new Builder\InjectionMethod(); + $injectionMethod->setName($injectionMethodName); + foreach ($injectionMethodData as $parameterName => $parameterType) { + $parameterType = ($parameterType) ?: null; // force empty string to null + $injectionMethod->addParameter($parameterName, $parameterType); + } + $class->addInjectionMethod($injectionMethod); + } + break; + + } + } + $this->addClass($class); + } + } public function addClass(Builder\PhpClass $phpClass) { diff --git a/library/Zend/Di/Definition/RuntimeDefinition.php b/library/Zend/Di/Definition/RuntimeDefinition.php index 27b3065743f..0714903e2af 100644 --- a/library/Zend/Di/Definition/RuntimeDefinition.php +++ b/library/Zend/Di/Definition/RuntimeDefinition.php @@ -19,6 +19,16 @@ public function getClasses() return array(); } + /** + * Set the Lookup Type + * + * @param string $lookupType + */ + public function setLookupType($lookupType) + { + $this->lookupType = $lookupType; + } + /** * Track classes when using EXPLICIT lookups * @param unknown_type $class @@ -28,11 +38,6 @@ public function addClass($class) $this->classes[] = $class; } - public function setLookupType($lookupType) - { - $this->lookupType = $lookupType; - } - public function hasClass($class) { return class_exists($class, true); diff --git a/library/Zend/Di/DependencyInjector.php b/library/Zend/Di/DependencyInjector.php index 46727a63767..13c29ee54e1 100755 --- a/library/Zend/Di/DependencyInjector.php +++ b/library/Zend/Di/DependencyInjector.php @@ -10,7 +10,7 @@ class DependencyInjector implements DependencyInjection protected $definition = null; /** - * @var Zend\Di\InstanceCollection + * @var Zend\Di\InstanceManager */ protected $instanceManager = null; @@ -28,21 +28,19 @@ class DependencyInjector implements DependencyInjection */ protected $references = array(); - protected $circularDependencyCheck = true; - /** * @param Zend\DI\Configuration $config */ public function __construct(Configuration $config = null) { if ($config) { - $this->setConfiguration($config); + $this->configure($config); } } - public function setConfiguration(Configuration $config) + public function configure(Configuration $config) { - // @todo process this + $config->configure($this); } public function setDefinition(Definition $definition) @@ -51,23 +49,69 @@ public function setDefinition(Definition $definition) return $this; } + /** + * Definition Factory + * + * @param string $class + */ + public function createDefinition($class) + { + $definition = new $class(); + if (!$definition instanceof Definition) { + throw new Exception\InvalidArgumentException('The class provided to the Definition factory ' . $class . ' does not implement the Definition interface'); + } + return $definition; + } + + public function hasDefinition() + { + return ($this->definition !== null); + } public function getDefinition() { if ($this->definition == null) { - $this->definition = new Definition\RuntimeDefinition(); + $this->definition = $this->createDefinition('Zend\Di\Definition\RuntimeDefinition'); } return $this->definition; } /** + * @return bool + */ + public function hasInstanceManager() + { + return ($this->instanceManager !== null); + } + + public function setInstanceManager(InstanceCollection $instanceManager) + { + $this->instanceManager = $instanceManager; + return $this; + } + + /** + * InstanceManager factory * + * @param string $class + * @return Zend\Di\InstanceManager + */ + public function createInstanceManager($class) + { + $instanceManager = new $class(); + if (!$instanceManager instanceof InstanceCollection) { + throw new Exception\InvalidArgumentException('The class provided to the InstanceManager factory ' . $class . ' does not implement the InstanceCollection interface'); + } + return $instanceManager; + } + + /** * @return Zend\Di\InstanceManager */ public function getInstanceManager() { if ($this->instanceManager == null) { - $this->instanceManager = new InstanceManager(); + $this->instanceManager = $this->createInstanceManager('Zend\Di\InstanceManager'); } return $this->instanceManager; } @@ -85,19 +129,17 @@ public function getInstanceManager() */ public function get($name, array $params = array()) { - /* - if ($params) { - throw new \Exception('Implementation not complete: get needs to hash params'); - } - */ - $im = $this->getInstanceManager(); - // Cached instance - if ($im->hasSharedInstance($name, $params)) { - return $im->getSharedInstance($name, $params); + if ($params) { + if (($fastHash = $im->hasSharedInstanceWithParameters($name, $params, true))) { + return $im->getSharedInstanceWithParameters(null, array(), $fastHash); + } + } else { + if ($im->hasSharedInstance($name, $params)) { + return $im->getSharedInstance($name, $params); + } } - return $this->newInstance($name, $params); } @@ -114,18 +156,26 @@ public function get($name, array $params = array()) public function newInstance($name, array $params = array(), $isShared = true) { $definition = $this->getDefinition(); - - $class = $this->getInstanceManager()->getClassFromAlias($name); + $instanceManager = $this->getInstanceManager(); + if ($instanceManager->hasAlias($name)) { + $class = $instanceManager->getClassFromAlias($name); + $alias = $name; + } else { + $class = $name; + $alias = null; + } + if (!$definition->hasClass($class)) { - throw new Exception\ClassNotFoundException('Class or alias name ' . $name . ' could not be located in provided definition.'); + $aliasMsg = ($alias) ? '(specified by alias ' . $alias . ') ' : ''; + throw new Exception\ClassNotFoundException('Class ' . $aliasMsg . $class . ' could not be located in provided definition.'); } $instantiator = $definition->getInstantiator($class); $injectionMethods = $definition->getInjectionMethods($class); if ($instantiator === '__construct') { - $object = $this->createInstanceViaConstructor($class, $params); + $object = $this->createInstanceViaConstructor($class, $params, $alias); if (in_array('__construct', $injectionMethods)) { unset($injectionMethods[array_search('__construct', $injectionMethods)]); } @@ -139,12 +189,16 @@ public function newInstance($name, array $params = array(), $isShared = true) if ($injectionMethods) { foreach ($injectionMethods as $injectionMethod) { - $this->handleInjectionMethodForObject($object, $injectionMethod, $params); + $this->handleInjectionMethodForObject($object, $injectionMethod, $params, $alias); } } if ($isShared) { - $this->getInstanceManager()->addSharedInstance($object, $class, $params); + if ($params) { + $this->getInstanceManager()->addSharedInstanceWithParameters($object, $name, $params); + } else { + $this->getInstanceManager()->addSharedInstance($object, $name); + } } return $object; @@ -161,11 +215,11 @@ public function newInstance($name, array $params = array(), $isShared = true) * @param array $params * @return object */ - protected function createInstanceViaConstructor($class, $params) + protected function createInstanceViaConstructor($class, $params, $alias = null) { $callParameters = array(); if ($this->definition->hasInjectionMethod($class, '__construct')) { - $callParameters = $this->resolveMethodParameters($class, '__construct', $params, true); + $callParameters = $this->resolveMethodParameters($class, '__construct', $params, true, $alias); } // Hack to avoid Reflection in most common use cases @@ -219,10 +273,10 @@ protected function createInstanceViaCallback($callback, $params) * @param string $method * @param array $params */ - protected function handleInjectionMethodForObject($object, $method, $params) + protected function handleInjectionMethodForObject($object, $method, $params, $alias) { // @todo make sure to resolve the supertypes for both the object & definition - $callParameters = $this->resolveMethodParameters(get_class($object), $method, $params); + $callParameters = $this->resolveMethodParameters(get_class($object), $method, $params, false, $alias); call_user_func_array(array($object, $method), $callParameters); } @@ -232,37 +286,98 @@ protected function handleInjectionMethodForObject($object, $method, $params) * @param array $params * @return array */ - protected function resolveMethodParameters($class, $method, array $params, $isInstantiator = false) + protected function resolveMethodParameters($class, $method, array $userParams, $isInstantiator, $alias) { - $resultParams = array(); + $resolvedParams = array(); - $params = array_merge($params, $this->getInstanceManager()->getProperties($class)); + $injectionMethodParameters = $this->definition->getInjectionMethodParameters($class, $method); - $index = 0; - foreach ($this->definition->getInjectionMethodParameters($class, $method) as $name => $value) { - if ($value === null && !array_key_exists($name, $params)) { - throw new Exception\MissingPropertyException('Missing parameter named ' . $name . ' for ' . $class . '::' . $method); + $computedValueParams = array(); + $computedLookupParams = array(); + + foreach ($injectionMethodParameters as $name => $type) { + //$computedValueParams[$name] = null; + + // first consult user provided parameters + if (isset($userParams[$name])) { + if (is_string($userParams[$name])) { + if ($this->instanceManager->hasAlias($userParams[$name])) { + $computedLookupParams[$name] = array($userParams[$name], $this->instanceManager->getClassFromAlias($userParams[$name])); + } elseif ($this->definition->hasClass($userParams[$name])) { + $computedLookupParams[$name] = array($userParams[$name], $userParams[$name]); + } else { + $computedValueParams[$name] = $userParams[$name]; + } + } else { + $computedValueParams[$name] = $userParams[$name]; + } + continue; } - // circular dep check - if ($isInstantiator && $value !== null) { - if (in_array($value, $this->currentDependencies)) { - throw new Exception\CircularDependencyException("Circular dependency detected: $class depends on $value and viceversa"); + // next consult alias specific properties + if ($alias && $this->instanceManager->hasProperty($alias, $name)) { + $computedValueParams[$name] = $this->instanceManager->getProperty($alias, $name); + continue; + } + + // next consult alias level preferred instances + if ($alias && $this->instanceManager->hasPreferredInstances($alias)) { + $pInstances = $this->instanceManager->getPreferredInstances($alias); + foreach ($pInstances as $pInstance) { + $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? + $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; + if ($pInstanceClass === $type || is_subclass_of($pInstanceClass, $type)) { + $computedLookupParams[$name] = array($pInstance, $pInstanceClass); + continue; + } } - //$this->checkCircularDependency($class, $value); } - if ($value === null) { - $resultParams[$index] = $params[$name]; - } else { + // next consult class level preferred instances + if ($type && $this->instanceManager->hasPreferredInstances($type)) { + $pInstances = $this->instanceManager->getPreferredInstances($type); + foreach ($pInstances as $pInstance) { + $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? + $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; + if ($pInstanceClass === $type || is_subclass_of($pInstanceClass, $type)) { + $computedLookupParams[$name] = array($pInstance, $pInstanceClass); + continue; + } + } + } + + // finally consult alias specific properties + if ($this->instanceManager->hasProperty($class, $name)) { + $computedValueParams[$name] = $this->instanceManager->getProperty($class, $name); + continue; + } + + if ($type) { + $computedLookupParams[$name] = array($type, $type); + } + + } + + $index = 0; + foreach ($injectionMethodParameters as $name => $value) { + + if (isset($computedValueParams[$name])) { + $resolvedParams[$index] = $computedValueParams[$name]; + } elseif (isset($computedLookupParams[$name])) { + if ($isInstantiator && in_array($computedLookupParams[$name][1], $this->currentDependencies)) { + throw new Exception\CircularDependencyException("Circular dependency detected: $class depends on $value and viceversa"); + } array_push($this->currentDependencies, $class); - $resultParams[$index] = $this->get($value, $params); + $resolvedParams[$index] = $this->get($computedLookupParams[$name][0], $userParams); array_pop($this->currentDependencies); + } else { + throw new Exception\MissingPropertyException('Missing parameter named ' . $name . ' for ' . $class . '::' . $method); } + $index++; } - return $resultParams; + return $resolvedParams; } } diff --git a/library/Zend/Di/InstanceCollection.php b/library/Zend/Di/InstanceCollection.php index 2be7a3b1625..13b6779c931 100644 --- a/library/Zend/Di/InstanceCollection.php +++ b/library/Zend/Di/InstanceCollection.php @@ -4,9 +4,12 @@ interface InstanceCollection { - public function hasSharedInstance($classOrAlias, array $params = array()); - public function getSharedInstance($classOrAlias, array $params = array()); - public function addSharedInstance($object, $class, array $params = array()); + public function hasSharedInstance($classOrAlias); + public function getSharedInstance($classOrAlias); + public function addSharedInstance($instance, $class); + public function hasSharedInstanceWithParameters($classOrAlias, array $params); + public function getSharedInstanceWithParameters($classOrAlias, array $params); + public function addSharedInstanceWithParameters($instance, $class, array $params); public function getClassFromAlias($alias); public function addAlias($class, $alias); public function hasProperties($classOrAlias); diff --git a/library/Zend/Di/InstanceManager.php b/library/Zend/Di/InstanceManager.php index 6e73b383c94..d06ccef52ab 100644 --- a/library/Zend/Di/InstanceManager.php +++ b/library/Zend/Di/InstanceManager.php @@ -5,13 +5,13 @@ class InstanceManager implements InstanceCollection { /** - * @todo Implement parameter hashing to determine which object is truely new - * @var array + * Preferred Instances for classes and aliases + * @var unknown_type */ - protected $parameterHashes = array(); + protected $preferredInstances = array(); /** - * Enter description here ... + * Properties array * @var array */ protected $properties = array(); @@ -22,6 +22,8 @@ class InstanceManager implements InstanceCollection */ protected $sharedInstances = array(); + protected $sharedInstancesWithParams = array('hashShort' => array(), 'hashLong' => array()); + /** * Array of class aliases * @var array @@ -29,62 +31,180 @@ class InstanceManager implements InstanceCollection protected $aliases = array(); /** - * (non-PHPdoc) - * @see Zend\Di.InstanceCollection::hasSharedInstance() + * Does this instance manager have this shared instance */ - public function hasSharedInstance($class, array $params = array()) + public function hasSharedInstance($classOrAlias) { - return isset($this->sharedInstances[$class]); + return isset($this->sharedInstances[$classOrAlias]); } /** - * (non-PHPdoc) - * @see Zend\Di.InstanceCollection::getSharedInstance() + * getSharedInstance() */ - public function getSharedInstance($class, array $params = array()) + public function getSharedInstance($classOrAlias) { - return $this->sharedInstances[$class]; + return $this->sharedInstances[$classOrAlias]; } /** - * (non-PHPdoc) - * @see Zend\Di.InstanceCollection::addSharedInstance() + * addSharedInstance() */ - public function addSharedInstance($object, $class, array $params = array()) + public function addSharedInstance($instance, $classOrAlias) + { + if (!is_object($instance)) { + throw new Exception\InvalidArgumentException('This method requires an object to be shared'); + } + + $this->sharedInstances[$classOrAlias] = $instance; + } + + public function hasSharedInstanceWithParameters($classOrAlias, array $params, $returnFashHashLookupKey = false) { - $this->sharedInstances[$class] = $object; + ksort($params); + $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); + if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) { + $hashValue = $this->createHashForValues($classOrAlias, $params); + if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) { + return ($returnFashHashLookupKey) ? $hashKey . '/' . $hashValue : true; + } + } + return false; + } + + public function addSharedInstanceWithParameters($instance, $classOrAlias, array $params) + { + ksort($params); + $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); + $hashValue = $this->createHashForValues($classOrAlias, $params); + + if (!isset($this->sharedInstancesWithParams[$hashKey]) || !is_array($this->sharedInstancesWithParams[$hashKey])) { + $this->sharedInstancesWithParams[$hashKey] = array(); + } + + $this->sharedInstancesWithParams['hashShort'][$hashKey] = true; + $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue] = $instance; + } + + public function getSharedInstanceWithParameters($classOrAlias, array $params, $fastHashFromHasLookup = null) + { + if ($fastHashFromHasLookup) { + return $this->sharedInstancesWithParams['hashLong'][$fastHashFromHasLookup]; + } + + ksort($params); + $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); + if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) { + $hashValue = $this->createHashForValues($classOrAlias, $params); + if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) { + return $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue]; + } + } + return false; + } + + + public function hasAlias($alias) + { + return array_key_exists($alias, $this->aliases); + } + + public function getAliases() + { + return $this->aliases; } /** - * (non-PHPdoc) - * @see Zend\Di.InstanceCollection::getClassFromAlias() + * getClassFromAlias() */ public function getClassFromAlias($alias) { if (isset($this->aliases[$alias])) { return $this->aliases[$alias]; } - return $alias; // must be a class? + return false; } /** * (non-PHPdoc) * @see Zend\Di.InstanceCollection::addAlias() */ - public function addAlias($class, $alias, $params = array()) + public function addAlias($alias, $class, array $properties = array(), array $preferredInstances = array()) { - // @todo impelement params for aliases + if (!preg_match('#^[a-zA-Z0-9-_]+$#', $alias)) { + throw new Exception\InvalidArgumentException('Aliases must be alphanumeric and can contain dashes and underscores only.'); + } $this->aliases[$alias] = $class; + if ($properties) { + $this->setProperties($alias, $properties); + } + if ($preferredInstances) { + $this->setPreferredInstances($alias, $preferredInstances); + } } + public function hasPreferredInstances($classOrAlias) + { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + return (isset($this->preferredInstances[$key]) && $this->preferredInstances[$key]); + } + + public function setPreferredInstances($classOrAlias, array $preferredInstances) + { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + foreach ($preferredInstances as $preferredInstance) { + $this->addPreferredInstance($key, $preferredInstance); + } + return $this; + } + + public function getPreferredInstances($classOrAlias) + { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (isset($this->preferredInstances[$key])) { + return $this->preferredInstances[$key]; + } + return array(); + } + + public function unsetPreferredInstances($classOrAlias) + { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (isset($this->preferredInstances[$key])) { + unset($this->preferredInstances[$key]); + } + return false; + } + + public function addPreferredInstance($classOrAlias, $preferredInstance) + { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (!isset($this->preferredInstances[$key])) { + $this->preferredInstances[$key] = array(); + } + $this->preferredInstances[$key][] = $preferredInstance; + return $this; + } + + public function removePreferredInstance($classOrAlias, $preferredInstance) + { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (!isset($this->preferredInstances[$key]) || !in_array($preferredInstance, $this->preferredInstances[$key])) { + return false; + } + unset($this->preferredInstances[$key][array_search($key, $this->preferredInstances)]); + return $this; + } + + + /** * (non-PHPdoc) * @see Zend\Di.InstanceCollection::hasProperties() */ public function hasProperties($classOrAlias) { - $class = $this->getClassFromAlias($classOrAlias); - return isset($this->properties[$class]); + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + return isset($this->properties[$key]); } /** @@ -93,21 +213,33 @@ public function hasProperties($classOrAlias) */ public function getProperties($classOrAlias) { - // @todo better alias property management - if (isset($this->properties[$classOrAlias])) { - return $this->properties[$classOrAlias]; + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (isset($this->properties[$key])) { + return $this->properties[$key]; } return array(); } + public function setProperties($classOrAlias, array $properties, $merge = false) + { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (isset($this->properties[$key]) && $merge == false) { + $this->properties[$key] = array(); + } + foreach ($properties as $propertyName => $propertyValue) { + $this->setProperty($key, $propertyName, $propertyValue); + } + return $this; + } + /** * (non-PHPdoc) * @see Zend\Di.InstanceCollection::getProperty() */ public function hasProperty($classOrAlias, $name) { - // @todo better alias property management - if (isset($this->properties[$classOrAlias])) { + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (isset($this->properties[$key]) && isset($this->properties[$key][$name])) { return true; } return false; @@ -119,37 +251,72 @@ public function hasProperty($classOrAlias, $name) */ public function getProperty($classOrAlias, $name) { - // @todo better alias property management - if (isset($this->properties[$classOrAlias])) { - return $this->properties[$classOrAlias][$name]; + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (isset($this->properties[$key][$name])) { + return $this->properties[$key][$name]; } return null; } /** - * (non-PHPdoc) - * @see Zend\Di.InstanceCollection::setProperty() + * setProperty() */ public function setProperty($classOrAlias, $name, $value) { - if (!isset($this->properties[$classOrAlias])) { - $this->properties[$classOrAlias] = array(); + if (is_object($value)) { + throw new Exception\InvalidArgumentException('Property value must be a scalar or array'); } - $this->properties[$classOrAlias][$name] = $value; + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (!isset($this->properties[$key])) { + $this->properties[$key] = array(); + } + $this->properties[$key][$name] = $value; + return $this; } /** - * (non-PHPdoc) - * @see Zend\Di.InstanceCollection::unsetProperty() + * unsetProperty() */ public function unsetProperty($classOrAlias, $name) { - if (isset($this->properties[$classOrAlias])) { - unset($this->properties[$classOrAlias][$name]); + $key = ($this->hasAlias($classOrAlias)) ? 'alias:' . $classOrAlias : $classOrAlias; + if (isset($this->properties[$key])) { + unset($this->properties[$key][$name]); return true; } return false; } + + protected function createHashForKeys($classOrAlias, $paramKeys) + { + return $classOrAlias . ':' . implode('|', $paramKeys); + } + + protected function createHashForValues($classOrAlias, $paramValues) + { + $hashValue = ''; + foreach ($paramValues as $param) { + switch (gettype($param)) { + case 'object': + $hashValue .= spl_object_hash($param) . '|'; + break; + case 'integer': + case 'string': + case 'boolean': + case 'NULL': + case 'double': + $hashValue .= $param . '|'; + break; + case 'array': + $hashValue .= 'Array|'; + break; + case 'resource': + $hashValue .= 'resource|'; + break; + } + } + return $hashValue; + } } \ No newline at end of file diff --git a/tests/Zend/Di/ConfigurationTest.php b/tests/Zend/Di/ConfigurationTest.php index e0ae932083e..f67215a9bf4 100644 --- a/tests/Zend/Di/ConfigurationTest.php +++ b/tests/Zend/Di/ConfigurationTest.php @@ -3,12 +3,74 @@ namespace ZendTest\Di; use Zend\Di\Configuration, + Zend\Di\DependencyInjector, PHPUnit_Framework_TestCase as TestCase; class ConfigurationTest extends TestCase { - public function testStub() + public function testConfigurationCanConfigureInstanceManagerWithIniFile() { - $this->markTestIncomplete(); + $ini = new \Zend\Config\Ini(__DIR__ . '/_files/sample.ini', 'section-a'); + $config = new Configuration($ini->di); + $di = new DependencyInjector($config); + + $im = $di->getInstanceManager(); + + $this->assertTrue($im->hasAlias('my-repository')); + $this->assertEquals('My\RepositoryA', $im->getClassFromAlias('my-repository')); + + $this->assertTrue($im->hasAlias('my-mapper')); + $this->assertEquals('My\Mapper', $im->getClassFromAlias('my-mapper')); + + $this->assertTrue($im->hasAlias('my-dbAdapter')); + $this->assertEquals('My\DbAdapter', $im->getClassFromAlias('my-dbAdapter')); + + $this->assertTrue($im->hasPreferredInstances('my-repository')); + $this->assertContains('my-mapper', $im->getPreferredInstances('my-repository')); + + $this->assertTrue($im->hasPreferredInstances('my-mapper')); + $this->assertContains('my-dbAdapter', $im->getPreferredInstances('my-mapper')); + + $this->assertTrue($im->hasProperty('My\DbAdapter', 'username')); + $this->assertEquals('readonly', $im->getProperty('My\DbAdapter', 'username')); + + $this->assertTrue($im->hasProperty('My\DbAdapter', 'password')); + $this->assertEquals('mypassword', $im->getProperty('My\DbAdapter', 'password')); + + $this->assertTrue($im->hasProperty('my-dbAdapter', 'username')); + $this->assertEquals('readwrite', $im->getProperty('my-dbAdapter', 'username')); + } + + public function testConfigurationCanConfigureBuilderDefinitionFromIni() + { + $ini = new \Zend\Config\Ini(__DIR__ . '/_files/sample.ini', 'section-b'); + $config = new Configuration($ini->di); + $di = new DependencyInjector($config); + $definition = $di->getDefinition(); + + $this->assertTrue($definition->hasClass('My\DbAdapter')); + $this->assertEquals('__construct', $definition->getInstantiator('My\DbAdapter')); + $this->assertEquals( + array('username' => null, 'password' => null), + $definition->getInjectionMethodParameters('My\DbAdapter', '__construct') + ); + + $this->assertTrue($definition->hasClass('My\Mapper')); + $this->assertEquals('__construct', $definition->getInstantiator('My\Mapper')); + $this->assertEquals( + array('dbAdapter' => 'My\DbAdapter'), + $definition->getInjectionMethodParameters('My\Mapper', '__construct') + ); + + $this->assertTrue($definition->hasClass('My\Repository')); + $this->assertEquals('__construct', $definition->getInstantiator('My\Repository')); + $this->assertEquals( + array('mapper' => 'My\Mapper'), + $definition->getInjectionMethodParameters('My\Repository', '__construct') + ); + + } + + } diff --git a/tests/Zend/Di/Definition/ArrayDefinitionTest.php b/tests/Zend/Di/Definition/ArrayDefinitionTest.php index a8683a5d521..6c36e9fcf1f 100644 --- a/tests/Zend/Di/Definition/ArrayDefinitionTest.php +++ b/tests/Zend/Di/Definition/ArrayDefinitionTest.php @@ -7,8 +7,78 @@ class ArrayDefinitionTest extends TestCase { - public function testStub() + + /** + * @var Zend\Di\Definition\ArrayDefinition + */ + protected $definition = null; + + public function setup() + { + $this->definition = new ArrayDefinition(include __DIR__ . '/../_files/definition-array.php'); + } + + public function testArrayDefinitionHasClasses() + { + $this->assertTrue($this->definition->hasClass('My\DbAdapter')); + $this->assertTrue($this->definition->hasClass('My\EntityA')); + $this->assertTrue($this->definition->hasClass('My\Mapper')); + $this->assertTrue($this->definition->hasClass('My\RepositoryA')); + $this->assertTrue($this->definition->hasClass('My\RepositoryB')); + $this->assertFalse($this->definition->hasClass('My\Foo')); + } + + public function testArrayDefinitionCanGetClassses() + { + $list = array( + 'My\DbAdapter', + 'My\EntityA', + 'My\Mapper', + 'My\RepositoryA', + 'My\RepositoryB' + ); + + $classes = $this->definition->getClasses(); + + foreach ($list as $class) { + $this->assertContains($class, $classes); + } + + } + + public function testArrayDefinitionCanGetClassSupertypes() + { + $this->assertEquals(array(), $this->definition->getClassSupertypes('My\EntityA')); + $this->assertContains('My\RepositoryA', $this->definition->getClassSupertypes('My\RepositoryB')); + } + + + public function testArrayDefinitionCanGetInstantiator() + { + $this->assertEquals('__construct', $this->definition->getInstantiator('My\RepositoryA')); + $this->assertNull($this->definition->getInstantiator('My\Foo')); + } + + public function testArrayDefinitionHasInjectionMethods() { $this->markTestIncomplete(); } + + public function testArrayDefinitionHasInjectionMethod() + { + $this->markTestIncomplete(); + } + + public function testArrayDefinitionGetInjectionMethods() + { + $this->markTestIncomplete(); + } + + public function testArrayDefinitionGetInjectionMethodParameters() + { + $this->markTestIncomplete(); + } + + + } diff --git a/tests/Zend/Di/Definition/BuilderDefinitionTest.php b/tests/Zend/Di/Definition/BuilderDefinitionTest.php index 45cea551800..e209a8b4e13 100644 --- a/tests/Zend/Di/Definition/BuilderDefinitionTest.php +++ b/tests/Zend/Di/Definition/BuilderDefinitionTest.php @@ -39,4 +39,37 @@ public function testBuilderCanBuildClassWithMethods() $this->assertEquals(array('bar' => 'Bar'), $definition->getInjectionMethodParameters('Foo', 'injectBar')); } + public function testBuilderCanBuildFromArray() + { + $ini = new \Zend\Config\Ini(__DIR__ . '/../_files/sample.ini', 'section-b'); + $iniAsArray = $ini->toArray(); + $definitionArray = $iniAsArray['di']['definitions'][1]; + unset($definitionArray['class']); + + $definition = new BuilderDefinition(); + $definition->createClassesFromArray($definitionArray); + + $this->assertTrue($definition->hasClass('My\DbAdapter')); + $this->assertEquals('__construct', $definition->getInstantiator('My\DbAdapter')); + $this->assertEquals( + array('username' => null, 'password' => null), + $definition->getInjectionMethodParameters('My\DbAdapter', '__construct') + ); + + $this->assertTrue($definition->hasClass('My\Mapper')); + $this->assertEquals('__construct', $definition->getInstantiator('My\Mapper')); + $this->assertEquals( + array('dbAdapter' => 'My\DbAdapter'), + $definition->getInjectionMethodParameters('My\Mapper', '__construct') + ); + + $this->assertTrue($definition->hasClass('My\Repository')); + $this->assertEquals('__construct', $definition->getInstantiator('My\Repository')); + $this->assertEquals( + array('mapper' => 'My\Mapper'), + $definition->getInjectionMethodParameters('My\Repository', '__construct') + ); + + } + } diff --git a/tests/Zend/Di/Definition/ConfigurationTest.php b/tests/Zend/Di/Definition/ConfigurationTest.php deleted file mode 100644 index 711aa19dab8..00000000000 --- a/tests/Zend/Di/Definition/ConfigurationTest.php +++ /dev/null @@ -1,14 +0,0 @@ -markTestIncomplete(); - } -} diff --git a/tests/Zend/Di/DependencyInjectorTest.php b/tests/Zend/Di/DependencyInjectorTest.php index 2ef6cf7d27d..697fdb30d47 100755 --- a/tests/Zend/Di/DependencyInjectorTest.php +++ b/tests/Zend/Di/DependencyInjectorTest.php @@ -256,8 +256,8 @@ public function testNewInstanceThrowsExceptionOnThreeLevelCircularDependency() $di = new DependencyInjector(); $this->setExpectedException( - 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\E depends on ZendTest\Di\TestAsset\CircularClasses\C and viceversa' + 'Zend\Di\Exception\CircularDependencyException', + 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\E depends on ZendTest\Di\TestAsset\CircularClasses\C and viceversa' ); $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\C'); } @@ -273,8 +273,8 @@ public function testNewInstanceThrowsExceptionWhenEnteringInMiddleOfCircularDepe $di = new DependencyInjector(); $this->setExpectedException( - 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\C depends on ZendTest\Di\TestAsset\CircularClasses\D and viceversa' + 'Zend\Di\Exception\CircularDependencyException', + 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\C depends on ZendTest\Di\TestAsset\CircularClasses\D and viceversa' ); $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\D'); } diff --git a/tests/Zend/Di/InstanceManagerTest.php b/tests/Zend/Di/InstanceManagerTest.php index 132b3927d8b..b7c1f145c75 100644 --- a/tests/Zend/Di/InstanceManagerTest.php +++ b/tests/Zend/Di/InstanceManagerTest.php @@ -23,6 +23,22 @@ public function testInstanceManagerCanPersistInstances() $this->assertSame($obj, $im->getSharedInstance('ZendTest\Di\TestAsset\BasicClass')); } + public function testInstanceManagerCanPersistInstancesWithParameters() + { + $im = new InstanceManager(); + $obj1 = new TestAsset\BasicClass(); + $obj2 = new TestAsset\BasicClass(); + $obj3 = new TestAsset\BasicClass(); + + $im->addSharedInstance($obj1, 'foo'); + $im->addSharedInstanceWithParameters($obj2, 'foo', array('foo' => 'bar')); + $im->addSharedInstanceWithParameters($obj3, 'foo', array('foo' => 'baz')); + + $this->assertSame($obj1, $im->getSharedInstance('foo')); + $this->assertSame($obj2, $im->getSharedInstanceWithParameters('foo', array('foo' => 'bar'))); + $this->assertSame($obj3, $im->getSharedInstanceWithParameters('foo', array('foo' => 'baz'))); + } + public function testInstanceManagerCanPersistProperties() { $im = new InstanceManager(); @@ -32,4 +48,6 @@ public function testInstanceManagerCanPersistProperties() $this->assertEquals('bar', $im->getProperty('ZendTest\Di\TestAsset\BasicClass', 'foo')); } + + } diff --git a/tests/Zend/Di/_files/definition-array.php b/tests/Zend/Di/_files/definition-array.php new file mode 100644 index 00000000000..d00bb137794 --- /dev/null +++ b/tests/Zend/Di/_files/definition-array.php @@ -0,0 +1,67 @@ + + array ( + 'superTypes' => + array ( + ), + 'instantiator' => '__construct', + 'injectionMethods' => + array ( + '__construct' => + array ( + 'username' => NULL, + 'password' => NULL, + ), + ), + ), + 'My\\EntityA' => + array ( + 'superTypes' => + array ( + ), + 'instantiator' => NULL, + 'injectionMethods' => + array ( + ), + ), + 'My\\Mapper' => + array ( + 'superTypes' => + array ( + 0 => 'ArrayObject', + ), + 'instantiator' => '__construct', + 'injectionMethods' => + array ( + 'setDbAdapter' => + array ( + 'dbAdapter' => 'My\\DbAdapter', + ), + ), + ), + 'My\\RepositoryA' => + array ( + 'superTypes' => + array ( + ), + 'instantiator' => '__construct', + 'injectionMethods' => + array ( + 'setMapper' => + array ( + 'mapper' => 'My\\Mapper', + ), + ), + ), + 'My\\RepositoryB' => + array ( + 'superTypes' => + array ( + 0 => 'My\\RepositoryA', + ), + 'instantiator' => NULL, + 'injectionMethods' => + array ( + ), + ), +); \ No newline at end of file diff --git a/tests/Zend/Di/_files/sample.ini b/tests/Zend/Di/_files/sample.ini new file mode 100644 index 00000000000..b53c8a8c182 --- /dev/null +++ b/tests/Zend/Di/_files/sample.ini @@ -0,0 +1,22 @@ + +[section-a] + +di.instance.alias.my-repository = 'My\RepositoryA' +di.instance.alias.my-mapper = 'My\Mapper' +di.instance.alias.my-dbAdapter = 'My\DbAdapter' + +di.instance.preferences.my-repository[] = 'my-mapper' +di.instance.preferences.my-mapper[] = 'my-dbAdapter' + +di.instance.properties.My\DbAdapter.username = 'readonly' +di.instance.properties.My\DbAdapter.password = 'mypassword' + +di.instance.properties.my-dbAdapter.username = 'readwrite'; + +[section-b] + +di.definitions.1.class = Zend\Di\Definition\BuilderDefinition +di.definitions.1.My\DbAdapter.injectionMethods.__construct.username = NULL +di.definitions.1.My\DbAdapter.injectionMethods.__construct.password = NULL +di.definitions.1.My\Mapper.injectionMethod.__construct.dbAdapter = My\DbAdapter +di.definitions.1.My\Repository.injectionMethod.__construct.mapper = My\Mapper \ No newline at end of file From 52ccee8c1e157d95651a3117168f4c4c326ca775 Mon Sep 17 00:00:00 2001 From: Ralph Schindler Date: Mon, 13 Jun 2011 00:55:11 -0500 Subject: [PATCH 45/45] Added quickstart doc for Zend\Di --- .../manual/en/tutorials/quickstart-di.xml | 511 ++++++++++++++++++ tests/phpunit.xml | 2 + 2 files changed, 513 insertions(+) create mode 100644 documentation/manual/en/tutorials/quickstart-di.xml diff --git a/documentation/manual/en/tutorials/quickstart-di.xml b/documentation/manual/en/tutorials/quickstart-di.xml new file mode 100644 index 00000000000..329b83a1387 --- /dev/null +++ b/documentation/manual/en/tutorials/quickstart-di.xml @@ -0,0 +1,511 @@ + + +
+ + + +
+ Very brief introduction to Di. + + Dependency Injection is a concept that has been + talked about in numerous places over the web. For the purposes of + this quickstart, we'll explain the act of injecting dependencies + simply with this below code: + + + + Above, A is a dependency of B, and A was + injected into B. If you are not familar with + the concept of dependency injection, here are a couple of great + reads: Matthew Weier O'Phinney's + Analogy, + Ralph Schindler's + Learning + DI, or Fabien Potencier's + Series + on DI. + +
+
+ Very brief introduction to Di Container. + +
+
+ Simplest usage case (2 classes, one consumes the other) + + In the simplest use case, a developer might have one class + (A) that is consumed by another class + (B) through the constructor. By having the + dependency injected through the constructor, this requires an object + of type A be instantiated before an object of + type B so that A can be + injected into B. + + + + To create B by hand, a developer would follow + this work flow, or a similar workflow to this: + + + + If this workflow becomes repeated throughout your application + multiple times, this creates an opportunity where one might want to + DRY up the code. While there are several ways to do this, using a + dependency injection container is one of these solutions. With + Zend's dependency injection container + Zend\Di\DependencyInjector, the above use case + can be taken care of with no configuration (provided all of your + autoloading is already configured properly) with the following + usage: + + + + Moreover, by using the DependencyInjector::get() + method, you are ensuring that the same exact object is returned on + subsequent calls. To force new objects to be created on each and + every request, one would use the + DependencyInjector::newInstance() method: + + + + Let's assume for a moment that A requires some + configuration before it can be created. Our previous use case is + expanded to this (we'll throw a 3rd class in for good measure): + + + + With the above, we need to ensure that our + DependencyInjector is capable of seeing the + A class with a few configuration values (which + are generally scalar in nature). To do this, we need to interact + with the InstanceManager: + + + + Now that our container has values it can use when creating + A, and our new goal is to have a + C object that consumes B and + in turn consumes A, the usage scenario is still + the same: + + + + Simple enough, but what if we wanted to pass in these parameters at + call time? Assuming a default DependencyInjector + object ($di = new Zend\Di\DependencyInjector() + without any configuration to the + InstanceManager), we could do the following: + + + + Constructor injection is not the only supported type of injection. + The other most popular method of injection is also supported: setter + injection. Setter injection allows one to have a usage scenario that + is the same as our previous example with the exception, for example, + of our B class now looking like this: + + + + Since the method is prefixed with set, and is followed by a capital + letter, the DependencyInjector knows that this + method is used for setter injection, and again, the use case + $c = $di->get('C'), will once again know how + to fill the dependencies when needed to create an object of type + C. + + + Other methods are being created to determine what the wirings + between classes are, such as interface injection and annotation + based injection. + +
+
+ Simplest Usage Case Without Type-hints + + If your code does not have type-hints or you are using 3rd party + code that does not have type-hints but does practice dependency + injection, you can still use the + DependencyInjector, but you might find you need + to describe your dependencies explicitly. To do this, you will need + to interact with one of the definitions that is capable of letting a + developer describe, with objects, the map between classes. This + particular definition is called the + BuilderDefinition and can work with, or in place + of, the default RuntimeDefinition. + + + Definitions are a part of the DependencyInjector + that attempt to describe the relationship between classes so that + DependencyInjector::newInstance() and + DependencyInjector::get() can know what the + dependencies are that need to be filled for a particular + class/object. With no configuration, + DependencyInjector will use the + RuntimeDefinition which uses reflection and the + type-hints in your code to determine the dependency map. Without + type-hints, it will assume that all dependencies are scalar or + required configuration parameters. + + + The BuilderDefinition, which can be used in + tandem with the RuntimeDefinition (technically, + it can be used in tandem with any definition by way of the + AggregateDefinition), allows you to + programmatically describe the mappings with objects. Let's say for + example, our above A/B/C usage scenario, were + altered such that class B now looks like this: + + + + You'll notice the only change is that setA now does not include any + type-hinting information. + + + + This above usage scenario provides that whatever the code looks + like, you can ensure that it works with the dependency injection + container. In an ideal world, all of your code would have the proper + type hinting and/or would be using a mapping strategy that reduces + the amount of bootstrapping work that needs to be done in order to + have a full definition that is capable of instantiating all of the + objects you might require. + +
+
+ Simplest usage case with Compiled Definition + + Without going into the gritty details, as you might expect, PHP at + its core is not DI friendly. Out-of-the-box, the + DependencyInjector uses a + RuntimeDefinition which does all class map + resolution via PHP's Reflection extension. Couple + that with the fact that PHP does not have a true application layer + capable of storing objects in-memory between requests, and you get a + recipe that is less performant than similar solutions you'll find in + Java and .Net (where there is an application layer with in-memory + object storage.) + + + To mitigate this shortcoming, Zend\Di has several + features built in capable of pre-compiling the most expensive tasks + that surround dependency injection. It is worth noting that the + RuntimeDefition, which is used by default, is the + only definition that does lookups + on-demand. The rest of the Definition objects are + capable of being aggregated and stored to disk in a very performant + way. + + + Ideally, 3rd party code will ship with a pre-compiled + Definition that will describe the various + relationships and parameter/property needs of each class that is to + be instantiated. This Definition would have been + built as part of some deployment or packaging task by this 3rd + party. When this is not the case, you can create these + Definitions via any of the + Definition types provided with the exception of + the RuntimeDefinition. Here is a breakdown of the + job of each definition type: + + + + + AggregateDefinition - Aggregates multiple + definitions of various types. When looking for a class, it looks + it up in the order the definitions were provided to this + aggregate. + + + + + ArrayDefinition - This definition takes an + array of information and exposes it via the interface provided + by Zend\Di\Definition suitable for usage by + DependencyInjector or an + AggregateDefinition + + + + + BuilderDefinition - Creates a definition + based on an object graph consisting of various + Builder\PhpClass objects and + Builder\InectionMethod objects that describe + the mapping needs of the target codebase and … + + + + + Compiler - This is not actually a definition, + but produces an ArrayDefinition based off of + a code scanner + (Zend\Code\Scanner\DirectoryScanner or + Zend\Code\Scanner\FileScanner). + + + + + The following is an example of producing a definition via a + DirectoryScanner: + + + + This definition can then be directly used by the + DependencyInjector (assuming the above + A, B, C scenario was actually a file per class on + disk): + + + + One strategy for persisting these compiled definitions would be the + following: + + + + Since Zend\Code\Scanner does not include files, + the classes contained within are not loaded into memory. Instead, + Zend\Code\Scanner uses tokenization to determine + the structure of your files. This makes this suitable to use this + solution during development and within the same request as any one + of your application's dispatched actions. + +
+
+ Creating a precompiled definition for others to use + + If you are a 3rd party code developer, it makes sense to produce a + Definition file that describes your code so that + others can utilize this Definition without having + to Reflect it via the + RuntimeDefintion, or create it via the + Compiler. To do this, use the same technique as + above. Instead of writing the resulting array to disk, you would + write the information into a definition directly, by way of + Zend\CodeGenerator: + + +
+
+ Using Multiple Definitions From Multiple Sources + + In all actuality, you will be using code from multiple places, some + Zend Framework code, some other 3rd party code, and of course, your + own code that makes up your application. Here is a method for + consuming definitions from multiple places: + + +
+
diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 2f1c8270ac5..263c3865951 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,7 +1,9 @@ +