web/lib/Zend/Server/Reflection/Function/Abstract.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Server
       
    17  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  */
       
    20 
       
    21 /**
       
    22  * Zend_Server_Reflection_Node
       
    23  */
       
    24 require_once 'Zend/Server/Reflection/Node.php';
       
    25 
       
    26 /**
       
    27  * Zend_Server_Reflection_Parameter
       
    28  */
       
    29 require_once 'Zend/Server/Reflection/Parameter.php';
       
    30 
       
    31 /**
       
    32  * Zend_Server_Reflection_Prototype
       
    33  */
       
    34 require_once 'Zend/Server/Reflection/Prototype.php';
       
    35 
       
    36 /**
       
    37  * Function/Method Reflection
       
    38  *
       
    39  * Decorates a ReflectionFunction. Allows setting and retrieving an alternate
       
    40  * 'service' name (i.e., the name to be used when calling via a service),
       
    41  * setting and retrieving the description (originally set using the docblock
       
    42  * contents), retrieving the callback and callback type, retrieving additional
       
    43  * method invocation arguments, and retrieving the
       
    44  * method {@link Zend_Server_Reflection_Prototype prototypes}.
       
    45  *
       
    46  * @category   Zend
       
    47  * @package    Zend_Server
       
    48  * @subpackage Reflection
       
    49  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    50  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    51  * @version $Id: Abstract.php 23320 2010-11-12 21:57:29Z alexander $
       
    52  */
       
    53 abstract class Zend_Server_Reflection_Function_Abstract
       
    54 {
       
    55     /**
       
    56      * @var ReflectionFunction
       
    57      */
       
    58     protected $_reflection;
       
    59 
       
    60     /**
       
    61      * Additional arguments to pass to method on invocation
       
    62      * @var array
       
    63      */
       
    64     protected $_argv = array();
       
    65 
       
    66     /**
       
    67      * Used to store extra configuration for the method (typically done by the
       
    68      * server class, e.g., to indicate whether or not to instantiate a class).
       
    69      * Associative array; access is as properties via {@link __get()} and
       
    70      * {@link __set()}
       
    71      * @var array
       
    72      */
       
    73     protected $_config = array();
       
    74 
       
    75     /**
       
    76      * Declaring class (needed for when serialization occurs)
       
    77      * @var string
       
    78      */
       
    79     protected $_class;
       
    80 
       
    81     /**
       
    82      * Function/method description
       
    83      * @var string
       
    84      */
       
    85     protected $_description = '';
       
    86 
       
    87     /**
       
    88      * Namespace with which to prefix function/method name
       
    89      * @var string
       
    90      */
       
    91     protected $_namespace;
       
    92 
       
    93     /**
       
    94      * Prototypes
       
    95      * @var array
       
    96      */
       
    97     protected $_prototypes = array();
       
    98 
       
    99     private $_return;
       
   100     private $_returnDesc;
       
   101     private $_paramDesc;
       
   102     private $_sigParams;
       
   103     private $_sigParamsDepth;
       
   104 
       
   105     /**
       
   106      * Constructor
       
   107      *
       
   108      * @param ReflectionFunction $r
       
   109      */
       
   110     public function __construct(Reflector $r, $namespace = null, $argv = array())
       
   111     {
       
   112         // In PHP 5.1.x, ReflectionMethod extends ReflectionFunction. In 5.2.x,
       
   113         // both extend ReflectionFunctionAbstract. So, we can't do normal type
       
   114         // hinting in the prototype, but instead need to do some explicit
       
   115         // testing here.
       
   116         if ((!$r instanceof ReflectionFunction)
       
   117             && (!$r instanceof ReflectionMethod)) {
       
   118             require_once 'Zend/Server/Reflection/Exception.php';
       
   119             throw new Zend_Server_Reflection_Exception('Invalid reflection class');
       
   120         }
       
   121         $this->_reflection = $r;
       
   122 
       
   123         // Determine namespace
       
   124         if (null !== $namespace){
       
   125             $this->setNamespace($namespace);
       
   126         }
       
   127 
       
   128         // Determine arguments
       
   129         if (is_array($argv)) {
       
   130             $this->_argv = $argv;
       
   131         }
       
   132 
       
   133         // If method call, need to store some info on the class
       
   134         if ($r instanceof ReflectionMethod) {
       
   135             $this->_class = $r->getDeclaringClass()->getName();
       
   136         }
       
   137 
       
   138         // Perform some introspection
       
   139         $this->_reflect();
       
   140     }
       
   141 
       
   142     /**
       
   143      * Create signature node tree
       
   144      *
       
   145      * Recursive method to build the signature node tree. Increments through
       
   146      * each array in {@link $_sigParams}, adding every value of the next level
       
   147      * to the current value (unless the current value is null).
       
   148      *
       
   149      * @param Zend_Server_Reflection_Node $parent
       
   150      * @param int $level
       
   151      * @return void
       
   152      */
       
   153     protected function _addTree(Zend_Server_Reflection_Node $parent, $level = 0)
       
   154     {
       
   155         if ($level >= $this->_sigParamsDepth) {
       
   156             return;
       
   157         }
       
   158 
       
   159         foreach ($this->_sigParams[$level] as $value) {
       
   160             $node = new Zend_Server_Reflection_Node($value, $parent);
       
   161             if ((null !== $value) && ($this->_sigParamsDepth > $level + 1)) {
       
   162                 $this->_addTree($node, $level + 1);
       
   163             }
       
   164         }
       
   165     }
       
   166 
       
   167     /**
       
   168      * Build the signature tree
       
   169      *
       
   170      * Builds a signature tree starting at the return values and descending
       
   171      * through each method argument. Returns an array of
       
   172      * {@link Zend_Server_Reflection_Node}s.
       
   173      *
       
   174      * @return array
       
   175      */
       
   176     protected function _buildTree()
       
   177     {
       
   178         $returnTree = array();
       
   179         foreach ((array) $this->_return as $value) {
       
   180             $node = new Zend_Server_Reflection_Node($value);
       
   181             $this->_addTree($node);
       
   182             $returnTree[] = $node;
       
   183         }
       
   184 
       
   185         return $returnTree;
       
   186     }
       
   187 
       
   188     /**
       
   189      * Build method signatures
       
   190      *
       
   191      * Builds method signatures using the array of return types and the array of
       
   192      * parameters types
       
   193      *
       
   194      * @param array $return Array of return types
       
   195      * @param string $returnDesc Return value description
       
   196      * @param array $params Array of arguments (each an array of types)
       
   197      * @param array $paramDesc Array of parameter descriptions
       
   198      * @return array
       
   199      */
       
   200     protected function _buildSignatures($return, $returnDesc, $paramTypes, $paramDesc)
       
   201     {
       
   202         $this->_return         = $return;
       
   203         $this->_returnDesc     = $returnDesc;
       
   204         $this->_paramDesc      = $paramDesc;
       
   205         $this->_sigParams      = $paramTypes;
       
   206         $this->_sigParamsDepth = count($paramTypes);
       
   207         $signatureTrees        = $this->_buildTree();
       
   208         $signatures            = array();
       
   209 
       
   210         $endPoints = array();
       
   211         foreach ($signatureTrees as $root) {
       
   212             $tmp = $root->getEndPoints();
       
   213             if (empty($tmp)) {
       
   214                 $endPoints = array_merge($endPoints, array($root));
       
   215             } else {
       
   216                 $endPoints = array_merge($endPoints, $tmp);
       
   217             }
       
   218         }
       
   219 
       
   220         foreach ($endPoints as $node) {
       
   221             if (!$node instanceof Zend_Server_Reflection_Node) {
       
   222                 continue;
       
   223             }
       
   224 
       
   225             $signature = array();
       
   226             do {
       
   227                 array_unshift($signature, $node->getValue());
       
   228                 $node = $node->getParent();
       
   229             } while ($node instanceof Zend_Server_Reflection_Node);
       
   230 
       
   231             $signatures[] = $signature;
       
   232         }
       
   233 
       
   234         // Build prototypes
       
   235         $params = $this->_reflection->getParameters();
       
   236         foreach ($signatures as $signature) {
       
   237             $return = new Zend_Server_Reflection_ReturnValue(array_shift($signature), $this->_returnDesc);
       
   238             $tmp    = array();
       
   239             foreach ($signature as $key => $type) {
       
   240                 $param = new Zend_Server_Reflection_Parameter($params[$key], $type, (isset($this->_paramDesc[$key]) ? $this->_paramDesc[$key] : null));
       
   241                 $param->setPosition($key);
       
   242                 $tmp[] = $param;
       
   243             }
       
   244 
       
   245             $this->_prototypes[] = new Zend_Server_Reflection_Prototype($return, $tmp);
       
   246         }
       
   247     }
       
   248 
       
   249     /**
       
   250      * Use code reflection to create method signatures
       
   251      *
       
   252      * Determines the method help/description text from the function DocBlock
       
   253      * comment. Determines method signatures using a combination of
       
   254      * ReflectionFunction and parsing of DocBlock @param and @return values.
       
   255      *
       
   256      * @param ReflectionFunction $function
       
   257      * @return array
       
   258      */
       
   259     protected function _reflect()
       
   260     {
       
   261         $function           = $this->_reflection;
       
   262         $helpText           = '';
       
   263         $signatures         = array();
       
   264         $returnDesc         = '';
       
   265         $paramCount         = $function->getNumberOfParameters();
       
   266         $paramCountRequired = $function->getNumberOfRequiredParameters();
       
   267         $parameters         = $function->getParameters();
       
   268         $docBlock           = $function->getDocComment();
       
   269 
       
   270         if (!empty($docBlock)) {
       
   271             // Get help text
       
   272             if (preg_match(':/\*\*\s*\r?\n\s*\*\s(.*?)\r?\n\s*\*(\s@|/):s', $docBlock, $matches))
       
   273             {
       
   274                 $helpText = $matches[1];
       
   275                 $helpText = preg_replace('/(^\s*\*\s)/m', '', $helpText);
       
   276                 $helpText = preg_replace('/\r?\n\s*\*\s*(\r?\n)*/s', "\n", $helpText);
       
   277                 $helpText = trim($helpText);
       
   278             }
       
   279 
       
   280             // Get return type(s) and description
       
   281             $return     = 'void';
       
   282             if (preg_match('/@return\s+(\S+)/', $docBlock, $matches)) {
       
   283                 $return = explode('|', $matches[1]);
       
   284                 if (preg_match('/@return\s+\S+\s+(.*?)(@|\*\/)/s', $docBlock, $matches))
       
   285                 {
       
   286                     $value = $matches[1];
       
   287                     $value = preg_replace('/\s?\*\s/m', '', $value);
       
   288                     $value = preg_replace('/\s{2,}/', ' ', $value);
       
   289                     $returnDesc = trim($value);
       
   290                 }
       
   291             }
       
   292 
       
   293             // Get param types and description
       
   294             if (preg_match_all('/@param\s+([^\s]+)/m', $docBlock, $matches)) {
       
   295                 $paramTypesTmp = $matches[1];
       
   296                 if (preg_match_all('/@param\s+\S+\s+(\$\S+)\s+(.*?)(?=@|\*\/)/s', $docBlock, $matches))
       
   297                 {
       
   298                     $paramDesc = $matches[2];
       
   299                     foreach ($paramDesc as $key => $value) {
       
   300                         $value = preg_replace('/\s?\*\s/m', '', $value);
       
   301                         $value = preg_replace('/\s{2,}/', ' ', $value);
       
   302                         $paramDesc[$key] = trim($value);
       
   303                     }
       
   304                 }
       
   305             }
       
   306         } else {
       
   307             $helpText = $function->getName();
       
   308             $return   = 'void';
       
   309 
       
   310             // Try and auto-determine type, based on reflection
       
   311             $paramTypesTmp = array();
       
   312             foreach ($parameters as $i => $param) {
       
   313                 $paramType = 'mixed';
       
   314                 if ($param->isArray()) {
       
   315                     $paramType = 'array';
       
   316                 }
       
   317                 $paramTypesTmp[$i] = $paramType;
       
   318             }
       
   319         }
       
   320 
       
   321         // Set method description
       
   322         $this->setDescription($helpText);
       
   323 
       
   324         // Get all param types as arrays
       
   325         if (!isset($paramTypesTmp) && (0 < $paramCount)) {
       
   326             $paramTypesTmp = array_fill(0, $paramCount, 'mixed');
       
   327         } elseif (!isset($paramTypesTmp)) {
       
   328             $paramTypesTmp = array();
       
   329         } elseif (count($paramTypesTmp) < $paramCount) {
       
   330             $start = $paramCount - count($paramTypesTmp);
       
   331             for ($i = $start; $i < $paramCount; ++$i) {
       
   332                 $paramTypesTmp[$i] = 'mixed';
       
   333             }
       
   334         }
       
   335 
       
   336         // Get all param descriptions as arrays
       
   337         if (!isset($paramDesc) && (0 < $paramCount)) {
       
   338             $paramDesc = array_fill(0, $paramCount, '');
       
   339         } elseif (!isset($paramDesc)) {
       
   340             $paramDesc = array();
       
   341         } elseif (count($paramDesc) < $paramCount) {
       
   342             $start = $paramCount - count($paramDesc);
       
   343             for ($i = $start; $i < $paramCount; ++$i) {
       
   344                 $paramDesc[$i] = '';
       
   345             }
       
   346         }
       
   347 
       
   348         if (count($paramTypesTmp) != $paramCount) {
       
   349             require_once 'Zend/Server/Reflection/Exception.php';
       
   350             throw new Zend_Server_Reflection_Exception(
       
   351                'Variable number of arguments is not supported for services (except optional parameters). '
       
   352              . 'Number of function arguments in ' . $function->getDeclaringClass()->getName() . '::'
       
   353              . $function->getName() . '() must correspond to actual number of arguments described in the '
       
   354              . 'docblock.');
       
   355         }
       
   356 
       
   357         $paramTypes = array();
       
   358         foreach ($paramTypesTmp as $i => $param) {
       
   359             $tmp = explode('|', $param);
       
   360             if ($parameters[$i]->isOptional()) {
       
   361                 array_unshift($tmp, null);
       
   362             }
       
   363             $paramTypes[] = $tmp;
       
   364         }
       
   365 
       
   366         $this->_buildSignatures($return, $returnDesc, $paramTypes, $paramDesc);
       
   367     }
       
   368 
       
   369 
       
   370     /**
       
   371      * Proxy reflection calls
       
   372      *
       
   373      * @param string $method
       
   374      * @param array $args
       
   375      * @return mixed
       
   376      */
       
   377     public function __call($method, $args)
       
   378     {
       
   379         if (method_exists($this->_reflection, $method)) {
       
   380             return call_user_func_array(array($this->_reflection, $method), $args);
       
   381         }
       
   382 
       
   383         require_once 'Zend/Server/Reflection/Exception.php';
       
   384         throw new Zend_Server_Reflection_Exception('Invalid reflection method ("' .$method. '")');
       
   385     }
       
   386 
       
   387     /**
       
   388      * Retrieve configuration parameters
       
   389      *
       
   390      * Values are retrieved by key from {@link $_config}. Returns null if no
       
   391      * value found.
       
   392      *
       
   393      * @param string $key
       
   394      * @return mixed
       
   395      */
       
   396     public function __get($key)
       
   397     {
       
   398         if (isset($this->_config[$key])) {
       
   399             return $this->_config[$key];
       
   400         }
       
   401 
       
   402         return null;
       
   403     }
       
   404 
       
   405     /**
       
   406      * Set configuration parameters
       
   407      *
       
   408      * Values are stored by $key in {@link $_config}.
       
   409      *
       
   410      * @param string $key
       
   411      * @param mixed $value
       
   412      * @return void
       
   413      */
       
   414     public function __set($key, $value)
       
   415     {
       
   416         $this->_config[$key] = $value;
       
   417     }
       
   418 
       
   419     /**
       
   420      * Set method's namespace
       
   421      *
       
   422      * @param string $namespace
       
   423      * @return void
       
   424      */
       
   425     public function setNamespace($namespace)
       
   426     {
       
   427         if (empty($namespace)) {
       
   428             $this->_namespace = '';
       
   429             return;
       
   430         }
       
   431 
       
   432         if (!is_string($namespace) || !preg_match('/[a-z0-9_\.]+/i', $namespace)) {
       
   433             require_once 'Zend/Server/Reflection/Exception.php';
       
   434             throw new Zend_Server_Reflection_Exception('Invalid namespace');
       
   435         }
       
   436 
       
   437         $this->_namespace = $namespace;
       
   438     }
       
   439 
       
   440     /**
       
   441      * Return method's namespace
       
   442      *
       
   443      * @return string
       
   444      */
       
   445     public function getNamespace()
       
   446     {
       
   447         return $this->_namespace;
       
   448     }
       
   449 
       
   450     /**
       
   451      * Set the description
       
   452      *
       
   453      * @param string $string
       
   454      * @return void
       
   455      */
       
   456     public function setDescription($string)
       
   457     {
       
   458         if (!is_string($string)) {
       
   459             require_once 'Zend/Server/Reflection/Exception.php';
       
   460             throw new Zend_Server_Reflection_Exception('Invalid description');
       
   461         }
       
   462 
       
   463         $this->_description = $string;
       
   464     }
       
   465 
       
   466     /**
       
   467      * Retrieve the description
       
   468      *
       
   469      * @return void
       
   470      */
       
   471     public function getDescription()
       
   472     {
       
   473         return $this->_description;
       
   474     }
       
   475 
       
   476     /**
       
   477      * Retrieve all prototypes as array of
       
   478      * {@link Zend_Server_Reflection_Prototype Zend_Server_Reflection_Prototypes}
       
   479      *
       
   480      * @return array
       
   481      */
       
   482     public function getPrototypes()
       
   483     {
       
   484         return $this->_prototypes;
       
   485     }
       
   486 
       
   487     /**
       
   488      * Retrieve additional invocation arguments
       
   489      *
       
   490      * @return array
       
   491      */
       
   492     public function getInvokeArguments()
       
   493     {
       
   494         return $this->_argv;
       
   495     }
       
   496 
       
   497     /**
       
   498      * Wakeup from serialization
       
   499      *
       
   500      * Reflection needs explicit instantiation to work correctly. Re-instantiate
       
   501      * reflection object on wakeup.
       
   502      *
       
   503      * @return void
       
   504      */
       
   505     public function __wakeup()
       
   506     {
       
   507         if ($this->_reflection instanceof ReflectionMethod) {
       
   508             $class = new ReflectionClass($this->_class);
       
   509             $this->_reflection = new ReflectionMethod($class->newInstance(), $this->getName());
       
   510         } else {
       
   511             $this->_reflection = new ReflectionFunction($this->getName());
       
   512         }
       
   513     }
       
   514 }