From 58abad7b06a387660c1ba0de10d6bcd74fc47ac3 Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Tue, 2 Jun 2015 18:54:32 +0200 Subject: [PATCH 01/10] Add code generators for interfaces and method declarations. Reflection classes and unit tests included. --- src/Generator/InterfaceGenerator.php | 733 ++++++++++++++++++ ...tor.php => MethodDeclarationGenerator.php} | 217 +++--- src/Reflection/InterfaceReflection.php | 222 ++++++ .../MethodDeclarationReflection.php | 454 +++++++++++ test/Generator/InterfaceGeneratorTest.php | 455 +++++++++++ .../MethodDeclarationGeneratorTest.php | 140 ++++ .../TestAsset/InterfaceWithConstants.php | 22 + .../TestAsset/InterfaceWithNamespace.php | 12 + test/Reflection/InterfaceReflectionTest.php | 132 ++++ .../MethodDeclarationReflectionTest.php | 110 +++ .../TestAsset/TestSampleInterface1.php | 67 ++ .../TestAsset/TestSampleInterface2.php | 30 + .../TestAsset/TestSampleInterface3.php | 30 + .../TestAsset/TestSampleInterface4.php | 15 + .../TestAsset/TestSampleInterface5.php | 15 + test/TestAsset/NonNamespaceInterface.php | 5 + 16 files changed, 2533 insertions(+), 126 deletions(-) create mode 100644 src/Generator/InterfaceGenerator.php rename src/Generator/{MethodGenerator.php => MethodDeclarationGenerator.php} (59%) create mode 100644 src/Reflection/InterfaceReflection.php create mode 100644 src/Reflection/MethodDeclarationReflection.php create mode 100644 test/Generator/InterfaceGeneratorTest.php create mode 100644 test/Generator/MethodDeclarationGeneratorTest.php create mode 100644 test/Generator/TestAsset/InterfaceWithConstants.php create mode 100644 test/Generator/TestAsset/InterfaceWithNamespace.php create mode 100644 test/Reflection/InterfaceReflectionTest.php create mode 100644 test/Reflection/MethodDeclarationReflectionTest.php create mode 100644 test/Reflection/TestAsset/TestSampleInterface1.php create mode 100644 test/Reflection/TestAsset/TestSampleInterface2.php create mode 100644 test/Reflection/TestAsset/TestSampleInterface3.php create mode 100644 test/Reflection/TestAsset/TestSampleInterface4.php create mode 100644 test/Reflection/TestAsset/TestSampleInterface5.php create mode 100644 test/TestAsset/NonNamespaceInterface.php diff --git a/src/Generator/InterfaceGenerator.php b/src/Generator/InterfaceGenerator.php new file mode 100644 index 00000000..cb5f08af --- /dev/null +++ b/src/Generator/InterfaceGenerator.php @@ -0,0 +1,733 @@ + + * @link http://github.com/zendframework/zf2 for the canonical source repository + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * + * + * @method add + */ + +use Zend\Code\Reflection\InterfaceReflection; +use Zend\Code\Generator\Exception\InvalidArgumentException; + +class InterfaceGenerator extends AbstractGenerator +{ + const OBJECT_TYPE = 'interface'; + + /** + * @var FileGenerator + */ + protected $containingFileGenerator = null; + + /** + * @var string + */ + protected $namespaceName = null; + + /** + * @var DocBlockGenerator + */ + protected $docBlock = null; + + /** + * @var string + */ + protected $name = null; + + /** + * @var bool + */ + protected $flags = 0x00; + + /** + * @var array Array of string names + */ + protected $extendedInterfaces = array(); + + /** + * @var PropertyGenerator[] Array of constants + */ + protected $constants = array(); + + /** + * @var MethodDeclarationGenerator[] Array of methods + */ + protected $methods = array(); + + /** + * Build a Code Generation Php Object from a Class Reflection. + * + * @param InterfaceReflection $reflection + * + * @return InterfaceGenerator + */ + public static function fromReflection(InterfaceReflection $reflection) + { + $ig = new static($reflection->getName()); + $ig->setSourceContent($ig->getSourceContent()); + $ig->setSourceDirty(false); + + if ($reflection->getDocComment() != '') { + $ig->setDocBlock(DocBlockGenerator::fromReflection($reflection->getDocBlock())); + } + + // set the namespace + if ($reflection->inNamespace()) { + $ig->setNamespaceName($reflection->getNamespaceName()); + } + + /* @var \Zend\Code\Reflection\ClassReflection[] $parentInterfaces */ + $parentInterfaces = $reflection->getParentInterfaces(); + $interfaceNames = array(); + if ($parentInterfaces) { + foreach ($parentInterfaces as $parentInterface) { + $interfaceNames[] = $parentInterface->getName(); + } + } + + $ig->setExtendedInterfaces($interfaceNames); + + $constants = array(); + foreach ($reflection->getConstants() as $name => $value) { + $constants[] = array( + 'name' => $name, + 'value' => $value + ); + } + + $ig->addConstants($constants); + + $methods = array(); + foreach ($reflection->getMethods() as $reflectionMethod) { + $className = ($ig->getNamespaceName()) ? $ig->getNamespaceName() . "\\" . $ig->getName() : $ig->getName(); + if ($reflectionMethod->getDeclaringInterface()->getName() == $className) { + $methods[] = MethodDeclarationGenerator::fromReflection($reflectionMethod); + } + } + + $ig->addMethods($methods); + + return $ig; + } + + /** + * Generate from array. + * + * @configkey name string [required] Class Name + * @configkey filegenerator FileGenerator File generator that holds this class + * @configkey namespacename string The namespace for this class + * @configkey docblock string The docblock information + * @configkey flags int Flags, one of InterfaceGenerator::FLAG_ABSTRACT InterfaceGenerator::FLAG_FINAL + * @configkey extendedclass string Class which this class is extending + * @configkey implementedinterfaces + * @configkey properties + * @configkey methods + * + * @throws InvalidArgumentException + * + * @param array $array + * + * @return InterfaceGenerator + */ + public static function fromArray(array $array) + { + if (!isset($array['name'])) { + throw new InvalidArgumentException( + 'Interface generator requires that a name is provided for this interface' + ); + } + + $cg = new static($array['name']); + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'containingfile': + $cg->setContainingFileGenerator($value); + break; + case 'namespacename': + $cg->setNamespaceName($value); + break; + case 'docblock': + $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value); + $cg->setDocBlock($docBlock); + break; + case 'flags': + $cg->setFlags($value); + break; + case 'extendedinterfaces': + $cg->setExtendedInterfaces($value); + break; + case 'constants': + $cg->addConstants($value); + break; + case 'methods': + $cg->addMethods($value); + break; + } + } + + return $cg; + } + + /** + * @param string $name + * @param string $namespaceName + * @param array|string $flags + * @param array $parents + * @param array $methods + * @param DocBlockGenerator $docBlock + */ + public function __construct( + $name = null, + $namespaceName = null, + $flags = null, + $parents = array(), + $methods = array(), + $docBlock = null + ) { + if ($name !== null) { + $this->setName($name); + } + if ($namespaceName !== null) { + $this->setNamespaceName($namespaceName); + } + if ($flags !== null) { + $this->setFlags($flags); + } + if (is_array($parents)) { + $this->setExtendedInterfaces($parents); + } + if ($methods !== array()) { + $this->addMethods($methods); + } + if ($docBlock !== null) { + $this->setDocBlock($docBlock); + } + } + + /** + * @param string $name + * + * @return InterfaceGenerator + */ + public function setName($name) + { + if (strstr($name, '\\')) { + $namespace = substr($name, 0, strrpos($name, '\\')); + $name = substr($name, strrpos($name, '\\') + 1); + $this->setNamespaceName($namespace); + } + + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $namespaceName + * + * @return InterfaceGenerator + */ + public function setNamespaceName($namespaceName) + { + $this->namespaceName = $namespaceName; + + return $this; + } + + /** + * @return string + */ + public function getNamespaceName() + { + return $this->namespaceName; + } + + /** + * @param FileGenerator $fileGenerator + * + * @return InterfaceGenerator + */ + public function setContainingFileGenerator(FileGenerator $fileGenerator) + { + $this->containingFileGenerator = $fileGenerator; + + return $this; + } + + /** + * @return FileGenerator + */ + public function getContainingFileGenerator() + { + if (!is_object($this->containingFileGenerator)) { + $this->containingFileGenerator = new FileGenerator(); + } + return $this->containingFileGenerator; + } + + /** + * @param DocBlockGenerator $docBlock + * + * @return InterfaceGenerator + */ + public function setDocBlock(DocBlockGenerator $docBlock) + { + $this->docBlock = $docBlock; + + return $this; + } + + /** + * @return DocBlockGenerator + */ + public function getDocBlock() + { + return $this->docBlock; + } + + /** + * @param array|string $flags + * + * @return InterfaceGenerator + */ + public function setFlags($flags) + { + if (is_array($flags)) { + $flagsArray = $flags; + $flags = 0x00; + foreach ($flagsArray as $flag) { + $flags |= $flag; + } + } + // check that visibility is one of three + $this->flags = $flags; + + return $this; + } + + /** + * @param string $flag + * + * @return InterfaceGenerator + */ + public function addFlag($flag) + { + $this->setFlags($this->flags | $flag); + + return $this; + } + + /** + * @param string $flag + * + * @return InterfaceGenerator + */ + public function removeFlag($flag) + { + $this->setFlags($this->flags & ~$flag); + + return $this; + } + + /** + * @param array|string $implementedInterfaces + * + * @return InterfaceGenerator + */ + public function setExtendedInterfaces($implementedInterfaces) + { + // Ignore empty parameters... + if (!empty($implementedInterfaces)) { + // Convert to array + if (!is_array($implementedInterfaces)) { + $implementedInterfaces = array($implementedInterfaces); + } + $this->extendedInterfaces = $implementedInterfaces; + } + + return $this; + } + + /** + * @return array + */ + public function getExtendedInterfaces() + { + return $this->extendedInterfaces; + } + + /** + * @alias setExtendedInterfaces + * @param array $implementedInterfaces + * + * @return InterfaceGenerator + */ + public function setImplementedInterfaces(array $implementedInterfaces) + { + return $this->setExtendedInterfaces($implementedInterfaces); + } + + /** + * @alias getExtendedInterfaces + * + * @return array + */ + public function getImplementedInterfaces() + { + return $this->getExtendedInterfaces(); + } + + /** + * @param string $constantName + * + * @return PropertyGenerator|false + */ + public function getConstant($constantName) + { + if (isset($this->constants[$constantName])) { + return $this->constants[$constantName]; + } + + return false; + } + + /** + * @return PropertyGenerator[] indexed by constant name + */ + public function getConstants() + { + return $this->constants; + } + + /** + * @param string $constantName + * + * @return bool + */ + public function hasConstant($constantName) + { + return isset($this->constants[$constantName]); + } + + /** + * Add constant from PropertyGenerator. + * + * @param PropertyGenerator $constant + * + * @throws InvalidArgumentException + * + * @return InterfaceGenerator + */ + public function addConstantFromGenerator(PropertyGenerator $constant) + { + $constantName = $constant->getName(); + + if (isset($this->constants[$constantName])) { + throw new InvalidArgumentException(sprintf( + 'A constant by name %s already exists in this class.', + $constantName + )); + } + + if (!$constant->isConst()) { + throw new InvalidArgumentException(sprintf( + 'The value %s is not defined as a constant.', + $constantName + )); + } + + $this->constants[$constantName] = $constant; + + return $this; + } + + /** + * Add Constant. + * + * @param string $name + * @param string $value + * + * @throws InvalidArgumentException + * + * @return InterfaceGenerator + */ + public function addConstant($name, $value) + { + if (!is_string($name)) { + throw new InvalidArgumentException(sprintf( + '%s expects string for name', + __METHOD__ + )); + } + + if (!is_string($value) && !is_numeric($value)) { + throw new InvalidArgumentException(sprintf( + '%s expects value for constant, value must be a string or numeric', + __METHOD__ + )); + } + + return $this->addConstantFromGenerator(new PropertyGenerator($name, $value, PropertyGenerator::FLAG_CONSTANT)); + } + + /** + * @param PropertyGenerator[]|array[] $constants + * + * @return InterfaceGenerator + */ + public function addConstants(array $constants) + { + foreach ($constants as $constant) { + if ($constant instanceof PropertyGenerator) { + $this->addConstantFromGenerator($constant); + } else { + if (is_array($constant)) { + call_user_func_array(array($this, 'addConstant'), $constant); + } + } + } + + return $this; + } + + /** + * Add method declaration + * + * @param array $methods + * + * @return InterfaceGenerator + */ + public function addMethods(array $methods) + { + foreach ($methods as $method) { + if ($method instanceof MethodDeclarationGenerator) { + $this->addMethodFromGenerator($method); + } else { + if (is_string($method)) { + $this->addMethod($method); + } elseif (is_array($method)) { + call_user_func_array(array($this, 'addMethod'), $method); + } + } + } + + return $this; + } + + /** + * Add method declaration from scalars. + * + * @param string $name + * @param array $parameters + * @param int $flags + * @param string $body + * @param string $docBlock + * + * @throws InvalidArgumentException + * + * @return InterfaceGenerator + */ + public function addMethod( + $name = null, + array $parameters = array(), + $flags = MethodDeclarationGenerator::FLAG_PUBLIC, + $body = null, + $docBlock = null + ) { + if (!is_string($name)) { + throw new InvalidArgumentException(sprintf( + '%s::%s expects string for name', + get_class($this), + __FUNCTION__ + )); + } + + return $this->addMethodFromGenerator(new MethodDeclarationGenerator($name, $parameters, $flags, $body, $docBlock)); + } + + /** + * Add Method from MethodDeclarationGenerator. + * + * @param MethodDeclarationGenerator $method + * + * @throws InvalidArgumentException + * + * @return InterfaceGenerator + */ + public function addMethodFromGenerator(MethodDeclarationGenerator $method) + { + $methodName = $method->getName(); + + if ($this->hasMethod($methodName)) { + throw new InvalidArgumentException(sprintf( + 'A method by name %s already exists in this class.', + $methodName + )); + } + + $this->methods[strtolower($methodName)] = $method; + + return $this; + } + + /** + * @return MethodDeclarationGenerator[] + */ + public function getMethods() + { + return $this->methods; + } + + /** + * @param string $methodName + * + * @return MethodDeclarationGenerator|false + */ + public function getMethod($methodName) + { + return $this->hasMethod($methodName) ? $this->methods[strtolower($methodName)] : false; + } + + /** + * @param string $methodName + * + * @return InterfaceGenerator + */ + public function removeMethod($methodName) + { + if ($this->hasMethod($methodName)) { + unset($this->methods[strtolower($methodName)]); + } + + return $this; + } + + /** + * @param string $methodName + * + * @return bool + */ + public function hasMethod($methodName) + { + return isset($this->methods[strtolower($methodName)]); + } + + /** + * @return bool + */ + public function isSourceDirty() + { + if (($docBlock = $this->getDocBlock()) && $docBlock->isSourceDirty()) { + return true; + } + + foreach ($this->getMethods() as $method) { + if ($method->isSourceDirty()) { + return true; + } + } + + return parent::isSourceDirty(); + } + + /** + * @inherit Zend\Code\Generator\GeneratorInterface + */ + public function generate() + { + if (!$this->isSourceDirty()) { + $output = $this->getSourceContent(); + if (!empty($output)) { + return $output; + } + } + + $output = ''; + + if (null !== ($namespace = $this->getNamespaceName())) { + $output .= "namespace {$namespace};".self::LINE_FEED.self::LINE_FEED; + } + + $uses = $this->getUses(); + if (!empty($uses)) { + foreach ($uses as $use) { + $use = (!array($use)) ? array($use) : $use; + $useClass = "use {$use[0]}"; + if (isset($use[1])) { + $useClass .= " as {$use[1]}"; + } + if (!empty($useClass)) { + $output .= $useClass.';'.self::LINE_FEED; + } + } + $output .= self::LINE_FEED; + } + + if (null !== ($docBlock = $this->getDocBlock())) { + $docBlock->setIndentation(''); + $output .= $docBlock->generate(); + } + + $output .= "interface {$this->getName()}"; + + $implemented = $this->getExtendedInterfaces(); + if (!empty($implemented)) { + $output .= ' extends '.implode(', ', $implemented); + } + + $output .= self::LINE_FEED.'{'.self::LINE_FEED.self::LINE_FEED; + + $constants = $this->getConstants(); + foreach ($constants as $constant) { + $output .= $constant->generate().self::LINE_FEED.self::LINE_FEED; + } + + $methods = $this->getMethods(); + foreach ($methods as $method) { + $output .= $method->generate().self::LINE_FEED; + } + + $output .= self::LINE_FEED.'}'.self::LINE_FEED; + + return $output; + } + + /** + * Add "use" class or interface + * + * @param string $use + * @param string|null $useAlias + * + * @return InterfaceGenerator + */ + public function addUse($use, $useAlias = null) + { + $this->getContainingFileGenerator()->setUse($use, $useAlias); + } + + /** + * Get "uses" of classes and interfaces + * + * @return array + */ + public function getUses() + { + return $this->getContainingFileGenerator()->getUses(); + } +} diff --git a/src/Generator/MethodGenerator.php b/src/Generator/MethodDeclarationGenerator.php similarity index 59% rename from src/Generator/MethodGenerator.php rename to src/Generator/MethodDeclarationGenerator.php index 1f405dbc..f29106e6 100644 --- a/src/Generator/MethodGenerator.php +++ b/src/Generator/MethodDeclarationGenerator.php @@ -1,17 +1,20 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace Zend\Code\Generator; - -use Zend\Code\Reflection\MethodReflection; +use Zend\Code\Generator\Exception\InvalidArgumentException; +use Zend\Code\Reflection\MethodDeclarationReflection; -class MethodGenerator extends AbstractMemberGenerator +class MethodDeclarationGenerator extends AbstractMemberGenerator { /** * @var DocBlockGenerator @@ -24,79 +27,34 @@ class MethodGenerator extends AbstractMemberGenerator protected $parameters = array(); /** - * @var string - */ - protected $body = null; - - /** - * @param MethodReflection $reflectionMethod + * @param MethodDeclarationReflection $reflectionMethod + * * @return MethodGenerator */ - public static function fromReflection(MethodReflection $reflectionMethod) + public static function fromReflection(MethodDeclarationReflection $reflectionMethod) { $method = new static(); + if (!$reflectionMethod->isPublic()) { + throw new InvalidArgumentException('Interfaces can only contain public methods!'); + } - $method->setSourceContent($reflectionMethod->getContents(false)); $method->setSourceDirty(false); - if ($reflectionMethod->getDocComment() != '') { $method->setDocBlock(DocBlockGenerator::fromReflection($reflectionMethod->getDocBlock())); } - $method->setFinal($reflectionMethod->isFinal()); - - if ($reflectionMethod->isPrivate()) { - $method->setVisibility(self::VISIBILITY_PRIVATE); - } elseif ($reflectionMethod->isProtected()) { - $method->setVisibility(self::VISIBILITY_PROTECTED); - } else { - $method->setVisibility(self::VISIBILITY_PUBLIC); - } - $method->setStatic($reflectionMethod->isStatic()); - $method->setName($reflectionMethod->getName()); foreach ($reflectionMethod->getParameters() as $reflectionParameter) { $method->setParameter(ParameterGenerator::fromReflection($reflectionParameter)); } - $method->setBody(static::clearBodyIndention($reflectionMethod->getBody())); - return $method; } /** - * Identify the space indention from the first line and remove this indention - * from all lines - * - * @param string $body - * - * @return string - */ - protected static function clearBodyIndention($body) - { - if (empty($body)) { - return $body; - } - - $lines = explode(PHP_EOL, $body); - - $indention = str_replace(trim($lines[1]), '', $lines[1]); - - foreach ($lines as $key => $line) { - if (substr($line, 0, strlen($indention)) == $indention) { - $lines[$key] = substr($line, strlen($indention)); - } - } - - $body = implode(PHP_EOL, $lines); - - return $body; - } - - /** - * Generate from array + * Generate from array. * * @configkey name string [required] Class Name * @configkey docblock string The docblock information @@ -108,19 +66,22 @@ protected static function clearBodyIndention($body) * @configkey static bool * @configkey visibility string * - * @throws Exception\InvalidArgumentException - * @param array $array - * @return MethodGenerator + * @throws InvalidArgumentException + * + * @param array $array + * + * @return MethodDeclarationGenerator */ public static function fromArray(array $array) { if (!isset($array['name'])) { - throw new Exception\InvalidArgumentException( + throw new InvalidArgumentException( 'Method generator requires that a name is provided for this object' ); } $method = new static($array['name']); + $method->setVisibility(self::VISIBILITY_PUBLIC); foreach ($array as $name => $value) { // normalize key switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { @@ -134,21 +95,9 @@ public static function fromArray(array $array) case 'parameters': $method->setParameters($value); break; - case 'body': - $method->setBody($value); - break; - case 'abstract': - $method->setAbstract($value); - break; - case 'final': - $method->setFinal($value); - break; case 'static': $method->setStatic($value); break; - case 'visibility': - $method->setVisibility($value); - break; } } @@ -156,11 +105,11 @@ public static function fromArray(array $array) } /** - * @param string $name - * @param array $parameters - * @param int $flags - * @param string $body - * @param DocBlockGenerator|string $docBlock + * @param string $name + * @param array $parameters + * @param int $flags + * @param string $body + * @param DocBlockGenerator|string $docBlock */ public function __construct( $name = null, @@ -178,18 +127,35 @@ public function __construct( if ($flags !== self::FLAG_PUBLIC) { $this->setFlags($flags); } - if ($body) { - $this->setBody($body); - } if ($docBlock) { $this->setDocBlock($docBlock); } } /** - * @param array $parameters + * Export pre configured method generator. + * * @return MethodGenerator */ + public function getMethodImplementation() + { + $generator = new MethodGenerator($this->getName()); + $generator->setBody('// TODO: Implement logic'); + $generator->setParameters($this->getParameters()); + $generator->setIndentation($this->getIndentation()); + $generator->setVisibility(MethodGenerator::VISIBILITY_PUBLIC); + $generator->setStatic($this->isStatic()); + $generator->setFinal($this->isFinal()); + $generator->setDocBlock($this->getDocBlock()); + + return $generator; + } + + /** + * @param array $parameters + * + * @return $this + */ public function setParameters(array $parameters) { foreach ($parameters as $parameter) { @@ -200,9 +166,11 @@ public function setParameters(array $parameters) } /** - * @param ParameterGenerator|array|string $parameter - * @throws Exception\InvalidArgumentException - * @return MethodGenerator + * @param ParameterGenerator|array|string $parameter + * + * @throws InvalidArgumentException + * + * @return $this */ public function setParameter($parameter) { @@ -215,7 +183,7 @@ public function setParameter($parameter) } if (!$parameter instanceof ParameterGenerator) { - throw new Exception\InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( '%s is expecting either a string, array or an instance of %s\ParameterGenerator', __METHOD__, __NAMESPACE__ @@ -235,24 +203,6 @@ public function getParameters() return $this->parameters; } - /** - * @param string $body - * @return MethodGenerator - */ - public function setBody($body) - { - $this->body = $body; - return $this; - } - - /** - * @return string - */ - public function getBody() - { - return $this->body; - } - /** * @return string */ @@ -269,17 +219,12 @@ public function generate() $output .= $indent; - if ($this->isAbstract()) { - $output .= 'abstract '; - } else { - $output .= (($this->isFinal()) ? 'final ' : ''); - } - - $output .= $this->getVisibility() - . (($this->isStatic()) ? ' static' : '') - . ' function ' . $this->getName() . '('; + $output .= self::VISIBILITY_PUBLIC + .(($this->isStatic()) ? ' static' : '') + .' function '.$this->getName().'('; $parameters = $this->getParameters(); + $parameterOutput = array(); if (!empty($parameters)) { foreach ($parameters as $parameter) { $parameterOutput[] = $parameter->generate(); @@ -287,25 +232,45 @@ public function generate() $output .= implode(', ', $parameterOutput); } + $output .= ');'; - $output .= ')'; - - if ($this->isAbstract()) { - return $output . ';'; - } - - $output .= self::LINE_FEED . $indent . '{' . self::LINE_FEED; - - if ($this->body) { - $output .= preg_replace('#^((?![a-zA-Z0-9_-]+;).+?)$#m', $indent . $indent . '$1', trim($this->body)) - . self::LINE_FEED; - } + return $output; + } - $output .= $indent . '}' . self::LINE_FEED; + /** + * @ignore + * @throws \LogicException + * @param bool $isAbstract + * + * @return void + */ + public function setAbstract($isAbstract) + { + throw new \LogicException( + "Abstract methods are not supported. To generate abstract methods use the MethodGenerator. " . + "The intended use of this generator is to work with interfaces. See method getMethodImplementation() to export a pre configured instance of this method " . + "declaration that you can use to generate your (abstract) class method." + ); + } - return $output; + /** + * @ignore + * @throws \LogicException + * + * @return bool + */ + public function isAbstract() + { + throw new \LogicException( + "Abstract methods are not supported. To generate abstract methods use the MethodGenerator. " . + "The intended use of this generator is to work with interfaces. See method getMethodImplementation() to export a pre configured instance of this method " . + "declaration that you can use to generate your (abstract) class method." + ); } + /** + * @return string + */ public function __toString() { return $this->generate(); diff --git a/src/Reflection/InterfaceReflection.php b/src/Reflection/InterfaceReflection.php new file mode 100644 index 00000000..5e08decf --- /dev/null +++ b/src/Reflection/InterfaceReflection.php @@ -0,0 +1,222 @@ +getFileName()); + + return $instance; + } + + /** + * Return the classes DocBlock reflection object. + * + * @return DocBlockReflection + * + * @throws ExceptionInterface for missing DocBock or invalid reflection class + */ + public function getDocBlock() + { + if (isset($this->docBlock)) { + return $this->docBlock; + } + + if ('' == $this->getDocComment()) { + return false; + } + + $this->docBlock = new DocBlockReflection($this); + + return $this->docBlock; + } + + /** + * @param AnnotationManager $annotationManager + * + * @return AnnotationCollection + */ + public function getAnnotations(AnnotationManager $annotationManager) + { + $docComment = $this->getDocComment(); + + if ($docComment == '') { + return false; + } + + if ($this->annotations) { + return $this->annotations; + } + + $fileScanner = $this->createFileScanner($this->getFileName()); + $nameInformation = $fileScanner->getClassNameInformation($this->getName()); + + if (!$nameInformation) { + return false; + } + + $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation); + + return $this->annotations; + } + + /** + * Return the start line of the class. + * + * @param bool $includeDocComment + * + * @return int + */ + public function getStartLine($includeDocComment = false) + { + if ($includeDocComment && $this->getDocComment() != '') { + return $this->getDocBlock()->getStartLine(); + } + + return parent::getStartLine(); + } + + /** + * Return the contents of the class. + * + * @param bool $includeDocBlock + * + * @return string + */ + public function getContents($includeDocBlock = true) + { + $fileName = $this->getFileName(); + + if (false === $fileName || !file_exists($fileName)) { + return ''; + } + + $filelines = file($fileName); + $startnum = $this->getStartLine($includeDocBlock); + $endnum = $this->getEndLine(); + + // Ensure we get between the open and close braces + $lines = array_slice($filelines, $startnum, $endnum); + array_unshift($lines, $filelines[$startnum - 1]); + + return strstr(implode('', $lines), '{'); + } + + /** + * Get all reflection objects of parent interfaces. + * + * @return InterfaceReflection[] + */ + public function getParentInterfaces() + { + if (parent::isInterface()) { + $phpReflections = parent::getInterfaces(); + $zendReflections = array(); + while ($phpReflections && ($phpReflection = array_shift($phpReflections))) { + $instance = new self($phpReflection->getName()); + $zendReflections[] = $instance; + unset($phpReflection); + } + unset($phpReflections); + + return $zendReflections; + } + + return array(); + } + + /** + * Return method reflection by name. + * + * @param string $name + * + * @return MethodDeclarationReflection + */ + public function getMethod($name) + { + $method = new MethodDeclarationReflection($this->getName(), parent::getMethod($name)->getName()); + + return $method; + } + + /** + * Get reflection objects of all methods. + * + * @param int $filter + * + * @return MethodDeclarationReflection[] + */ + public function getMethods($filter = -1) + { + $methods = array(); + foreach (parent::getMethods($filter) as $method) { + $instance = new MethodDeclarationReflection($this->getName(), $method->getName()); + $methods[] = $instance; + } + + return $methods; + } + + /** + * @return string + */ + public function toString() + { + return parent::__toString(); + } + + /** + * @return string + */ + public function __toString() + { + return parent::__toString(); + } + + /** + * Creates a new FileScanner instance. + * + * By having this as a seperate method it allows the method to be overridden + * if a different FileScanner is needed. + * + * @param string $filename + * + * @return FileScanner + */ + protected function createFileScanner($filename) + { + return new FileScanner($filename); + } +} diff --git a/src/Reflection/MethodDeclarationReflection.php b/src/Reflection/MethodDeclarationReflection.php new file mode 100644 index 00000000..90f03454 --- /dev/null +++ b/src/Reflection/MethodDeclarationReflection.php @@ -0,0 +1,454 @@ +getDocComment()) { + return false; + } + + $instance = new DocBlockReflection($this); + + return $instance; + } + + /** + * @param AnnotationManager $annotationManager + * + * @return AnnotationScanner + */ + public function getAnnotations(AnnotationManager $annotationManager) + { + if (($docComment = $this->getDocComment()) == '') { + return false; + } + + if ($this->annotations) { + return $this->annotations; + } + + $cachingFileScanner = $this->createFileScanner($this->getFileName()); + $nameInformation = $cachingFileScanner->getClassNameInformation($this->getDeclaringInterface()->getName()); + + if (!$nameInformation) { + return false; + } + + $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation); + + return $this->annotations; + } + + /** + * Get start line (position) of method. + * + * @param bool $includeDocComment + * + * @return int + */ + public function getStartLine($includeDocComment = false) + { + if ($includeDocComment) { + if ($this->getDocComment() != '') { + return $this->getDocBlock()->getStartLine(); + } + } + + return parent::getStartLine(); + } + + /** + * Get reflection of declaring class. + * + * @return InterfaceReflection + */ + public function getDeclaringInterface() + { + $phpReflection = parent::getDeclaringClass(); + $zendReflection = new InterfaceReflection($phpReflection->getName()); + unset($phpReflection); + + return $zendReflection; + } + + /** + * (PHP 5)
+ * Gets declaring class for the reflected method. + * @link http://php.net/manual/en/reflectionmethod.getdeclaringclass.php + * @return ReflectionClass A ReflectionClass object of the class that the + * reflected method is part of. + */ + final public function getDeclaringClass() + { + throw new \LogicException("Method not supported for method declarations. Use getDeclaringInterface() instead."); + } + + /** + * Get method prototype. + * + * @return array + */ + public function getPrototype($format = self::PROTOTYPE_AS_ARRAY) + { + $returnType = 'mixed'; + $docBlock = $this->getDocBlock(); + if ($docBlock) { + $return = $docBlock->getTag('return'); + $returnTypes = $return->getTypes(); + $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0]; + } + + $declaringClass = $this->getDeclaringInterface(); + $prototype = array( + 'namespace' => $declaringClass->getNamespaceName(), + 'class' => substr($declaringClass->getName(), strlen($declaringClass->getNamespaceName()) + 1), + 'name' => $this->getName(), + 'visibility' => ($this->isPublic() ? 'public' : ($this->isPrivate() ? 'private' : 'protected')), + 'return' => $returnType, + 'arguments' => array(), + ); + + $parameters = $this->getParameters(); + foreach ($parameters as $parameter) { + $prototype['arguments'][$parameter->getName()] = array( + 'type' => $parameter->getType(), + 'required' => !$parameter->isOptional(), + 'by_ref' => $parameter->isPassedByReference(), + 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null, + ); + } + + if ($format == static::PROTOTYPE_AS_STRING) { + $line = $prototype['visibility'].' '.$prototype['return'].' '.$prototype['name'].'('; + $args = array(); + foreach ($prototype['arguments'] as $name => $argument) { + $argsLine = ($argument['type'] ? $argument['type'].' ' : '').($argument['by_ref'] ? '&' : '').'$'.$name; + if (!$argument['required']) { + $argsLine .= ' = '.var_export($argument['default'], true); + } + $args[] = $argsLine; + } + $line .= implode(', ', $args); + $line .= ');'; + + return $line; + } + + return $prototype; + } + + /** + * Get all method parameter reflection objects. + * + * @return ParameterReflection[] + */ + public function getParameters() + { + $phpReflections = parent::getParameters(); + $zendReflections = array(); + while ($phpReflections && ($phpReflection = array_shift($phpReflections))) { + $instance = new ParameterReflection( + array($this->getDeclaringInterface()->getName(), $this->getName()), + $phpReflection->getName() + ); + $zendReflections[] = $instance; + unset($phpReflection); + } + unset($phpReflections); + + return $zendReflections; + } + + /** + * Get method contents. + * + * @param bool $includeDocBlock + * + * @return string + */ + public function getContents($includeDocBlock = true) + { + $docComment = $this->getDocComment(); + $content = ($includeDocBlock && !empty($docComment)) ? $docComment."\n" : ''; + $content .= $this->extractMethodContents(); + + return $content; + } + + /** + * Get method body. + * + * @return string + */ + public function getBody() + { + return; + } + + /** + * Tokenize method string and return concatenated body. + * + * @return string + */ + protected function extractMethodContents() + { + $fileName = $this->getFileName(); + + if ((class_exists($this->class) && false === $fileName) || !file_exists($fileName)) { + return ''; + } + + $lines = array_slice( + file($fileName, FILE_IGNORE_NEW_LINES), + $this->getStartLine() - 1, + ($this->getEndLine() - ($this->getStartLine() - 1)), + true + ); + + $functionLine = implode("\n", $lines); + $tokens = token_get_all(' $token) { + $tokenType = (is_array($token)) ? token_name($token[0]) : $token; + $tokenValue = (is_array($token)) ? $token[1] : $token; + + switch ($tokenType) { + case 'T_FINAL': + case 'T_ABSTRACT': + case 'T_PUBLIC': + case 'T_PROTECTED': + case 'T_PRIVATE': + case 'T_STATIC': + case 'T_FUNCTION': + // check to see if we have a valid function + // then check if we are inside function and have a closure + if ($this->isValidFunction($tokens, $key, $this->getName())) { + // if first instance of tokenType grab prefixed whitespace and append to body + if ($capture === false) { + $body .= $this->extractPrefixedWhitespace($tokens, $key); + } + $body .= $tokenValue; + $capture = true; + } + break; + } + } + + //remove ending whitespace and return + return rtrim($body).';'; + } + + /** + * Take current position and find any whitespace. + * + * @param array $haystack + * @param int $position + * + * @return string + */ + protected function extractPrefixedWhitespace($haystack, $position) + { + $content = ''; + $count = count($haystack); + if ($position + 1 == $count) { + return $content; + } + + for ($i = $position - 1;$i >= 0;$i--) { + $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i]; + $tokenValue = (is_array($haystack[$i])) ? $haystack[$i][1] : $haystack[$i]; + + //search only for whitespace + if ($tokenType == 'T_WHITESPACE') { + $content .= $tokenValue; + } else { + break; + } + } + + return $content; + } + + /** + * Test for ending brace. + * + * @param array $haystack + * @param int $position + * + * @return bool + */ + protected function isEndingBrace($haystack, $position) + { + $count = count($haystack); + + //advance one position + $position = $position + 1; + + if ($position == $count) { + return true; + } + + for ($i = $position;$i < $count; $i++) { + $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i]; + switch ($tokenType) { + case 'T_FINAL': + case 'T_ABSTRACT': + case 'T_PUBLIC': + case 'T_PROTECTED': + case 'T_PRIVATE': + case 'T_STATIC': + return true; + + case 'T_FUNCTION': + // If a function is encountered and that function is not a closure + // then return true. otherwise the function is a closure, return false + if ($this->isValidFunction($haystack, $i)) { + return true; + } + + return false; + + case '}': + case ';'; + case 'T_BREAK': + case 'T_CATCH': + case 'T_DO': + case 'T_ECHO': + case 'T_ELSE': + case 'T_ELSEIF': + case 'T_EVAL': + case 'T_EXIT': + case 'T_FINALLY': + case 'T_FOR': + case 'T_FOREACH': + case 'T_GOTO': + case 'T_IF': + case 'T_INCLUDE': + case 'T_INCLUDE_ONCE': + case 'T_PRINT': + case 'T_STRING': + case 'T_STRING_VARNAME': + case 'T_THROW': + case 'T_USE': + case 'T_VARIABLE': + case 'T_WHILE': + case 'T_YIELD': + + return false; + } + } + } + + /** + * Test to see if current position is valid function or + * closure. Returns true if it's a function and NOT a closure. + * + * @param array $haystack + * @param int $position + * @param string $functionName + * + * @return bool + */ + protected function isValidFunction($haystack, $position, $functionName = null) + { + $isValid = false; + $count = count($haystack); + for ($i = $position + 1; $i < $count; $i++) { + $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i]; + $tokenValue = (is_array($haystack[$i])) ? $haystack[$i][1] : $haystack[$i]; + + //check for occurance of ( or + if ($tokenType == 'T_STRING') { + //check to see if function name is passed, if so isValidDataSource against that + if ($functionName !== null && $tokenValue != $functionName) { + $isValid = false; + break; + } + + $isValid = true; + break; + } elseif ($tokenValue == '(') { + break; + } + } + + return $isValid; + } + + /** + * @return string + */ + public function toString() + { + return parent::__toString(); + } + + /** + * @return string + */ + public function __toString() + { + return parent::__toString(); + } + + /** + * Creates a new FileScanner instance. + * + * By having this as a seperate method it allows the method to be overridden + * if a different FileScanner is needed. + * + * @param string $filename + * + * @return CachingFileScanner + */ + protected function createFileScanner($filename) + { + return new CachingFileScanner($filename); + } +} diff --git a/test/Generator/InterfaceGeneratorTest.php b/test/Generator/InterfaceGeneratorTest.php new file mode 100644 index 00000000..f418a890 --- /dev/null +++ b/test/Generator/InterfaceGeneratorTest.php @@ -0,0 +1,455 @@ + + * @link http://github.com/zendframework/zf2 for the canonical source repository + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +namespace ZendTest\Code\Generator; + +use Zend\Code\Generator\DocBlockGenerator; +use Zend\Code\Generator\InterfaceGenerator; +use Zend\Code\Generator\MethodDeclarationGenerator; +use Zend\Code\Generator\PropertyGenerator; +use Zend\Code\Reflection\InterfaceReflection; + +/** + * @group Zend_Code_Generator + * @group Zend_Code_Generator_Php + */ +class InterfaceGeneratorTest extends \PHPUnit_Framework_TestCase +{ + + public function testNameAccessors() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('FooInterface'); + $this->assertEquals($interfaceGenerator->getName(), 'FooInterface'); + } + + public function testClassDocBlockAccessors() + { + $docBlockGenerator = new DocBlockGenerator(); + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setDocBlock($docBlockGenerator); + $this->assertSame($docBlockGenerator, $interfaceGenerator->getDocBlock()); + } + + public function testExtendedInterfacesAccessors() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setExtendedInterfaces(array('FooInterface', 'BarInterface')); + $this->assertEquals($interfaceGenerator->getExtendedInterfaces(), array('FooInterface', 'BarInterface')); + } + + public function testMethodAccessors() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->addMethods(array( + 'doSomething', + new MethodDeclarationGenerator('doSomethingElse') + )); + + $methods = $interfaceGenerator->getMethods(); + $this->assertEquals(count($methods), 2); + $this->isInstanceOf(current($methods), '\Zend\Code\Generator\PhpMethod'); + + $method = $interfaceGenerator->getMethod('doSomething'); + $this->isInstanceOf($method, '\Zend\Code\Generator\PhpMethod'); + $this->assertEquals($method->getName(), 'doSomething'); + + // add a new property + $interfaceGenerator->addMethod('pretendYouAreATrain'); + $this->assertEquals(count($interfaceGenerator->getMethods()), 3); + } + + public function testSetMethodNoMethodOrArrayThrowsException() + { + $interfaceGenerator = new InterfaceGenerator(); + + $this->setExpectedException( + 'Zend\Code\Generator\Exception\ExceptionInterface', + 'Zend\Code\Generator\InterfaceGenerator::addMethod expects string for name' + ); + + $interfaceGenerator->addMethod(true); + } + + public function testSetMethodNameAlreadyExistsThrowsException() + { + $methodA = new MethodDeclarationGenerator(); + $methodA->setName("foo"); + $methodB = new MethodDeclarationGenerator(); + $methodB->setName("foo"); + + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->addMethodFromGenerator($methodA); + + $this->setExpectedException( + 'Zend\Code\Generator\Exception\InvalidArgumentException', + 'A method by name foo already exists in this class.' + ); + + $interfaceGenerator->addMethodFromGenerator($methodB); + } + + /** + * @group ZF-7361 + */ + public function testHasMethod() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->addMethod('methodOne'); + + $this->assertTrue($interfaceGenerator->hasMethod('methodOne')); + } + + public function testRemoveMethod() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->addMethod('methodOne'); + $this->assertTrue($interfaceGenerator->hasMethod('methodOne')); + + $interfaceGenerator->removeMethod('methodOne'); + $this->assertFalse($interfaceGenerator->hasMethod('methodOne')); + } + + + public function testToString() + { + $interfaceGenerator = InterfaceGenerator::fromArray(array( + 'name' => 'SampleInterface', + 'extendedInterfaces' => array('FooInterface', 'BarInterface'), + 'constants' => array( + array('FOO', 0), + array('name' => 'BAR', 'value' => 1) + ), + 'methods' => array( + array('name' => 'baz') + ), + )); + + $expectedOutput = <<generate(); + $this->assertEquals($expectedOutput, $output, $output); + } + + /** + * @group 4988 + */ + public function testNonNamespaceInterfaceReturnsAllMethods() + { + require_once __DIR__ . '/../TestAsset/NonNamespaceInterface.php'; + + $refl = new InterfaceReflection('NonNamespaceInterface'); + $interfaceGenerator = InterfaceGenerator::fromReflection($refl); + $this->assertCount(1, $interfaceGenerator->getMethods()); + } + + /** + * @group ZF-9602 + */ + public function testSetextendedclassShouldIgnoreEmptyClassnameOnGenerate() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator + ->setName('MyInterface') + ->setExtendedInterfaces(''); + + $expected = <<assertEquals($expected, $interfaceGenerator->generate()); + } + + /** + * @group namespace + */ + public function testCodeGenerationShouldTakeIntoAccountNamespacesFromReflection() + { + $refl = new InterfaceReflection('ZendTest\Code\Generator\TestAsset\InterfaceWithNamespace'); + $interfaceGenerator = InterfaceGenerator::fromReflection($refl); + $this->assertEquals('ZendTest\Code\Generator\TestAsset', $interfaceGenerator->getNamespaceName()); + $this->assertEquals('InterfaceWithNamespace', $interfaceGenerator->getName()); + $expected = <<generate(); + $this->assertEquals($expected, $received, $received); + } + + /** + * @group namespace + */ + public function testSetNameShouldDetermineIfNamespaceSegmentIsPresent() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('My\Namespaced\FooInterface'); + $this->assertEquals('My\Namespaced', $interfaceGenerator->getNamespaceName()); + } + + /** + * @group namespace + */ + public function testPassingANamespacedNameShouldGenerateANamespaceDeclaration() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('My\Namespaced\FunClass'); + $received = $interfaceGenerator->generate(); + $this->assertContains('namespace My\Namespaced;', $received, $received); + } + + /** + * @group namespace + */ + public function testPassingANamespacedClassnameShouldGenerateAClassnameWithoutItsNamespace() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('My\Namespaced\FooBarInterface'); + $received = $interfaceGenerator->generate(); + $this->assertContains('interface FooBarInterface', $received, $received); + } + + /** + * @group ZF2-151 + */ + public function testAddUses() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('My\FooInterface'); + $interfaceGenerator->addUse('My\First\Use\FooInterface'); + $interfaceGenerator->addUse('My\Second\Use\FooInterface', 'MyAlias'); + $generated = $interfaceGenerator->generate(); + + $this->assertContains('use My\First\Use\FooInterface;', $generated); + $this->assertContains('use My\Second\Use\FooInterface as MyAlias;', $generated); + } + + /** + * @group 4990 + */ + public function testAddOneUseTwiceOnlyAddsOne() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('My\FooInterface'); + $interfaceGenerator->addUse('My\First\Use\FooInterface'); + $interfaceGenerator->addUse('My\First\Use\FooInterface'); + $generated = $interfaceGenerator->generate(); + + $this->assertCount(1, $interfaceGenerator->getUses()); + + $this->assertContains('use My\First\Use\FooInterface;', $generated); + } + + /** + * @group 4990 + */ + public function testAddOneUseWithAliasTwiceOnlyAddsOne() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('My\FooInterface'); + $interfaceGenerator->addUse('My\First\Use\FooInterface', 'MyAlias'); + $interfaceGenerator->addUse('My\First\Use\FooInterface', 'MyAlias'); + $generated = $interfaceGenerator->generate(); + + $this->assertCount(1, $interfaceGenerator->getUses()); + + $this->assertContains('use My\First\Use\FooInterface as MyAlias;', $generated); + } + + public function testCreateFromArrayWithDocBlockFromArray() + { + $interfaceGenerator = InterfaceGenerator::fromArray(array( + 'name' => 'SampleInterface', + 'docblock' => array( + 'shortdescription' => 'foo', + ), + )); + + $docBlock = $interfaceGenerator->getDocBlock(); + $this->assertInstanceOf('Zend\Code\Generator\DocBlockGenerator', $docBlock); + } + + public function testCreateFromArrayWithDocBlockInstance() + { + $interfaceGenerator = InterfaceGenerator::fromArray(array( + 'name' => 'SampleInterface', + 'docblock' => new DocBlockGenerator('foo'), + )); + + $docBlock = $interfaceGenerator->getDocBlock(); + $this->assertInstanceOf('Zend\Code\Generator\DocBlockGenerator', $docBlock); + } + + public function testHasMethodInsensitive() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->addMethod('methodOne'); + + $this->assertTrue($interfaceGenerator->hasMethod('methodOne')); + $this->assertTrue($interfaceGenerator->hasMethod('MethoDonE')); + } + + public function testRemoveMethodInsensitive() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->addMethod('methodOne'); + + $interfaceGenerator->removeMethod('METHODONe'); + $this->assertFalse($interfaceGenerator->hasMethod('methodOne')); + } + + public function testGenerateInterfaceAndAddMethod() + { + $interfaceGenerator = new InterfaceGenerator(); + $interfaceGenerator->setName('MyInterface'); + $interfaceGenerator->addMethod('methodOne'); + + $expected = <<generate(); + $this->assertEquals($expected, $output); + } + + /** + * @group 6274 + */ + public function testCanAddConstant() + { + $interfaceGenerator = new InterfaceGenerator(); + + $interfaceGenerator->setName('My\MyInterface'); + $interfaceGenerator->addConstant('X', 'value'); + + $this->assertTrue($interfaceGenerator->hasConstant('X')); + + $constant = $interfaceGenerator->getConstant('X'); + + $this->assertInstanceOf('Zend\Code\Generator\PropertyGenerator', $constant); + $this->assertTrue($constant->isConst()); + $this->assertEquals($constant->getDefaultValue()->getValue(), 'value'); + } + + /** + * @group 6274 + */ + public function testCanAddConstantsWithArrayOfGenerators() + { + $interfaceGenerator = new InterfaceGenerator(); + + $interfaceGenerator->addConstants(array( + new PropertyGenerator('X', 'value1', PropertyGenerator::FLAG_CONSTANT), + new PropertyGenerator('Y', 'value2', PropertyGenerator::FLAG_CONSTANT) + )); + + $this->assertCount(2, $interfaceGenerator->getConstants()); + $this->assertEquals($interfaceGenerator->getConstant('X')->getDefaultValue()->getValue(), 'value1'); + $this->assertEquals($interfaceGenerator->getConstant('Y')->getDefaultValue()->getValue(), 'value2'); + } + + /** + * @group 6274 + */ + public function testCanAddConstantsWithArrayOfKeyValues() + { + $interfaceGenerator = new InterfaceGenerator(); + + $interfaceGenerator->addConstants(array( + array( 'name'=> 'X', 'value' => 'value1'), + array('name' => 'Y', 'value' => 'value2') + )); + + $this->assertCount(2, $interfaceGenerator->getConstants()); + $this->assertEquals($interfaceGenerator->getConstant('X')->getDefaultValue()->getValue(), 'value1'); + $this->assertEquals($interfaceGenerator->getConstant('Y')->getDefaultValue()->getValue(), 'value2'); + } + + /** + * @group 6274 + */ + public function testAddConstantThrowsExceptionWithInvalidName() + { + $this->setExpectedException('InvalidArgumentException'); + + $interfaceGenerator = new InterfaceGenerator(); + + $interfaceGenerator->addConstant(array(), 'value1'); + } + + /** + * @group 6274 + */ + public function testAddConstantThrowsExceptionWithInvalidValue() + { + $this->setExpectedException('InvalidArgumentException'); + + $interfaceGenerator = new InterfaceGenerator(); + + $interfaceGenerator->addConstant('X', null); + } + + /** + * @group 6274 + */ + public function testAddConstantThrowsExceptionOnDuplicate() + { + $this->setExpectedException('InvalidArgumentException'); + + $interfaceGenerator = new InterfaceGenerator(); + + $interfaceGenerator->addConstant('X', 'value1'); + $interfaceGenerator->addConstant('X', 'value1'); + } + + /** + * @group 6274 + */ + public function testConstantsAddedFromReflection() + { + $reflector = new InterfaceReflection('ZendTest\Code\Generator\TestAsset\InterfaceWithConstants'); + $interfaceGenerator = InterfaceGenerator::fromReflection($reflector); + $constant = $interfaceGenerator->getConstant('FOO'); + $this->assertEquals($constant->getDefaultValue()->getValue(), 1); + $constant = $interfaceGenerator->getConstant('BAR'); + $this->assertEquals($constant->getDefaultValue()->getValue(), '2'); + $constant = $interfaceGenerator->getConstant('FOOBAR'); + $this->assertEquals($constant->getDefaultValue()->getValue(), 0x20); + } + +} diff --git a/test/Generator/MethodDeclarationGeneratorTest.php b/test/Generator/MethodDeclarationGeneratorTest.php new file mode 100644 index 00000000..4ddd3d14 --- /dev/null +++ b/test/Generator/MethodDeclarationGeneratorTest.php @@ -0,0 +1,140 @@ + + * @link http://github.com/zendframework/zf2 for the canonical source repository + * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +namespace ZendTest\Code\Generator; + +use Zend\Code\Generator\MethodDeclarationGenerator; +use Zend\Code\Generator\ParameterGenerator; +use Zend\Code\Generator\ValueGenerator; +use Zend\Code\Reflection\MethodDeclarationReflection; + +/** + * @group Zend_Code_Generator + * @group Zend_Code_Generator_Php + */ +class MethodDeclarationGeneratorTest extends \PHPUnit_Framework_TestCase +{ + public function testMethodConstructor() + { + $methodGenerator = new MethodDeclarationGenerator(); + $this->isInstanceOf($methodGenerator, '\Zend\Code\Generator\PhpMethod'); + } + + public function testMethodParameterAccessors() + { + $methodGenerator = new MethodDeclarationGenerator(); + $methodGenerator->setParameters(array('one')); + $params = $methodGenerator->getParameters(); + $param = array_shift($params); + $this->assertTrue($param instanceof \Zend\Code\Generator\ParameterGenerator, + 'Failed because $param was not instance of Zend\Code\Generator\ParameterGenerator'); + } + + + public function testDocBlock() + { + $docblockGenerator = new \Zend\Code\Generator\DocBlockGenerator(); + + $method = new MethodDeclarationGenerator(); + $method->setDocBlock($docblockGenerator); + $this->assertTrue($docblockGenerator === $method->getDocBlock()); + } + + + public function testMethodFromReflection() + { + $refl = new MethodDeclarationReflection('ZendTest\Code\Generator\TestAsset\InterfaceWithConstants', 'someMethod'); + + $methodGenerator = MethodDeclarationGenerator::fromReflection($refl); + $target = <<assertEquals($target, (string) $methodGenerator); + } + + /** + * @group ZF-6444 + */ + public function testMethodWithStaticModifierIsEmitted() + { + $methodGenerator = new MethodDeclarationGenerator(); + $methodGenerator->setName('foo'); + $methodGenerator->setParameters(array('one')); + $methodGenerator->setStatic(true); + + $expected = <<assertEquals($expected, $methodGenerator->generate()); + } + + /** + * @group ZF-7205 + */ + public function testMethodCanHaveDocBlock() + { + $methodGeneratorProperty = new MethodDeclarationGenerator( + 'someFoo', + array(), + MethodDeclarationGenerator::FLAG_STATIC | MethodDeclarationGenerator::FLAG_PUBLIC, + null, + '@var string $someVal This is some val' + ); + + $expected = <<assertEquals($expected, $methodGeneratorProperty->generate()); + } + + /** + * @group ZF-7268 + */ + public function testDefaultValueGenerationDoesNotIncludeTrailingSemicolon() + { + $method = new MethodDeclarationGenerator('setOptions'); + $default = new ValueGenerator(); + $default->setValue(array()); + + $param = new ParameterGenerator('options', 'array'); + $param->setDefaultValue($default); + + $method->setParameter($param); + $generated = $method->generate(); + $this->assertRegexp('/array \$options = array\(\)\)/', $generated, $generated); + } + + public function testCreateFromArray() + { + $methodGenerator = MethodDeclarationGenerator::fromArray(array( + 'name' => 'SampleMethod', + 'body' => 'foo', + 'docblock' => array( + 'shortdescription' => 'foo', + ), + 'static' => true, + 'visibility' => MethodDeclarationGenerator::VISIBILITY_PUBLIC, + )); + + $this->assertEquals('SampleMethod', $methodGenerator->getName()); + $this->assertInstanceOf('Zend\Code\Generator\DocBlockGenerator', $methodGenerator->getDocBlock()); + $this->assertTrue($methodGenerator->isStatic()); + $this->assertEquals(MethodDeclarationGenerator::VISIBILITY_PUBLIC, $methodGenerator->getVisibility()); + } +} diff --git a/test/Generator/TestAsset/InterfaceWithConstants.php b/test/Generator/TestAsset/InterfaceWithConstants.php new file mode 100644 index 00000000..1f6f073e --- /dev/null +++ b/test/Generator/TestAsset/InterfaceWithConstants.php @@ -0,0 +1,22 @@ + + * @link http://github.com/zendframework/zf2 for the canonical source repository + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +namespace ZendTest\Code\Reflection; + +use Zend\Code\Reflection\ClassReflection; +use Zend\Code\Reflection\InterfaceReflection; +use ZendTest\Code\Reflection\TestAsset\InjectableClassReflection; + +/** + * + * @group Zend_Reflection + * @group Zend_Reflection_Class + */ +class InterfaceReflectionTest extends \PHPUnit_Framework_TestCase +{ + public function testMethodReturns() + { + $reflectionInterface = new InterfaceReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface3'); + + $methodByName = $reflectionInterface->getMethod('doSomething'); + $this->assertInstanceOf('Zend\Code\Reflection\MethodDeclarationReflection', $methodByName); + + $methodsAll = $reflectionInterface->getMethods(); + $this->assertEquals(2, count($methodsAll)); + + $firstMethod = array_shift($methodsAll); + $this->assertEquals('doSomething', $firstMethod->getName()); + } + + + public function testParentInterfacesReturn() + { + $reflectionInterface = new InterfaceReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface3'); + $interfaces = $reflectionInterface->getParentInterfaces(); + $this->assertEquals(2, count($interfaces)); + $this->assertEquals('ZendTest\Code\Reflection\TestAsset\TestSampleInterface4', $interfaces[0]->getName()); + $this->assertEquals('ZendTest\Code\Reflection\TestAsset\TestSampleInterface5', $interfaces[1]->getName()); + } + + public function testGetContentsReturnsContents() + { + $reflectionInterface = new InterfaceReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface2'); + $target = <<getContents(); + $this->assertEquals(trim($target), trim($contents)); + } + + + public function testStartLine() + { + $reflectionInterface = new InterfaceReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface1'); + + $this->assertEquals(18, $reflectionInterface->getStartLine()); + $this->assertEquals(5, $reflectionInterface->getStartLine(true)); + } + + public function testGetDeclaringFileReturnsFilename() + { + $reflectionInterface = new InterfaceReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface1'); + $this->assertContains('TestSampleInterface1.php', $reflectionInterface->getDeclaringFile()->getFileName()); + } + + public function testGetAnnotationsWithNoNameInformations() + { + $reflectionInterface = new InjectableClassReflection( + // TestSampleClass5 has the annotations required to get to the + // right point in the getAnnotations method. + 'ZendTest\Code\Reflection\TestAsset\TestSampleInterface1' + ); + + $annotationManager = new \Zend\Code\Annotation\AnnotationManager(); + + $fileScanner = $this->getMockBuilder('Zend\Code\Scanner\FileScanner') + ->disableOriginalConstructor() + ->getMock(); + + $reflectionInterface->setFileScanner($fileScanner); + + $fileScanner->expects($this->any()) + ->method('getClassNameInformation') + ->will($this->returnValue(false)); + + $this->assertFalse($reflectionInterface->getAnnotations($annotationManager)); + } + + public function testGetContentsReturnsEmptyContentsOnEvaldCode() + { + $className = uniqid('InterfaceReflectionTestGenerated'); + + eval('name' . 'space ' . __NAMESPACE__ . '; inter' . 'face ' . $className . '{}'); + + $reflectionInterface = new InterfaceReflection(__NAMESPACE__ . '\\' . $className); + + $this->assertSame('', $reflectionInterface->getContents()); + } + + public function testGetContentsReturnsEmptyContentsOnInternalCode() + { + $reflectionInterface = new ClassReflection('Iterator'); + $this->assertSame('', $reflectionInterface->getContents()); + } + + +} diff --git a/test/Reflection/MethodDeclarationReflectionTest.php b/test/Reflection/MethodDeclarationReflectionTest.php new file mode 100644 index 00000000..7b06ba9b --- /dev/null +++ b/test/Reflection/MethodDeclarationReflectionTest.php @@ -0,0 +1,110 @@ + + * @link http://github.com/zendframework/zf2 for the canonical source repository + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +namespace ZendTest\Code\Reflection; + +use Zend\Code\Reflection\MethodDeclarationReflection; +use Zend\Code\Reflection\MethodReflection; +use ZendTest\Code\Reflection\TestAsset\InjectableMethodReflection; + +/** + * @group Zend_Reflection + * @group Zend_Reflection_Method + */ +class MethodDeclarationReflectionTest extends \PHPUnit_Framework_TestCase +{ + public function testDeclaringInterfaceReturn() + { + $method = new MethodDeclarationReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface1', 'doSomething'); + $this->assertInstanceOf('Zend\Code\Reflection\InterfaceReflection', $method->getDeclaringInterface()); + } + + public function testParameterReturn() + { + $method = new MethodDeclarationReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface1', 'doSomething'); + $parameters = $method->getParameters(); + $this->assertEquals(5, count($parameters)); + $this->assertInstanceOf('Zend\Code\Reflection\ParameterReflection', array_shift($parameters)); + } + + public function testStartLine() + { + $reflectionMethod = new MethodDeclarationReflection('ZendTest\Code\Reflection\TestAsset\TestSampleInterface1', 'doSomething'); + + $this->assertEquals(36, $reflectionMethod->getStartLine()); + $this->assertEquals(20, $reflectionMethod->getStartLine(true)); + } + + public function testGetPrototypeMethod() + { + $reflectionMethod = new MethodDeclarationReflection( + 'ZendTest\Code\Reflection\TestAsset\TestSampleInterface1', + 'doSomethingElse' + ); + $prototype = array( + 'namespace' => 'ZendTest\Code\Reflection\TestAsset', + 'class' => 'TestSampleInterface1', + 'name' => 'doSomethingElse', + 'visibility' => 'public', + 'return' => 'int', + 'arguments' => array( + 'one' => array( + 'type' => 'int', + 'required' => true, + 'by_ref' => false, + 'default' => null, + ), + 'two' => array( + 'type' => 'int', + 'required' => false, + 'by_ref' => false, + 'default' => 2, + ), + 'three' => array( + 'type' => 'string', + 'required' => false, + 'by_ref' => false, + 'default' => 'three', + ), + ), + ); + $this->assertEquals($prototype, $reflectionMethod->getPrototype()); + $this->assertEquals( + 'public int doSomethingElse(int $one, int $two = 2, string $three = \'three\');', + $reflectionMethod->getPrototype(MethodDeclarationReflection::PROTOTYPE_AS_STRING) + ); + + } + + public function testGetAnnotationsWithNoNameInformations() + { + $reflectionMethod = new InjectableMethodReflection( + // TestSampleClass5 has the annotations required to get to the + // right point in the getAnnotations method. + 'ZendTest\Code\Reflection\TestAsset\TestSampleInterface1', + 'orDoNothingAtAll' + ); + + $annotationManager = new \Zend\Code\Annotation\AnnotationManager(); + + $fileScanner = $this->getMockBuilder('Zend\Code\Scanner\CachingFileScanner') + ->disableOriginalConstructor() + ->getMock(); + + $reflectionMethod->setFileScanner($fileScanner); + + $fileScanner->expects($this->any()) + ->method('getClassNameInformation') + ->will($this->returnValue(false)); + + $this->assertFalse($reflectionMethod->getAnnotations($annotationManager)); + } + +} diff --git a/test/Reflection/TestAsset/TestSampleInterface1.php b/test/Reflection/TestAsset/TestSampleInterface1.php new file mode 100644 index 00000000..d751e161 --- /dev/null +++ b/test/Reflection/TestAsset/TestSampleInterface1.php @@ -0,0 +1,67 @@ + Date: Tue, 2 Jun 2015 18:55:27 +0200 Subject: [PATCH 02/10] Fix possible bug. Variable may be undefined. --- src/Generator/MethodGenerator.php | 314 ++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 src/Generator/MethodGenerator.php diff --git a/src/Generator/MethodGenerator.php b/src/Generator/MethodGenerator.php new file mode 100644 index 00000000..8a5909a4 --- /dev/null +++ b/src/Generator/MethodGenerator.php @@ -0,0 +1,314 @@ +setSourceContent($reflectionMethod->getContents(false)); + $method->setSourceDirty(false); + + if ($reflectionMethod->getDocComment() != '') { + $method->setDocBlock(DocBlockGenerator::fromReflection($reflectionMethod->getDocBlock())); + } + + $method->setFinal($reflectionMethod->isFinal()); + + if ($reflectionMethod->isPrivate()) { + $method->setVisibility(self::VISIBILITY_PRIVATE); + } elseif ($reflectionMethod->isProtected()) { + $method->setVisibility(self::VISIBILITY_PROTECTED); + } else { + $method->setVisibility(self::VISIBILITY_PUBLIC); + } + + $method->setStatic($reflectionMethod->isStatic()); + + $method->setName($reflectionMethod->getName()); + + foreach ($reflectionMethod->getParameters() as $reflectionParameter) { + $method->setParameter(ParameterGenerator::fromReflection($reflectionParameter)); + } + + $method->setBody(static::clearBodyIndention($reflectionMethod->getBody())); + + return $method; + } + + /** + * Identify the space indention from the first line and remove this indention + * from all lines + * + * @param string $body + * + * @return string + */ + protected static function clearBodyIndention($body) + { + if (empty($body)) { + return $body; + } + + $lines = explode(PHP_EOL, $body); + + $indention = str_replace(trim($lines[1]), '', $lines[1]); + + foreach ($lines as $key => $line) { + if (substr($line, 0, strlen($indention)) == $indention) { + $lines[$key] = substr($line, strlen($indention)); + } + } + + $body = implode(PHP_EOL, $lines); + + return $body; + } + + /** + * Generate from array + * + * @configkey name string [required] Class Name + * @configkey docblock string The docblock information + * @configkey flags int Flags, one of MethodGenerator::FLAG_ABSTRACT MethodGenerator::FLAG_FINAL + * @configkey parameters string Class which this class is extending + * @configkey body string + * @configkey abstract bool + * @configkey final bool + * @configkey static bool + * @configkey visibility string + * + * @throws Exception\InvalidArgumentException + * @param array $array + * @return MethodGenerator + */ + public static function fromArray(array $array) + { + if (!isset($array['name'])) { + throw new Exception\InvalidArgumentException( + 'Method generator requires that a name is provided for this object' + ); + } + + $method = new static($array['name']); + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'docblock': + $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value); + $method->setDocBlock($docBlock); + break; + case 'flags': + $method->setFlags($value); + break; + case 'parameters': + $method->setParameters($value); + break; + case 'body': + $method->setBody($value); + break; + case 'abstract': + $method->setAbstract($value); + break; + case 'final': + $method->setFinal($value); + break; + case 'static': + $method->setStatic($value); + break; + case 'visibility': + $method->setVisibility($value); + break; + } + } + + return $method; + } + + /** + * @param string $name + * @param array $parameters + * @param int $flags + * @param string $body + * @param DocBlockGenerator|string $docBlock + */ + public function __construct( + $name = null, + array $parameters = array(), + $flags = self::FLAG_PUBLIC, + $body = null, + $docBlock = null + ) { + if ($name) { + $this->setName($name); + } + if ($parameters) { + $this->setParameters($parameters); + } + if ($flags !== self::FLAG_PUBLIC) { + $this->setFlags($flags); + } + if ($body) { + $this->setBody($body); + } + if ($docBlock) { + $this->setDocBlock($docBlock); + } + } + + /** + * @param array $parameters + * @return MethodGenerator + */ + public function setParameters(array $parameters) + { + foreach ($parameters as $parameter) { + $this->setParameter($parameter); + } + + return $this; + } + + /** + * @param ParameterGenerator|array|string $parameter + * @throws Exception\InvalidArgumentException + * @return MethodGenerator + */ + public function setParameter($parameter) + { + if (is_string($parameter)) { + $parameter = new ParameterGenerator($parameter); + } + + if (is_array($parameter)) { + $parameter = ParameterGenerator::fromArray($parameter); + } + + if (!$parameter instanceof ParameterGenerator) { + throw new Exception\InvalidArgumentException(sprintf( + '%s is expecting either a string, array or an instance of %s\ParameterGenerator', + __METHOD__, + __NAMESPACE__ + )); + } + + $this->parameters[$parameter->getName()] = $parameter; + + return $this; + } + + /** + * @return ParameterGenerator[] + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * @param string $body + * @return MethodGenerator + */ + public function setBody($body) + { + $this->body = $body; + return $this; + } + + /** + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * @return string + */ + public function generate() + { + $output = ''; + + $indent = $this->getIndentation(); + + if (($docBlock = $this->getDocBlock()) !== null) { + $docBlock->setIndentation($indent); + $output .= $docBlock->generate(); + } + + $output .= $indent; + + if ($this->isAbstract()) { + $output .= 'abstract '; + } else { + $output .= (($this->isFinal()) ? 'final ' : ''); + } + + $output .= $this->getVisibility() + . (($this->isStatic()) ? ' static' : '') + . ' function ' . $this->getName() . '('; + + $parameters = $this->getParameters(); + $parameterOutput = array(); + if (!empty($parameters)) { + foreach ($parameters as $parameter) { + $parameterOutput[] = $parameter->generate(); + } + + $output .= implode(', ', $parameterOutput); + } + + $output .= ')'; + + if ($this->isAbstract()) { + return $output . ';'; + } + + $output .= self::LINE_FEED . $indent . '{' . self::LINE_FEED; + + if ($this->body) { + $output .= preg_replace('#^((?![a-zA-Z0-9_-]+;).+?)$#m', $indent . $indent . '$1', trim($this->body)) + . self::LINE_FEED; + } + + $output .= $indent . '}' . self::LINE_FEED; + + return $output; + } + + public function __toString() + { + return $this->generate(); + } +} From 78e4d4ce5e4f2ad8d80af852d52323a8a1eaabca Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Tue, 2 Jun 2015 18:56:25 +0200 Subject: [PATCH 03/10] Add composer.phar to .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a7fc91d6..533a730e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ .*.un~ nbproject tmp/ - +composer.phar clover.xml composer.lock coveralls-upload.json From 6674e89922f1ea5b4877d0bc05b1303d96597cfb Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Tue, 2 Jun 2015 19:08:56 +0200 Subject: [PATCH 04/10] Fix PSR-2 compliance issues in tests --- test/Generator/InterfaceGeneratorTest.php | 1 - test/Reflection/InterfaceReflectionTest.php | 2 -- test/Reflection/MethodDeclarationReflectionTest.php | 2 -- 3 files changed, 5 deletions(-) diff --git a/test/Generator/InterfaceGeneratorTest.php b/test/Generator/InterfaceGeneratorTest.php index f418a890..d9698407 100644 --- a/test/Generator/InterfaceGeneratorTest.php +++ b/test/Generator/InterfaceGeneratorTest.php @@ -451,5 +451,4 @@ public function testConstantsAddedFromReflection() $constant = $interfaceGenerator->getConstant('FOOBAR'); $this->assertEquals($constant->getDefaultValue()->getValue(), 0x20); } - } diff --git a/test/Reflection/InterfaceReflectionTest.php b/test/Reflection/InterfaceReflectionTest.php index bb4eb394..837f9932 100644 --- a/test/Reflection/InterfaceReflectionTest.php +++ b/test/Reflection/InterfaceReflectionTest.php @@ -127,6 +127,4 @@ public function testGetContentsReturnsEmptyContentsOnInternalCode() $reflectionInterface = new ClassReflection('Iterator'); $this->assertSame('', $reflectionInterface->getContents()); } - - } diff --git a/test/Reflection/MethodDeclarationReflectionTest.php b/test/Reflection/MethodDeclarationReflectionTest.php index 7b06ba9b..a7b1fea7 100644 --- a/test/Reflection/MethodDeclarationReflectionTest.php +++ b/test/Reflection/MethodDeclarationReflectionTest.php @@ -80,7 +80,6 @@ public function testGetPrototypeMethod() 'public int doSomethingElse(int $one, int $two = 2, string $three = \'three\');', $reflectionMethod->getPrototype(MethodDeclarationReflection::PROTOTYPE_AS_STRING) ); - } public function testGetAnnotationsWithNoNameInformations() @@ -106,5 +105,4 @@ public function testGetAnnotationsWithNoNameInformations() $this->assertFalse($reflectionMethod->getAnnotations($annotationManager)); } - } From 0f1ea2a1a6054964c11ff14a48d93f02e740a099 Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Tue, 2 Jun 2015 19:33:51 +0200 Subject: [PATCH 05/10] Remove unused use statement --- test/Reflection/MethodDeclarationReflectionTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Reflection/MethodDeclarationReflectionTest.php b/test/Reflection/MethodDeclarationReflectionTest.php index a7b1fea7..a4031b6b 100644 --- a/test/Reflection/MethodDeclarationReflectionTest.php +++ b/test/Reflection/MethodDeclarationReflectionTest.php @@ -11,7 +11,6 @@ namespace ZendTest\Code\Reflection; use Zend\Code\Reflection\MethodDeclarationReflection; -use Zend\Code\Reflection\MethodReflection; use ZendTest\Code\Reflection\TestAsset\InjectableMethodReflection; /** From fe243e5f37789fcf6fd0699647d37098aeca4cee Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Tue, 2 Jun 2015 22:50:44 +0200 Subject: [PATCH 06/10] Remove composer.phar from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 533a730e..8c4f6bd3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ .*.un~ nbproject tmp/ -composer.phar clover.xml composer.lock coveralls-upload.json From 8dfc81ec8f100f8a9a20298f488bf57add007df0 Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Thu, 4 Jun 2015 16:17:17 +0200 Subject: [PATCH 07/10] Fix docblocks --- src/Generator/InterfaceGenerator.php | 19 ++++++++++++------- src/Generator/MethodDeclarationGenerator.php | 16 ++++++++++++---- src/Reflection/InterfaceReflection.php | 14 +++++++++++--- .../MethodDeclarationReflection.php | 13 ++++++++++++- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/Generator/InterfaceGenerator.php b/src/Generator/InterfaceGenerator.php index cb5f08af..1e7bb316 100644 --- a/src/Generator/InterfaceGenerator.php +++ b/src/Generator/InterfaceGenerator.php @@ -1,22 +1,27 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * - * - * @method add */ +namespace Zend\Code\Generator; + use Zend\Code\Reflection\InterfaceReflection; use Zend\Code\Generator\Exception\InvalidArgumentException; +/** + * Interface generator + * + * A generator used to generate PHP interfaces. + * + * Class InterfaceGenerator + * @package Zend\Code\Generator + * @author Daan Biesterbos + * @filesource + */ class InterfaceGenerator extends AbstractGenerator { const OBJECT_TYPE = 'interface'; diff --git a/src/Generator/MethodDeclarationGenerator.php b/src/Generator/MethodDeclarationGenerator.php index f29106e6..afb0d35f 100644 --- a/src/Generator/MethodDeclarationGenerator.php +++ b/src/Generator/MethodDeclarationGenerator.php @@ -1,19 +1,27 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ +namespace Zend\Code\Generator; + use Zend\Code\Generator\Exception\InvalidArgumentException; use Zend\Code\Reflection\MethodDeclarationReflection; +/** + * Method declaration generator + * + * A generator used to generate (interface) method declarations. + * + * Class MethodDeclarationGenerator + * @package Zend\Code\Generator + * @author Daan Biesterbos + * @filesource + */ class MethodDeclarationGenerator extends AbstractMemberGenerator { /** diff --git a/src/Reflection/InterfaceReflection.php b/src/Reflection/InterfaceReflection.php index 5e08decf..9b68499e 100644 --- a/src/Reflection/InterfaceReflection.php +++ b/src/Reflection/InterfaceReflection.php @@ -1,7 +1,4 @@ + * @filesource + */ class InterfaceReflection extends ReflectionClass implements ReflectionInterface { /** diff --git a/src/Reflection/MethodDeclarationReflection.php b/src/Reflection/MethodDeclarationReflection.php index 90f03454..760d861b 100644 --- a/src/Reflection/MethodDeclarationReflection.php +++ b/src/Reflection/MethodDeclarationReflection.php @@ -12,9 +12,19 @@ use ReflectionMethod as PhpReflectionMethod; use Zend\Code\Annotation\AnnotationManager; +use Zend\Code\Reflection\DocBlock\Tag\ReturnTag; use Zend\Code\Scanner\AnnotationScanner; use Zend\Code\Scanner\CachingFileScanner; + +/** + * Reflection class used for method declarations in interfaces. + * + * Class MethodDeclarationReflection + * @package Zend\Code\Reflection + * @author Daan Biesterbos + * @filesource + */ class MethodDeclarationReflection extends PhpReflectionMethod implements ReflectionInterface { /** @@ -111,7 +121,7 @@ public function getDeclaringInterface() * (PHP 5)
* Gets declaring class for the reflected method. * @link http://php.net/manual/en/reflectionmethod.getdeclaringclass.php - * @return ReflectionClass A ReflectionClass object of the class that the + * @return \ReflectionClass A ReflectionClass object of the class that the * reflected method is part of. */ final public function getDeclaringClass() @@ -129,6 +139,7 @@ public function getPrototype($format = self::PROTOTYPE_AS_ARRAY) $returnType = 'mixed'; $docBlock = $this->getDocBlock(); if ($docBlock) { + /** @var ReturnTag $return */ $return = $docBlock->getTag('return'); $returnTypes = $return->getTypes(); $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0]; From 446815cb82cf88ddfbdc430ebb537afd9fdb271d Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Fri, 5 Jun 2015 00:01:01 +0200 Subject: [PATCH 08/10] Add changes method declaration generator, fix docblock + add test --- src/Generator/MethodDeclarationGenerator.php | 33 ++++++++----------- .../MethodDeclarationReflection.php | 6 ++-- .../MethodDeclarationGeneratorTest.php | 12 +++++++ 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Generator/MethodDeclarationGenerator.php b/src/Generator/MethodDeclarationGenerator.php index afb0d35f..ba6e5dbc 100644 --- a/src/Generator/MethodDeclarationGenerator.php +++ b/src/Generator/MethodDeclarationGenerator.php @@ -10,6 +10,7 @@ namespace Zend\Code\Generator; use Zend\Code\Generator\Exception\InvalidArgumentException; +use Zend\Code\Generator\Exception\RuntimeException; use Zend\Code\Reflection\MethodDeclarationReflection; /** @@ -246,34 +247,26 @@ public function generate() } /** - * @ignore - * @throws \LogicException - * @param bool $isAbstract - * - * @return void + * @param string $visibility + * @return AbstractMemberGenerator */ - public function setAbstract($isAbstract) + public function setVisibility($visibility) { - throw new \LogicException( - "Abstract methods are not supported. To generate abstract methods use the MethodGenerator. " . - "The intended use of this generator is to work with interfaces. See method getMethodImplementation() to export a pre configured instance of this method " . - "declaration that you can use to generate your (abstract) class method." - ); + if ($visibility !== self::VISIBILITY_PUBLIC) { + throw new RuntimeException("Method declarations for interfaces must be public."); + } + + return $this; } /** - * @ignore - * @throws \LogicException + * Method declarations for interfaces are always public. * - * @return bool + * @return string */ - public function isAbstract() + public function getVisibility() { - throw new \LogicException( - "Abstract methods are not supported. To generate abstract methods use the MethodGenerator. " . - "The intended use of this generator is to work with interfaces. See method getMethodImplementation() to export a pre configured instance of this method " . - "declaration that you can use to generate your (abstract) class method." - ); + return self::VISIBILITY_PUBLIC; } /** diff --git a/src/Reflection/MethodDeclarationReflection.php b/src/Reflection/MethodDeclarationReflection.php index 760d861b..57b99760 100644 --- a/src/Reflection/MethodDeclarationReflection.php +++ b/src/Reflection/MethodDeclarationReflection.php @@ -1,7 +1,4 @@ assertTrue($methodGenerator->isStatic()); $this->assertEquals(MethodDeclarationGenerator::VISIBILITY_PUBLIC, $methodGenerator->getVisibility()); } + + public function testShouldThrowExceptionsForNonSupportedMethods() + { + $methodGenerator = new MethodDeclarationGenerator(); + + // Abstract methods are not supported for interface method declarations. + $this->setExpectedException( + 'Zend\Code\Generator\Exception\RuntimeException', + "Method declarations for interfaces must be public." + ); + $methodGenerator->setVisibility($methodGenerator::VISIBILITY_PROTECTED); + } } From 03bc5d047620c10fd101b8378ba12bbb56bc63a8 Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Fri, 12 Jun 2015 11:42:51 +0200 Subject: [PATCH 09/10] Fix docblocks + short array syntax --- src/Generator/InterfaceGenerator.php | 30 +++++++------------ src/Generator/MethodDeclarationGenerator.php | 16 ++-------- src/Reflection/InterfaceReflection.php | 9 ------ .../MethodDeclarationReflection.php | 8 ----- test/Generator/InterfaceGeneratorTest.php | 1 - .../MethodDeclarationGeneratorTest.php | 1 - test/Reflection/InterfaceReflectionTest.php | 1 - .../MethodDeclarationReflectionTest.php | 1 - 8 files changed, 13 insertions(+), 54 deletions(-) diff --git a/src/Generator/InterfaceGenerator.php b/src/Generator/InterfaceGenerator.php index 1e7bb316..30325985 100644 --- a/src/Generator/InterfaceGenerator.php +++ b/src/Generator/InterfaceGenerator.php @@ -12,16 +12,6 @@ use Zend\Code\Reflection\InterfaceReflection; use Zend\Code\Generator\Exception\InvalidArgumentException; -/** - * Interface generator - * - * A generator used to generate PHP interfaces. - * - * Class InterfaceGenerator - * @package Zend\Code\Generator - * @author Daan Biesterbos - * @filesource - */ class InterfaceGenerator extends AbstractGenerator { const OBJECT_TYPE = 'interface'; @@ -54,17 +44,17 @@ class InterfaceGenerator extends AbstractGenerator /** * @var array Array of string names */ - protected $extendedInterfaces = array(); + protected $extendedInterfaces = []; /** * @var PropertyGenerator[] Array of constants */ - protected $constants = array(); + protected $constants = []; /** * @var MethodDeclarationGenerator[] Array of methods */ - protected $methods = array(); + protected $methods = []; /** * Build a Code Generation Php Object from a Class Reflection. @@ -90,7 +80,7 @@ public static function fromReflection(InterfaceReflection $reflection) /* @var \Zend\Code\Reflection\ClassReflection[] $parentInterfaces */ $parentInterfaces = $reflection->getParentInterfaces(); - $interfaceNames = array(); + $interfaceNames = []; if ($parentInterfaces) { foreach ($parentInterfaces as $parentInterface) { $interfaceNames[] = $parentInterface->getName(); @@ -99,7 +89,7 @@ public static function fromReflection(InterfaceReflection $reflection) $ig->setExtendedInterfaces($interfaceNames); - $constants = array(); + $constants = []; foreach ($reflection->getConstants() as $name => $value) { $constants[] = array( 'name' => $name, @@ -109,7 +99,7 @@ public static function fromReflection(InterfaceReflection $reflection) $ig->addConstants($constants); - $methods = array(); + $methods = []; foreach ($reflection->getMethods() as $reflectionMethod) { $className = ($ig->getNamespaceName()) ? $ig->getNamespaceName() . "\\" . $ig->getName() : $ig->getName(); if ($reflectionMethod->getDeclaringInterface()->getName() == $className) { @@ -193,8 +183,8 @@ public function __construct( $name = null, $namespaceName = null, $flags = null, - $parents = array(), - $methods = array(), + $parents = [], + $methods = [], $docBlock = null ) { if ($name !== null) { @@ -209,7 +199,7 @@ public function __construct( if (is_array($parents)) { $this->setExtendedInterfaces($parents); } - if ($methods !== array()) { + if ($methods !== []) { $this->addMethods($methods); } if ($docBlock !== null) { @@ -550,7 +540,7 @@ public function addMethods(array $methods) */ public function addMethod( $name = null, - array $parameters = array(), + array $parameters = [], $flags = MethodDeclarationGenerator::FLAG_PUBLIC, $body = null, $docBlock = null diff --git a/src/Generator/MethodDeclarationGenerator.php b/src/Generator/MethodDeclarationGenerator.php index ba6e5dbc..f191aa1a 100644 --- a/src/Generator/MethodDeclarationGenerator.php +++ b/src/Generator/MethodDeclarationGenerator.php @@ -13,16 +13,6 @@ use Zend\Code\Generator\Exception\RuntimeException; use Zend\Code\Reflection\MethodDeclarationReflection; -/** - * Method declaration generator - * - * A generator used to generate (interface) method declarations. - * - * Class MethodDeclarationGenerator - * @package Zend\Code\Generator - * @author Daan Biesterbos - * @filesource - */ class MethodDeclarationGenerator extends AbstractMemberGenerator { /** @@ -33,7 +23,7 @@ class MethodDeclarationGenerator extends AbstractMemberGenerator /** * @var ParameterGenerator[] */ - protected $parameters = array(); + protected $parameters = []; /** * @param MethodDeclarationReflection $reflectionMethod @@ -122,7 +112,7 @@ public static function fromArray(array $array) */ public function __construct( $name = null, - array $parameters = array(), + array $parameters = [], $flags = self::FLAG_PUBLIC, $body = null, $docBlock = null @@ -233,7 +223,7 @@ public function generate() .' function '.$this->getName().'('; $parameters = $this->getParameters(); - $parameterOutput = array(); + $parameterOutput = []; if (!empty($parameters)) { foreach ($parameters as $parameter) { $parameterOutput[] = $parameter->generate(); diff --git a/src/Reflection/InterfaceReflection.php b/src/Reflection/InterfaceReflection.php index 9b68499e..65876a8b 100644 --- a/src/Reflection/InterfaceReflection.php +++ b/src/Reflection/InterfaceReflection.php @@ -16,15 +16,6 @@ use Zend\Code\Scanner\FileScanner; use Zend\Code\Exception\ExceptionInterface; -/** - * Reflection class for PHP interfaces. This class adds some functionality that is needed to generate interfaces. - * - * - * Class InterfaceReflection - * @package Zend\Code\Reflection - * @author Daan Biesterbos - * @filesource - */ class InterfaceReflection extends ReflectionClass implements ReflectionInterface { /** diff --git a/src/Reflection/MethodDeclarationReflection.php b/src/Reflection/MethodDeclarationReflection.php index 57b99760..ca1fa788 100644 --- a/src/Reflection/MethodDeclarationReflection.php +++ b/src/Reflection/MethodDeclarationReflection.php @@ -15,14 +15,6 @@ use Zend\Code\Scanner\AnnotationScanner; use Zend\Code\Scanner\CachingFileScanner; -/** - * Reflection class used for method declarations in interfaces. - * - * Class MethodDeclarationReflection - * @package Zend\Code\Reflection - * @author Daan Biesterbos - * @filesource - */ class MethodDeclarationReflection extends PhpReflectionMethod implements ReflectionInterface { /** diff --git a/test/Generator/InterfaceGeneratorTest.php b/test/Generator/InterfaceGeneratorTest.php index d9698407..f6582bb4 100644 --- a/test/Generator/InterfaceGeneratorTest.php +++ b/test/Generator/InterfaceGeneratorTest.php @@ -2,7 +2,6 @@ /** * Zend Framework (http://framework.zend.com/) * - * @author Daan Biesterbos * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License diff --git a/test/Generator/MethodDeclarationGeneratorTest.php b/test/Generator/MethodDeclarationGeneratorTest.php index b4a1da4c..eec32fb5 100644 --- a/test/Generator/MethodDeclarationGeneratorTest.php +++ b/test/Generator/MethodDeclarationGeneratorTest.php @@ -2,7 +2,6 @@ /** * Zend Framework (http://framework.zend.com/) * - * @author Daan Biesterbos * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License diff --git a/test/Reflection/InterfaceReflectionTest.php b/test/Reflection/InterfaceReflectionTest.php index 837f9932..4c8f63ba 100644 --- a/test/Reflection/InterfaceReflectionTest.php +++ b/test/Reflection/InterfaceReflectionTest.php @@ -2,7 +2,6 @@ /** * Zend Framework (http://framework.zend.com/) * - * @author Daan Biesterbos * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License diff --git a/test/Reflection/MethodDeclarationReflectionTest.php b/test/Reflection/MethodDeclarationReflectionTest.php index a4031b6b..1ceb3b38 100644 --- a/test/Reflection/MethodDeclarationReflectionTest.php +++ b/test/Reflection/MethodDeclarationReflectionTest.php @@ -2,7 +2,6 @@ /** * Zend Framework (http://framework.zend.com/) * - * @author Daan Biesterbos * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License From 54652c6acb21eef8f16bf6ad6ffba5e9605f981e Mon Sep 17 00:00:00 2001 From: Daan Biesterbos Date: Fri, 12 Jun 2015 12:21:01 +0200 Subject: [PATCH 10/10] Undo most short array statements --- src/Generator/InterfaceGenerator.php | 14 +++++++------- src/Generator/MethodDeclarationGenerator.php | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Generator/InterfaceGenerator.php b/src/Generator/InterfaceGenerator.php index 30325985..9d0c82fa 100644 --- a/src/Generator/InterfaceGenerator.php +++ b/src/Generator/InterfaceGenerator.php @@ -80,7 +80,7 @@ public static function fromReflection(InterfaceReflection $reflection) /* @var \Zend\Code\Reflection\ClassReflection[] $parentInterfaces */ $parentInterfaces = $reflection->getParentInterfaces(); - $interfaceNames = []; + $interfaceNames = array(); if ($parentInterfaces) { foreach ($parentInterfaces as $parentInterface) { $interfaceNames[] = $parentInterface->getName(); @@ -89,7 +89,7 @@ public static function fromReflection(InterfaceReflection $reflection) $ig->setExtendedInterfaces($interfaceNames); - $constants = []; + $constants = array(); foreach ($reflection->getConstants() as $name => $value) { $constants[] = array( 'name' => $name, @@ -99,7 +99,7 @@ public static function fromReflection(InterfaceReflection $reflection) $ig->addConstants($constants); - $methods = []; + $methods = array(); foreach ($reflection->getMethods() as $reflectionMethod) { $className = ($ig->getNamespaceName()) ? $ig->getNamespaceName() . "\\" . $ig->getName() : $ig->getName(); if ($reflectionMethod->getDeclaringInterface()->getName() == $className) { @@ -183,8 +183,8 @@ public function __construct( $name = null, $namespaceName = null, $flags = null, - $parents = [], - $methods = [], + $parents = array(), + $methods = array(), $docBlock = null ) { if ($name !== null) { @@ -199,7 +199,7 @@ public function __construct( if (is_array($parents)) { $this->setExtendedInterfaces($parents); } - if ($methods !== []) { + if ($methods !== array()) { $this->addMethods($methods); } if ($docBlock !== null) { @@ -540,7 +540,7 @@ public function addMethods(array $methods) */ public function addMethod( $name = null, - array $parameters = [], + array $parameters = array(), $flags = MethodDeclarationGenerator::FLAG_PUBLIC, $body = null, $docBlock = null diff --git a/src/Generator/MethodDeclarationGenerator.php b/src/Generator/MethodDeclarationGenerator.php index f191aa1a..823a7b9a 100644 --- a/src/Generator/MethodDeclarationGenerator.php +++ b/src/Generator/MethodDeclarationGenerator.php @@ -112,7 +112,7 @@ public static function fromArray(array $array) */ public function __construct( $name = null, - array $parameters = [], + array $parameters = array(), $flags = self::FLAG_PUBLIC, $body = null, $docBlock = null @@ -223,7 +223,7 @@ public function generate() .' function '.$this->getName().'('; $parameters = $this->getParameters(); - $parameterOutput = []; + $parameterOutput = array(); if (!empty($parameters)) { foreach ($parameters as $parameter) { $parameterOutput[] = $parameter->generate();