File indexing completed on 2025-01-19 05:21:28

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Server
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  */
0020 
0021 /**
0022  * Zend_Server_Reflection_Node
0023  */
0024 // require_once 'Zend/Server/Reflection/Node.php';
0025 
0026 /**
0027  * Zend_Server_Reflection_Parameter
0028  */
0029 // require_once 'Zend/Server/Reflection/Parameter.php';
0030 
0031 /**
0032  * Zend_Server_Reflection_Prototype
0033  */
0034 // require_once 'Zend/Server/Reflection/Prototype.php';
0035 
0036 /**
0037  * Function/Method Reflection
0038  *
0039  * Decorates a ReflectionFunction. Allows setting and retrieving an alternate
0040  * 'service' name (i.e., the name to be used when calling via a service),
0041  * setting and retrieving the description (originally set using the docblock
0042  * contents), retrieving the callback and callback type, retrieving additional
0043  * method invocation arguments, and retrieving the
0044  * method {@link Zend_Server_Reflection_Prototype prototypes}.
0045  *
0046  * @category   Zend
0047  * @package    Zend_Server
0048  * @subpackage Reflection
0049  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0050  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0051  * @version $Id$
0052  */
0053 abstract class Zend_Server_Reflection_Function_Abstract
0054 {
0055     /**
0056      * @var ReflectionFunction
0057      */
0058     protected $_reflection;
0059 
0060     /**
0061      * Additional arguments to pass to method on invocation
0062      * @var array
0063      */
0064     protected $_argv = array();
0065 
0066     /**
0067      * Used to store extra configuration for the method (typically done by the
0068      * server class, e.g., to indicate whether or not to instantiate a class).
0069      * Associative array; access is as properties via {@link __get()} and
0070      * {@link __set()}
0071      * @var array
0072      */
0073     protected $_config = array();
0074 
0075     /**
0076      * Declaring class (needed for when serialization occurs)
0077      * @var string
0078      */
0079     protected $_class;
0080 
0081     /**
0082      * Function/method description
0083      * @var string
0084      */
0085     protected $_description = '';
0086 
0087     /**
0088      * Namespace with which to prefix function/method name
0089      * @var string
0090      */
0091     protected $_namespace;
0092 
0093     /**
0094      * Prototypes
0095      * @var array
0096      */
0097     protected $_prototypes = array();
0098 
0099     private $_return;
0100     private $_returnDesc;
0101     private $_paramDesc;
0102     private $_sigParams;
0103     private $_sigParamsDepth;
0104 
0105     /**
0106      * Constructor
0107      *
0108      * @param ReflectionFunction $r
0109      */
0110     public function __construct(Reflector $r, $namespace = null, $argv = array())
0111     {
0112         // In PHP 5.1.x, ReflectionMethod extends ReflectionFunction. In 5.2.x,
0113         // both extend ReflectionFunctionAbstract. So, we can't do normal type
0114         // hinting in the prototype, but instead need to do some explicit
0115         // testing here.
0116         if ((!$r instanceof ReflectionFunction)
0117             && (!$r instanceof ReflectionMethod)) {
0118             // require_once 'Zend/Server/Reflection/Exception.php';
0119             throw new Zend_Server_Reflection_Exception('Invalid reflection class');
0120         }
0121         $this->_reflection = $r;
0122 
0123         // Determine namespace
0124         if (null !== $namespace){
0125             $this->setNamespace($namespace);
0126         }
0127 
0128         // Determine arguments
0129         if (is_array($argv)) {
0130             $this->_argv = $argv;
0131         }
0132 
0133         // If method call, need to store some info on the class
0134         if ($r instanceof ReflectionMethod) {
0135             $this->_class = $r->getDeclaringClass()->getName();
0136         }
0137 
0138         // Perform some introspection
0139         $this->_reflect();
0140     }
0141 
0142     /**
0143      * Create signature node tree
0144      *
0145      * Recursive method to build the signature node tree. Increments through
0146      * each array in {@link $_sigParams}, adding every value of the next level
0147      * to the current value (unless the current value is null).
0148      *
0149      * @param Zend_Server_Reflection_Node $parent
0150      * @param int $level
0151      * @return void
0152      */
0153     protected function _addTree(Zend_Server_Reflection_Node $parent, $level = 0)
0154     {
0155         if ($level >= $this->_sigParamsDepth) {
0156             return;
0157         }
0158 
0159         foreach ($this->_sigParams[$level] as $value) {
0160             $node = new Zend_Server_Reflection_Node($value, $parent);
0161             if ((null !== $value) && ($this->_sigParamsDepth > $level + 1)) {
0162                 $this->_addTree($node, $level + 1);
0163             }
0164         }
0165     }
0166 
0167     /**
0168      * Build the signature tree
0169      *
0170      * Builds a signature tree starting at the return values and descending
0171      * through each method argument. Returns an array of
0172      * {@link Zend_Server_Reflection_Node}s.
0173      *
0174      * @return array
0175      */
0176     protected function _buildTree()
0177     {
0178         $returnTree = array();
0179         foreach ((array) $this->_return as $value) {
0180             $node = new Zend_Server_Reflection_Node($value);
0181             $this->_addTree($node);
0182             $returnTree[] = $node;
0183         }
0184 
0185         return $returnTree;
0186     }
0187 
0188     /**
0189      * Build method signatures
0190      *
0191      * Builds method signatures using the array of return types and the array of
0192      * parameters types
0193      *
0194      * @param array $return Array of return types
0195      * @param string $returnDesc Return value description
0196      * @param array $params Array of arguments (each an array of types)
0197      * @param array $paramDesc Array of parameter descriptions
0198      * @return array
0199      */
0200     protected function _buildSignatures($return, $returnDesc, $paramTypes, $paramDesc)
0201     {
0202         $this->_return         = $return;
0203         $this->_returnDesc     = $returnDesc;
0204         $this->_paramDesc      = $paramDesc;
0205         $this->_sigParams      = $paramTypes;
0206         $this->_sigParamsDepth = count($paramTypes);
0207         $signatureTrees        = $this->_buildTree();
0208         $signatures            = array();
0209 
0210         $endPoints = array();
0211         foreach ($signatureTrees as $root) {
0212             $tmp = $root->getEndPoints();
0213             if (empty($tmp)) {
0214                 $endPoints = array_merge($endPoints, array($root));
0215             } else {
0216                 $endPoints = array_merge($endPoints, $tmp);
0217             }
0218         }
0219 
0220         foreach ($endPoints as $node) {
0221             if (!$node instanceof Zend_Server_Reflection_Node) {
0222                 continue;
0223             }
0224 
0225             $signature = array();
0226             do {
0227                 array_unshift($signature, $node->getValue());
0228                 $node = $node->getParent();
0229             } while ($node instanceof Zend_Server_Reflection_Node);
0230 
0231             $signatures[] = $signature;
0232         }
0233 
0234         // Build prototypes
0235         $params = $this->_reflection->getParameters();
0236         foreach ($signatures as $signature) {
0237             $return = new Zend_Server_Reflection_ReturnValue(array_shift($signature), $this->_returnDesc);
0238             $tmp    = array();
0239             foreach ($signature as $key => $type) {
0240                 $param = new Zend_Server_Reflection_Parameter($params[$key], $type, (isset($this->_paramDesc[$key]) ? $this->_paramDesc[$key] : null));
0241                 $param->setPosition($key);
0242                 $tmp[] = $param;
0243             }
0244 
0245             $this->_prototypes[] = new Zend_Server_Reflection_Prototype($return, $tmp);
0246         }
0247     }
0248 
0249     /**
0250      * Use code reflection to create method signatures
0251      *
0252      * Determines the method help/description text from the function DocBlock
0253      * comment. Determines method signatures using a combination of
0254      * ReflectionFunction and parsing of DocBlock @param and @return values.
0255      *
0256      * @param ReflectionFunction $function
0257      * @return array
0258      */
0259     protected function _reflect()
0260     {
0261         $function           = $this->_reflection;
0262         $helpText           = '';
0263         $signatures         = array();
0264         $returnDesc         = '';
0265         $paramCount         = $function->getNumberOfParameters();
0266         $paramCountRequired = $function->getNumberOfRequiredParameters();
0267         $parameters         = $function->getParameters();
0268         $docBlock           = $function->getDocComment();
0269 
0270         if (!empty($docBlock)) {
0271             // Get help text
0272             if (preg_match(':/\*\*\s*\r?\n\s*\*\s(.*?)\r?\n\s*\*(\s@|/):s', $docBlock, $matches))
0273             {
0274                 $helpText = $matches[1];
0275                 $helpText = preg_replace('/(^\s*\*\s)/m', '', $helpText);
0276                 $helpText = preg_replace('/\r?\n\s*\*\s*(\r?\n)*/s', "\n", $helpText);
0277                 $helpText = trim($helpText);
0278             }
0279 
0280             // Get return type(s) and description
0281             $return     = 'void';
0282             if (preg_match('/@return\s+(\S+)/', $docBlock, $matches)) {
0283                 $return = explode('|', $matches[1]);
0284                 if (preg_match('/@return\s+\S+\s+(.*?)(@|\*\/)/s', $docBlock, $matches))
0285                 {
0286                     $value = $matches[1];
0287                     $value = preg_replace('/\s?\*\s/m', '', $value);
0288                     $value = preg_replace('/\s{2,}/', ' ', $value);
0289                     $returnDesc = trim($value);
0290                 }
0291             }
0292 
0293             // Get param types and description
0294             if (preg_match_all('/@param\s+([^\s]+)/m', $docBlock, $matches)) {
0295                 $paramTypesTmp = $matches[1];
0296                 if (preg_match_all('/@param\s+\S+\s+(\$\S+)\s+(.*?)(?=@|\*\/)/s', $docBlock, $matches))
0297                 {
0298                     $paramDesc = $matches[2];
0299                     foreach ($paramDesc as $key => $value) {
0300                         $value = preg_replace('/\s?\*\s/m', '', $value);
0301                         $value = preg_replace('/\s{2,}/', ' ', $value);
0302                         $paramDesc[$key] = trim($value);
0303                     }
0304                 }
0305             }
0306         } else {
0307             $helpText = $function->getName();
0308             $return   = 'void';
0309 
0310             // Try and auto-determine type, based on reflection
0311             $paramTypesTmp = array();
0312             foreach ($parameters as $i => $param) {
0313                 $paramType = 'mixed';
0314                 if ($param->isArray()) {
0315                     $paramType = 'array';
0316                 }
0317                 $paramTypesTmp[$i] = $paramType;
0318             }
0319         }
0320 
0321         // Set method description
0322         $this->setDescription($helpText);
0323 
0324         // Get all param types as arrays
0325         if (!isset($paramTypesTmp) && (0 < $paramCount)) {
0326             $paramTypesTmp = array_fill(0, $paramCount, 'mixed');
0327         } elseif (!isset($paramTypesTmp)) {
0328             $paramTypesTmp = array();
0329         } elseif (count($paramTypesTmp) < $paramCount) {
0330             $start = $paramCount - count($paramTypesTmp);
0331             for ($i = $start; $i < $paramCount; ++$i) {
0332                 $paramTypesTmp[$i] = 'mixed';
0333             }
0334         }
0335 
0336         // Get all param descriptions as arrays
0337         if (!isset($paramDesc) && (0 < $paramCount)) {
0338             $paramDesc = array_fill(0, $paramCount, '');
0339         } elseif (!isset($paramDesc)) {
0340             $paramDesc = array();
0341         } elseif (count($paramDesc) < $paramCount) {
0342             $start = $paramCount - count($paramDesc);
0343             for ($i = $start; $i < $paramCount; ++$i) {
0344                 $paramDesc[$i] = '';
0345             }
0346         }
0347 
0348         if (count($paramTypesTmp) != $paramCount) {
0349             // require_once 'Zend/Server/Reflection/Exception.php';
0350             throw new Zend_Server_Reflection_Exception(
0351                'Variable number of arguments is not supported for services (except optional parameters). '
0352              . 'Number of function arguments in ' . $function->getDeclaringClass()->getName() . '::'
0353              . $function->getName() . '() must correspond to actual number of arguments described in the '
0354              . 'docblock.');
0355         }
0356 
0357         $paramTypes = array();
0358         foreach ($paramTypesTmp as $i => $param) {
0359             $tmp = explode('|', $param);
0360             if ($parameters[$i]->isOptional()) {
0361                 array_unshift($tmp, null);
0362             }
0363             $paramTypes[] = $tmp;
0364         }
0365 
0366         $this->_buildSignatures($return, $returnDesc, $paramTypes, $paramDesc);
0367     }
0368 
0369 
0370     /**
0371      * Proxy reflection calls
0372      *
0373      * @param string $method
0374      * @param array $args
0375      * @return mixed
0376      */
0377     public function __call($method, $args)
0378     {
0379         if (method_exists($this->_reflection, $method)) {
0380             return call_user_func_array(array($this->_reflection, $method), $args);
0381         }
0382 
0383         // require_once 'Zend/Server/Reflection/Exception.php';
0384         throw new Zend_Server_Reflection_Exception('Invalid reflection method ("' .$method. '")');
0385     }
0386 
0387     /**
0388      * Retrieve configuration parameters
0389      *
0390      * Values are retrieved by key from {@link $_config}. Returns null if no
0391      * value found.
0392      *
0393      * @param string $key
0394      * @return mixed
0395      */
0396     public function __get($key)
0397     {
0398         if (isset($this->_config[$key])) {
0399             return $this->_config[$key];
0400         }
0401 
0402         return null;
0403     }
0404 
0405     /**
0406      * Set configuration parameters
0407      *
0408      * Values are stored by $key in {@link $_config}.
0409      *
0410      * @param string $key
0411      * @param mixed $value
0412      * @return void
0413      */
0414     public function __set($key, $value)
0415     {
0416         $this->_config[$key] = $value;
0417     }
0418 
0419     /**
0420      * Set method's namespace
0421      *
0422      * @param string $namespace
0423      * @return void
0424      */
0425     public function setNamespace($namespace)
0426     {
0427         if (empty($namespace)) {
0428             $this->_namespace = '';
0429             return;
0430         }
0431 
0432         if (!is_string($namespace) || !preg_match('/[a-z0-9_\.]+/i', $namespace)) {
0433             // require_once 'Zend/Server/Reflection/Exception.php';
0434             throw new Zend_Server_Reflection_Exception('Invalid namespace');
0435         }
0436 
0437         $this->_namespace = $namespace;
0438     }
0439 
0440     /**
0441      * Return method's namespace
0442      *
0443      * @return string
0444      */
0445     public function getNamespace()
0446     {
0447         return $this->_namespace;
0448     }
0449 
0450     /**
0451      * Set the description
0452      *
0453      * @param string $string
0454      * @return void
0455      */
0456     public function setDescription($string)
0457     {
0458         if (!is_string($string)) {
0459             // require_once 'Zend/Server/Reflection/Exception.php';
0460             throw new Zend_Server_Reflection_Exception('Invalid description');
0461         }
0462 
0463         $this->_description = $string;
0464     }
0465 
0466     /**
0467      * Retrieve the description
0468      *
0469      * @return void
0470      */
0471     public function getDescription()
0472     {
0473         return $this->_description;
0474     }
0475 
0476     /**
0477      * Retrieve all prototypes as array of
0478      * {@link Zend_Server_Reflection_Prototype Zend_Server_Reflection_Prototypes}
0479      *
0480      * @return array
0481      */
0482     public function getPrototypes()
0483     {
0484         return $this->_prototypes;
0485     }
0486 
0487     /**
0488      * Retrieve additional invocation arguments
0489      *
0490      * @return array
0491      */
0492     public function getInvokeArguments()
0493     {
0494         return $this->_argv;
0495     }
0496 
0497     /**
0498      * Wakeup from serialization
0499      *
0500      * Reflection needs explicit instantiation to work correctly. Re-instantiate
0501      * reflection object on wakeup.
0502      *
0503      * @return void
0504      */
0505     public function __wakeup()
0506     {
0507         if ($this->_reflection instanceof ReflectionMethod) {
0508             $class = new ReflectionClass($this->_class);
0509             $this->_reflection = new ReflectionMethod($class->newInstance(), $this->getName());
0510         } else {
0511             $this->_reflection = new ReflectionFunction($this->getName());
0512         }
0513     }
0514 }