web/Zend/Server/Reflection/Function/Abstract.php
changeset 0 4eba9c11703f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/Zend/Server/Reflection/Function/Abstract.php	Mon Dec 13 18:29:26 2010 +0100
@@ -0,0 +1,514 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Server
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Server_Reflection_Node
+ */
+require_once 'Zend/Server/Reflection/Node.php';
+
+/**
+ * Zend_Server_Reflection_Parameter
+ */
+require_once 'Zend/Server/Reflection/Parameter.php';
+
+/**
+ * Zend_Server_Reflection_Prototype
+ */
+require_once 'Zend/Server/Reflection/Prototype.php';
+
+/**
+ * Function/Method Reflection
+ *
+ * Decorates a ReflectionFunction. Allows setting and retrieving an alternate
+ * 'service' name (i.e., the name to be used when calling via a service),
+ * setting and retrieving the description (originally set using the docblock
+ * contents), retrieving the callback and callback type, retrieving additional
+ * method invocation arguments, and retrieving the
+ * method {@link Zend_Server_Reflection_Prototype prototypes}.
+ *
+ * @category   Zend
+ * @package    Zend_Server
+ * @subpackage Reflection
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version $Id: Abstract.php 23320 2010-11-12 21:57:29Z alexander $
+ */
+abstract class Zend_Server_Reflection_Function_Abstract
+{
+    /**
+     * @var ReflectionFunction
+     */
+    protected $_reflection;
+
+    /**
+     * Additional arguments to pass to method on invocation
+     * @var array
+     */
+    protected $_argv = array();
+
+    /**
+     * Used to store extra configuration for the method (typically done by the
+     * server class, e.g., to indicate whether or not to instantiate a class).
+     * Associative array; access is as properties via {@link __get()} and
+     * {@link __set()}
+     * @var array
+     */
+    protected $_config = array();
+
+    /**
+     * Declaring class (needed for when serialization occurs)
+     * @var string
+     */
+    protected $_class;
+
+    /**
+     * Function/method description
+     * @var string
+     */
+    protected $_description = '';
+
+    /**
+     * Namespace with which to prefix function/method name
+     * @var string
+     */
+    protected $_namespace;
+
+    /**
+     * Prototypes
+     * @var array
+     */
+    protected $_prototypes = array();
+
+    private $_return;
+    private $_returnDesc;
+    private $_paramDesc;
+    private $_sigParams;
+    private $_sigParamsDepth;
+
+    /**
+     * Constructor
+     *
+     * @param ReflectionFunction $r
+     */
+    public function __construct(Reflector $r, $namespace = null, $argv = array())
+    {
+        // In PHP 5.1.x, ReflectionMethod extends ReflectionFunction. In 5.2.x,
+        // both extend ReflectionFunctionAbstract. So, we can't do normal type
+        // hinting in the prototype, but instead need to do some explicit
+        // testing here.
+        if ((!$r instanceof ReflectionFunction)
+            && (!$r instanceof ReflectionMethod)) {
+            require_once 'Zend/Server/Reflection/Exception.php';
+            throw new Zend_Server_Reflection_Exception('Invalid reflection class');
+        }
+        $this->_reflection = $r;
+
+        // Determine namespace
+        if (null !== $namespace){
+            $this->setNamespace($namespace);
+        }
+
+        // Determine arguments
+        if (is_array($argv)) {
+            $this->_argv = $argv;
+        }
+
+        // If method call, need to store some info on the class
+        if ($r instanceof ReflectionMethod) {
+            $this->_class = $r->getDeclaringClass()->getName();
+        }
+
+        // Perform some introspection
+        $this->_reflect();
+    }
+
+    /**
+     * Create signature node tree
+     *
+     * Recursive method to build the signature node tree. Increments through
+     * each array in {@link $_sigParams}, adding every value of the next level
+     * to the current value (unless the current value is null).
+     *
+     * @param Zend_Server_Reflection_Node $parent
+     * @param int $level
+     * @return void
+     */
+    protected function _addTree(Zend_Server_Reflection_Node $parent, $level = 0)
+    {
+        if ($level >= $this->_sigParamsDepth) {
+            return;
+        }
+
+        foreach ($this->_sigParams[$level] as $value) {
+            $node = new Zend_Server_Reflection_Node($value, $parent);
+            if ((null !== $value) && ($this->_sigParamsDepth > $level + 1)) {
+                $this->_addTree($node, $level + 1);
+            }
+        }
+    }
+
+    /**
+     * Build the signature tree
+     *
+     * Builds a signature tree starting at the return values and descending
+     * through each method argument. Returns an array of
+     * {@link Zend_Server_Reflection_Node}s.
+     *
+     * @return array
+     */
+    protected function _buildTree()
+    {
+        $returnTree = array();
+        foreach ((array) $this->_return as $value) {
+            $node = new Zend_Server_Reflection_Node($value);
+            $this->_addTree($node);
+            $returnTree[] = $node;
+        }
+
+        return $returnTree;
+    }
+
+    /**
+     * Build method signatures
+     *
+     * Builds method signatures using the array of return types and the array of
+     * parameters types
+     *
+     * @param array $return Array of return types
+     * @param string $returnDesc Return value description
+     * @param array $params Array of arguments (each an array of types)
+     * @param array $paramDesc Array of parameter descriptions
+     * @return array
+     */
+    protected function _buildSignatures($return, $returnDesc, $paramTypes, $paramDesc)
+    {
+        $this->_return         = $return;
+        $this->_returnDesc     = $returnDesc;
+        $this->_paramDesc      = $paramDesc;
+        $this->_sigParams      = $paramTypes;
+        $this->_sigParamsDepth = count($paramTypes);
+        $signatureTrees        = $this->_buildTree();
+        $signatures            = array();
+
+        $endPoints = array();
+        foreach ($signatureTrees as $root) {
+            $tmp = $root->getEndPoints();
+            if (empty($tmp)) {
+                $endPoints = array_merge($endPoints, array($root));
+            } else {
+                $endPoints = array_merge($endPoints, $tmp);
+            }
+        }
+
+        foreach ($endPoints as $node) {
+            if (!$node instanceof Zend_Server_Reflection_Node) {
+                continue;
+            }
+
+            $signature = array();
+            do {
+                array_unshift($signature, $node->getValue());
+                $node = $node->getParent();
+            } while ($node instanceof Zend_Server_Reflection_Node);
+
+            $signatures[] = $signature;
+        }
+
+        // Build prototypes
+        $params = $this->_reflection->getParameters();
+        foreach ($signatures as $signature) {
+            $return = new Zend_Server_Reflection_ReturnValue(array_shift($signature), $this->_returnDesc);
+            $tmp    = array();
+            foreach ($signature as $key => $type) {
+                $param = new Zend_Server_Reflection_Parameter($params[$key], $type, (isset($this->_paramDesc[$key]) ? $this->_paramDesc[$key] : null));
+                $param->setPosition($key);
+                $tmp[] = $param;
+            }
+
+            $this->_prototypes[] = new Zend_Server_Reflection_Prototype($return, $tmp);
+        }
+    }
+
+    /**
+     * Use code reflection to create method signatures
+     *
+     * Determines the method help/description text from the function DocBlock
+     * comment. Determines method signatures using a combination of
+     * ReflectionFunction and parsing of DocBlock @param and @return values.
+     *
+     * @param ReflectionFunction $function
+     * @return array
+     */
+    protected function _reflect()
+    {
+        $function           = $this->_reflection;
+        $helpText           = '';
+        $signatures         = array();
+        $returnDesc         = '';
+        $paramCount         = $function->getNumberOfParameters();
+        $paramCountRequired = $function->getNumberOfRequiredParameters();
+        $parameters         = $function->getParameters();
+        $docBlock           = $function->getDocComment();
+
+        if (!empty($docBlock)) {
+            // Get help text
+            if (preg_match(':/\*\*\s*\r?\n\s*\*\s(.*?)\r?\n\s*\*(\s@|/):s', $docBlock, $matches))
+            {
+                $helpText = $matches[1];
+                $helpText = preg_replace('/(^\s*\*\s)/m', '', $helpText);
+                $helpText = preg_replace('/\r?\n\s*\*\s*(\r?\n)*/s', "\n", $helpText);
+                $helpText = trim($helpText);
+            }
+
+            // Get return type(s) and description
+            $return     = 'void';
+            if (preg_match('/@return\s+(\S+)/', $docBlock, $matches)) {
+                $return = explode('|', $matches[1]);
+                if (preg_match('/@return\s+\S+\s+(.*?)(@|\*\/)/s', $docBlock, $matches))
+                {
+                    $value = $matches[1];
+                    $value = preg_replace('/\s?\*\s/m', '', $value);
+                    $value = preg_replace('/\s{2,}/', ' ', $value);
+                    $returnDesc = trim($value);
+                }
+            }
+
+            // Get param types and description
+            if (preg_match_all('/@param\s+([^\s]+)/m', $docBlock, $matches)) {
+                $paramTypesTmp = $matches[1];
+                if (preg_match_all('/@param\s+\S+\s+(\$\S+)\s+(.*?)(?=@|\*\/)/s', $docBlock, $matches))
+                {
+                    $paramDesc = $matches[2];
+                    foreach ($paramDesc as $key => $value) {
+                        $value = preg_replace('/\s?\*\s/m', '', $value);
+                        $value = preg_replace('/\s{2,}/', ' ', $value);
+                        $paramDesc[$key] = trim($value);
+                    }
+                }
+            }
+        } else {
+            $helpText = $function->getName();
+            $return   = 'void';
+
+            // Try and auto-determine type, based on reflection
+            $paramTypesTmp = array();
+            foreach ($parameters as $i => $param) {
+                $paramType = 'mixed';
+                if ($param->isArray()) {
+                    $paramType = 'array';
+                }
+                $paramTypesTmp[$i] = $paramType;
+            }
+        }
+
+        // Set method description
+        $this->setDescription($helpText);
+
+        // Get all param types as arrays
+        if (!isset($paramTypesTmp) && (0 < $paramCount)) {
+            $paramTypesTmp = array_fill(0, $paramCount, 'mixed');
+        } elseif (!isset($paramTypesTmp)) {
+            $paramTypesTmp = array();
+        } elseif (count($paramTypesTmp) < $paramCount) {
+            $start = $paramCount - count($paramTypesTmp);
+            for ($i = $start; $i < $paramCount; ++$i) {
+                $paramTypesTmp[$i] = 'mixed';
+            }
+        }
+
+        // Get all param descriptions as arrays
+        if (!isset($paramDesc) && (0 < $paramCount)) {
+            $paramDesc = array_fill(0, $paramCount, '');
+        } elseif (!isset($paramDesc)) {
+            $paramDesc = array();
+        } elseif (count($paramDesc) < $paramCount) {
+            $start = $paramCount - count($paramDesc);
+            for ($i = $start; $i < $paramCount; ++$i) {
+                $paramDesc[$i] = '';
+            }
+        }
+
+        if (count($paramTypesTmp) != $paramCount) {
+            require_once 'Zend/Server/Reflection/Exception.php';
+            throw new Zend_Server_Reflection_Exception(
+               'Variable number of arguments is not supported for services (except optional parameters). '
+             . 'Number of function arguments in ' . $function->getDeclaringClass()->getName() . '::'
+             . $function->getName() . '() must correspond to actual number of arguments described in the '
+             . 'docblock.');
+        }
+
+        $paramTypes = array();
+        foreach ($paramTypesTmp as $i => $param) {
+            $tmp = explode('|', $param);
+            if ($parameters[$i]->isOptional()) {
+                array_unshift($tmp, null);
+            }
+            $paramTypes[] = $tmp;
+        }
+
+        $this->_buildSignatures($return, $returnDesc, $paramTypes, $paramDesc);
+    }
+
+
+    /**
+     * Proxy reflection calls
+     *
+     * @param string $method
+     * @param array $args
+     * @return mixed
+     */
+    public function __call($method, $args)
+    {
+        if (method_exists($this->_reflection, $method)) {
+            return call_user_func_array(array($this->_reflection, $method), $args);
+        }
+
+        require_once 'Zend/Server/Reflection/Exception.php';
+        throw new Zend_Server_Reflection_Exception('Invalid reflection method ("' .$method. '")');
+    }
+
+    /**
+     * Retrieve configuration parameters
+     *
+     * Values are retrieved by key from {@link $_config}. Returns null if no
+     * value found.
+     *
+     * @param string $key
+     * @return mixed
+     */
+    public function __get($key)
+    {
+        if (isset($this->_config[$key])) {
+            return $this->_config[$key];
+        }
+
+        return null;
+    }
+
+    /**
+     * Set configuration parameters
+     *
+     * Values are stored by $key in {@link $_config}.
+     *
+     * @param string $key
+     * @param mixed $value
+     * @return void
+     */
+    public function __set($key, $value)
+    {
+        $this->_config[$key] = $value;
+    }
+
+    /**
+     * Set method's namespace
+     *
+     * @param string $namespace
+     * @return void
+     */
+    public function setNamespace($namespace)
+    {
+        if (empty($namespace)) {
+            $this->_namespace = '';
+            return;
+        }
+
+        if (!is_string($namespace) || !preg_match('/[a-z0-9_\.]+/i', $namespace)) {
+            require_once 'Zend/Server/Reflection/Exception.php';
+            throw new Zend_Server_Reflection_Exception('Invalid namespace');
+        }
+
+        $this->_namespace = $namespace;
+    }
+
+    /**
+     * Return method's namespace
+     *
+     * @return string
+     */
+    public function getNamespace()
+    {
+        return $this->_namespace;
+    }
+
+    /**
+     * Set the description
+     *
+     * @param string $string
+     * @return void
+     */
+    public function setDescription($string)
+    {
+        if (!is_string($string)) {
+            require_once 'Zend/Server/Reflection/Exception.php';
+            throw new Zend_Server_Reflection_Exception('Invalid description');
+        }
+
+        $this->_description = $string;
+    }
+
+    /**
+     * Retrieve the description
+     *
+     * @return void
+     */
+    public function getDescription()
+    {
+        return $this->_description;
+    }
+
+    /**
+     * Retrieve all prototypes as array of
+     * {@link Zend_Server_Reflection_Prototype Zend_Server_Reflection_Prototypes}
+     *
+     * @return array
+     */
+    public function getPrototypes()
+    {
+        return $this->_prototypes;
+    }
+
+    /**
+     * Retrieve additional invocation arguments
+     *
+     * @return array
+     */
+    public function getInvokeArguments()
+    {
+        return $this->_argv;
+    }
+
+    /**
+     * Wakeup from serialization
+     *
+     * Reflection needs explicit instantiation to work correctly. Re-instantiate
+     * reflection object on wakeup.
+     *
+     * @return void
+     */
+    public function __wakeup()
+    {
+        if ($this->_reflection instanceof ReflectionMethod) {
+            $class = new ReflectionClass($this->_class);
+            $this->_reflection = new ReflectionMethod($class->newInstance(), $this->getName());
+        } else {
+            $this->_reflection = new ReflectionFunction($this->getName());
+        }
+    }
+}