Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 38 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
38
Dung lượng
1,08 MB
Nội dung
CHAPTER 7 ■ REFLECTION API 89 public function printDocTokens() { foreach($this->_tokens as $token) { echo $token[0] . '='; echo docblock_token_name($token[0]) . '='; print_r($token[1]); echo "\n"; } } public function getParsedTags() { return $this->_tags; } public function getParsedComments() { return $this->_comments; } } The DocumentingReflectionMethod class extends from ReflectionMethod, inheriting all the abilities of ReflectionMethod. Then it initializes the base class by calling parent::__construct. This parent construction must be done as the first line in the overridden constructor; other- wise, you may get errors or, even worse, code that crashes without any error explanation. After the base class is initialized, the documentation extensions go into effect. The class calls the static method you previously defined to parse the doccomment, passing in its own doccomment, and storing its results in several protected member variables. Finally, several accessor methods are added to allow you to see that it’s all working. With all the code in Listings 7-12 and 7-13 in DocumentingReflection.php, you can test the results so far. Create another file named test.php and add the code shown in Listing 7-14. Listing 7-14. Testing the DocmentingReflection Classes (test.php) require_once('DocumentingReflection.php'); class demo { /** * This method is for demonstration purposes. * * It takes a single parameter and returns it. * * @param mixed $param1 A variable to return. * @returns mixed The input variable is returned. */ public function demoMethod($param1) { return $param1; } McArthur_819-9C07.fm Page 89 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 90 CHAPTER 7 ■ REFLECTION API } $reflector = new DocumentingReflectionMethod('demo', 'demoMethod'); $reflector->printDocTokens(); print_r($reflector->getParsedTags()); print_r($reflector->getParsedComments()); The script in Listing 7-14 should result in the following output: 1=DOCBLOCK_NEWLINE= 1=DOCBLOCK_NEWLINE= 2=DOCBLOCK_WHITESPACE= 36=DOCBLOCK_TEXT=This method is for demonstration purposes. 1=DOCBLOCK_NEWLINE= 1=DOCBLOCK_NEWLINE= 2=DOCBLOCK_WHITESPACE= 36=DOCBLOCK_TEXT=It takes a single parameter and returns it. 1=DOCBLOCK_NEWLINE= 1=DOCBLOCK_NEWLINE= 2=DOCBLOCK_WHITESPACE= 5=DOCBLOCK_TAG=@param 36=DOCBLOCK_TEXT= mixed $param1 A variable to return. 1=DOCBLOCK_NEWLINE= 2=DOCBLOCK_WHITESPACE= 5=DOCBLOCK_TAG=@returns 36=DOCBLOCK_TEXT= mixed The input variable is returned. 1=DOCBLOCK_NEWLINE= Array ( [param] => mixed $param1 A variable to return. [returns] => mixed The input variable is returned. ) Array ( [0] => This method is for demonstration purposes. [1] => It takes a single parameter and returns it. ) McArthur_819-9C07.fm Page 90 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ REFLECTION API 91 Next, you need to create an extension class for ReflectionParameter. Since there is no doccomment associated with parameters, you will need to obtain the parameter data from the param tags of the associated methods’ doccomments. To do this, you must customize ReflectionParameter to fetch the data from the method, as follows. public void ReflectionParameter::__construct(mixed function, mixed parameter) Notice that the function parameter is of mixed type. This parameter may be passed a numeric array consisting of a class name string or object instance and a method name string. The code for extending ReflectionParameter is shown in Listing 7-15. Listing 7-15. Extending ReflectionParameter (DocumentingReflection.php) class DocumentingReflectionParameter extends ReflectionParameter { protected $_reflectionMethod, $_reflectionClass, $_comment, $_type; public function __construct($method, $parameter) { parent::__construct($method, $parameter); $this->_comment = ''; $this->_type = 'undefined'; $this->_reflectionMethod = new DocumentingReflectionMethod($method[0], $method[1]); $tags = $this->_reflectionMethod->getParsedTags(); if(array_key_exists('param', $tags)) { $params = $tags['param']; if(is_array($params)) { foreach($params as $param) { if($this->_isParamTag($this->getName(), $param)) { $paramFound = $param; } } } else { if($this->_isParamTag($this->getName(), $params)) { $paramFound = $params; } } if(isset($paramFound)) { $tokens = preg_split("/[\s\t]+/", $paramFound, 3); $this->_comment = $tokens[2]; $this->_type = $tokens[0]; } } } McArthur_819-9C07.fm Page 91 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 92 CHAPTER 7 ■ REFLECTION API public function getDeclaringFunction() { return $this->_reflectionMethod; } public function getComment() { return $this->_comment; } public function getType() { return $this->_type; } private function _isParamTag($paramName, $paramData) { $paramSplit = preg_split("/[\s\t]+/", $paramData, 3); $explodedName = trim($paramSplit[1], ' $,.'); if($explodedName == $paramName) { return true; } else { return false; } } } This class is a lot more complicated than the previous classes. Most of the comment processing for this class is done in the constructor. First, you construct the class and call the parent methods. Default values are assigned for cases where there is no documentation associated. Then processing begins. Using the information passed to the constructor, a DocumentingReflectionMethod is instan- tiated. This class will give you access to the documentation information via the getParsedTags() method. Next, it checks for the presence of 'param' in the $tags array. If it’s there, it determines whether the entry is an array or a single string value and processes accordingly. During this process, the private member function _isParamTag() is called to determine if the parameter is the one described by the tag. The function determines this by splitting the param tag into three parts. The split is based on a regular expression that divides the string into tokens, separating them where there are one or more of tabs or spaces. The third parameter to the function limits string splitting to three times. This will produce an array with the type, vari- able name, and the comment. The variable name entry is checked against the ReflectionParameter class’s own name. If there is a match, the tag currently being tested is known to belong to the parameter. Once the correct tag is found, the data is split up and stored in the protected member variables _comment and _type. This data can be later accessed by get functions. You can now experiment with this class, as shown in Listing 7-16. McArthur_819-9C07.fm Page 92 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ REFLECTION API 93 Listing 7-16. Experimenting with DocumentingReflection (Experiment.php) require_once('DocumentingReflection.php'); class demo { /** * @param string $param this is the comment */ public function demoMethod($param='test') {} } $refparam = new DocumentingReflectionParameter( array('demo', 'demoMethod'), 'param' ); var_dump($refparam->getComment()); var_dump($refparam->getType()); You should see the following output: string(19) "this is the comment" string(6) "string" Now, normally you don’t access parameters by providing that much information. Let’s modify the DocumentingReflectionMethod class to override the getParameters() function, making it return DocumentingReflectionParmeter[] instead of ReflectionParameter[]. Include the code in Listing 7-17 in the DocumentingReflectionMethod class. Listing 7-17. Overriding getParameters (DocumentingReflection.php) public function getParameters() { $parameters = array(); if(is_object($this->_declaringClass)) { $class = get_class($this->_declaringClass); } else if(is_string($this->_declaringClass)) { $class = $this->_declaringClass; } McArthur_819-9C07.fm Page 93 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 94 CHAPTER 7 ■ REFLECTION API foreach(parent::getParameters() as $parameter) { $parameters[] = new DocumentingReflectionParameter( array($class, $this->getName()), $parameter->getName() ); } return $parameters; } This method first determines the declaring class that was stored at construction and checks if it is an object or a string. Since you need a string for the next step, determine the object’s type with get_class(). Following that, the parent’s getParameters() method is called. This will get you an array of ReflectionParameter objects, but not DocumentingReflectionParameter objects. The whole purpose of this function is to invoke the extended documenting form rather than the native form. To test the getParameters() override, run the code in Listing 7-18. Listing 7-18. Using getParameters (test2.php) require_once('DocumentingReflection.php'); class demo { /** * @param mixed $param1 The first comment. * @param string $param2 The second comment. */ public function demoMethod($param1, $param2) {} } $reflector = new DocumentingReflectionMethod('demo', 'demoMethod'); foreach($reflector->getParameters() as $param) { echo $param->getName() . ' '; echo $param->getType() . ' '; echo $param->getComment(); echo "\n"; } param1 mixed The first comment. param2 string The second comment. So, now you have the methods and parameters worked out. What about classes? DocumentingReflectionClass is the next class you need to create. Create this class as shown in Listing 7-19. and place the code in your DocumentingReflection.php file. McArthur_819-9C07.fm Page 94 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ REFLECTION API 95 Listing 7-19. Creating the DocumentingReflectionClass (DocumentingReflection.php) class DocumentingReflectionClass extends ReflectionClass { protected $_comments, $_tags, $_tokens; public function __construct($class) { parent::__construct($class); $docComment = $this->getDocComment(); $parsedComment = DocumentingReflection::ParseDocComment($docComment); $this->_comments = $parsedComment['comments']; $this->_tags = $parsedComment['tags']; $this->_tokens = $parsedComment['tokens']; } public function getMethods() { $methods = array(); foreach(parent::getMethods() as $method) { $methods[] = new DocumentingReflectionMethod( $this->getName(), $method->getName() ); } return $methods; } public function printDocTokens() { foreach($this->_tokens as $token) { echo $token[0] . '='; echo docblock_token_name($token[0]) . '='; print_r($token[1]); echo "\n"; } } public function getParsedTags() { return $this->_tags; } public function getParsedComments() { return $this->_comments; } } McArthur_819-9C07.fm Page 95 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 96 CHAPTER 7 ■ REFLECTION API By now, this should be getting repetitive. Dozens of functions in these classes need to be overridden, and I’ve included only the most critical few in processing an OOP tree. Any func- tion in the API that returns a Reflection* class natively should be converted to a DocumentingReflection* class and translated, just as getParameters() and getMethods() were translated. Updating the Parser to Handle In-Line Tags Now we need to return to the original documentation parser. The parser you created earlier in the chapter does not respect any of the in-line PHPDoc tags. As an example, Listing 7-20 shows a parser capable of processing the in-line link tag. Listing 7-20. Processing In-Line Link Tags (DocumentingReflection.php) public static function ParseDocComment($docComment) { $returnData = $comments = $tags = array(); $tagNames = $tagData = array(); $tokens = docblock_tokenize($docComment,true); foreach($tokens as $token) { switch( $token[0] ) { case DOCBLOCK_INLINETAG: $inlineTag = trim($token[1], ' @{}'); break; case DOCBLOCK_ENDINLINETAG: switch($inlineTag) { case 'link': $inlineTagContents = preg_split("/[\s\t]+/", trim($inlineTagData), 2); $data = '<a href="'. $inlineTagContents[0]; $data .= '">'. $inlineTagContents[1] .'</a>'; break; } if(array_key_exists($tagId, $tagData)) { $tagData[$tagId] .= ' ' . $data; } else { $tagData[$tagId] = $data; } unset($inlineTag, $inlineTagData, $inlineTagContents); break; case DOCBLOCK_INLINETAGCONTENTS: $addData = trim($token[1], ' }'); McArthur_819-9C07.fm Page 96 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ REFLECTION API 97 if(isset($inlineTagData)) { $inlineTagData .= ' ' . $addData; } else { $inlineTagData = $addData; } unset($addData); break; case DOCBLOCK_TEXT: if(!isset($tagId)) { $comments[] = $token[1]; } else { if(array_key_exists($tagId, $tagData)) { $tagData[$tagId] .= ' ' . trim($token[1]); } else { $tagData[$tagId] = trim($token[1]); } } break; case DOCBLOCK_TAG: $tagId = uniqid(); $tagNames[$tagId] = trim($token[1], '@ '); break; } } foreach($tagData as $tagId => $data) { $tagName = $tagNames[$tagId]; if(array_key_exists($tagName, $tags)) { if(!is_array($tags[$tagName])) { $backupData = $tags[$tagName]; $tags[$tagName] = array(); $tags[$tagName][] = $backupData; } $tags[$tagName][] = $data; } else { $tags[$tagName] = $data; } } $returnData['comments'] = $comments; $returnData['tags'] = $tags; $returnData['tokens'] = $tokens; return $returnData; } McArthur_819-9C07.fm Page 97 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 98 CHAPTER 7 ■ REFLECTION API The code in Listing 7-21 demonstrates how to use the getMethods() method as well and the processing of the in-line link tag. Listing 7-21. Using getMethods() and Processing the In-Line Link Tag (test3.php) require_once('DocumentingReflection.php'); class demo { /** * This is the first test method * * @param mixed $param1 The first comment {@link * http://www.apress.com See the website} * @param string $param2 The second comment. */ public function demoMethod($param1, $param2) {} /** * This is the second test method * * @param mixed $param1 The first comment of the second method * @param string $param2 The second comment of the second method */ public function demoMethod2($param1, $param2) {} } $reflector = new DocumentingReflectionClass('demo'); foreach($reflector->getMethods() as $method) { echo $method->getName() . "\n"; echo print_r($method->getParsedComments(),1); foreach($method->getParameters() as $param) { echo "\t". $param->getName() . ' '; echo $param->getType() . ' '; echo $param->getComment(); echo "\n"; } echo "\n\n"; } McArthur_819-9C07.fm Page 98 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... 579 64 0.0011 581 04 0.0011 581 04 0.0011 58128 0.00 14 58196 0.0015 58196 0.0016 58196 0.0016 58 244 0.0017 58 244 0.0017 58 244 0.0018 58 244 0.0019 58 244 TRACE END [16:53:57] -> MyClass->myCaller() /code/xdebug .php: 21 -> MyOther->myCallee() /code/xdebug .php: 4 -> xdebug_call_class() /code/xdebug .php: 10 -> printf() /code/xdebug .php: 10 -> xdebug_call_function() /code/xdebug .php: 11 -> printf() /code/xdebug .php: 11... succeeded Next, install PHPUnit and any dependencies it needs: > pear install alldeps phpunit/PHPUnit downloading PHPUnit-3.1.9.tgz Starting to download PHPUnit-3.1.9.tgz (116, 945 bytes) done: 116, 945 bytes install ok: channel://pear.phpunit.de/PHPUnit-3.1.9 Depending on your system’s PEAR layout, the PHPUnit source files should be found in /usr/share /php/ PHPUnit Simpo PDF Merge Page 111Split... real code, be sure that you have proper authentication and SSL security measures in place See Chapter 21 for SSL client certificate setup instructions PHPUnit for Unit Testing PHPUnit lets you create unit tests for your application In short, PHP unit testing involves writing PHP scripts specifically to test other PHP scripts This type of testing is referred to as unit testing because the test apparatus... Fortunately, PHPUnit provides two methods, called setUp() and tearDown(), which allow you to define a common configuration for every test Listing 8-3 shows the same test, split into setUp() and tearDown() methods Listing 8-3 A Test Split into setUp and tearDown Methods (./tests/DemoTest .php) < ?php require_once('PHPUnit/Framework .php' ); require_once(dirname( FILE ) '/ /code/Demo .php' ); class DemoTest extends PHPUnit_Framework_TestCase... classes and methods, one at a time PHPUnit is an elegant solution to writing these tests, and it follows an object-oriented development approach Installing PHPUnit Installing PHPUnit is done through PEAR and is fairly straightforward First use PEAR to “discover” the pear.phpunit.de channel: > pear channel-discover pear.phpunit.de Adding Channel "pear.phpunit.de" succeeded Discovery of channel "pear.phpunit.de"... with each test and on a clean instance of the test class, so your setup code will not persist between tests and can be expected not to leak state between each test You can also create a test suite that runs multiple test cases, as shown in Listing 8 -4 Listing 8 -4 A Suite of Tests < ?php require_once 'PHPUnit/Framework .php' ; require_once 'PHPUnit/TextUI/TestRunner .php' ; require_once 'DemoTest .php' ; class... WebServiceMethodAttribute extends Attribute { protected $data; function construct($data) { $this->data = $data; } function getData() { return $this->data; } } ?> Finally, create a demonstration class and use reflection to examine its attributes, as shown in Listing 7- 24 Listing 7- 24 Testing the Attributes (Testing .php) < ?php require_once('DocumentingReflection .php' ); require_once('Attributes .php' ); class demo { /** *... test, and manage PHP applications Subversion provides revision control After you create a repository and checkouts, you can work with files under revision control PHP helps you perform unit testing with PHPUnit, following an object-oriented approach Phing automates your build and deployment steps You can use it to create targets to update a Subversion checkout, run unit tests, copy files to a beta testing. .. Test .php Your DemoTest .php file will be found and run If any tests fail, the target will fail, and as a result, the tasks try and deploy will not occur, as they depend on test The printsummary attribute will give you some extra information about the test success when you run the phing command Next, you need to define copy commands for try and deploy These should reference only the code files and copy... cycle with happy developers and an overdue, overbudget application with an employee-turnover problem There is no silver bullet that prevents these problems, but a series of tools can help you better manage your projects and track your project’s progress in real time These tools, when combined, form a programming technique called continuous integration Any continuous integration project includes four main . as shown in Listing 7- 24. Listing 7- 24. Testing the Attributes (Testing .php) < ?php require_once('DocumentingReflection .php& apos;); require_once('Attributes .php& apos;); class demo. DocumentingReflection .php, you can test the results so far. Create another file named test .php and add the code shown in Listing 7- 14. Listing 7- 14. Testing the DocmentingReflection Classes (test .php) require_once('DocumentingReflection .php& apos;); class. http://www.simpopdf.com McArthur_819-9C07.fm Page 1 04 Friday, February 22, 2008 8:59 AM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 105 ■ ■ ■ CHAPTER 8 Testing, Deployment, and