diff --git a/.gitignore b/.gitignore index a7fc91d6..8c4f6bd3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ .*.un~ nbproject tmp/ - clover.xml composer.lock coveralls-upload.json diff --git a/src/Generator/InterfaceGenerator.php b/src/Generator/InterfaceGenerator.php new file mode 100644 index 00000000..9d0c82fa --- /dev/null +++ b/src/Generator/InterfaceGenerator.php @@ -0,0 +1,728 @@ +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/MethodDeclarationGenerator.php b/src/Generator/MethodDeclarationGenerator.php new file mode 100644 index 00000000..823a7b9a --- /dev/null +++ b/src/Generator/MethodDeclarationGenerator.php @@ -0,0 +1,269 @@ +isPublic()) { + throw new InvalidArgumentException('Interfaces can only contain public methods!'); + } + + $method->setSourceDirty(false); + if ($reflectionMethod->getDocComment() != '') { + $method->setDocBlock(DocBlockGenerator::fromReflection($reflectionMethod->getDocBlock())); + } + + $method->setStatic($reflectionMethod->isStatic()); + $method->setName($reflectionMethod->getName()); + + foreach ($reflectionMethod->getParameters() as $reflectionParameter) { + $method->setParameter(ParameterGenerator::fromReflection($reflectionParameter)); + } + + return $method; + } + + /** + * 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 InvalidArgumentException + * + * @param array $array + * + * @return MethodDeclarationGenerator + */ + public static function fromArray(array $array) + { + if (!isset($array['name'])) { + 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))) { + 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 'static': + $method->setStatic($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 ($docBlock) { + $this->setDocBlock($docBlock); + } + } + + /** + * 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) { + $this->setParameter($parameter); + } + + return $this; + } + + /** + * @param ParameterGenerator|array|string $parameter + * + * @throws InvalidArgumentException + * + * @return $this + */ + 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 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; + } + + /** + * @return string + */ + public function generate() + { + $output = ''; + + $indent = $this->getIndentation(); + + if (($docBlock = $this->getDocBlock()) !== null) { + $docBlock->setIndentation($indent); + $output .= $docBlock->generate(); + } + + $output .= $indent; + + $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(); + } + + $output .= implode(', ', $parameterOutput); + } + $output .= ');'; + + return $output; + } + + /** + * @param string $visibility + * @return AbstractMemberGenerator + */ + public function setVisibility($visibility) + { + if ($visibility !== self::VISIBILITY_PUBLIC) { + throw new RuntimeException("Method declarations for interfaces must be public."); + } + + return $this; + } + + /** + * Method declarations for interfaces are always public. + * + * @return string + */ + public function getVisibility() + { + return self::VISIBILITY_PUBLIC; + } + + /** + * @return string + */ + public function __toString() + { + return $this->generate(); + } +} diff --git a/src/Generator/MethodGenerator.php b/src/Generator/MethodGenerator.php index 1f405dbc..8a5909a4 100644 --- a/src/Generator/MethodGenerator.php +++ b/src/Generator/MethodGenerator.php @@ -280,6 +280,7 @@ public function generate() . ' function ' . $this->getName() . '('; $parameters = $this->getParameters(); + $parameterOutput = array(); if (!empty($parameters)) { foreach ($parameters as $parameter) { $parameterOutput[] = $parameter->generate(); diff --git a/src/Reflection/InterfaceReflection.php b/src/Reflection/InterfaceReflection.php new file mode 100644 index 00000000..65876a8b --- /dev/null +++ b/src/Reflection/InterfaceReflection.php @@ -0,0 +1,221 @@ +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..ca1fa788 --- /dev/null +++ b/src/Reflection/MethodDeclarationReflection.php @@ -0,0 +1,455 @@ +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) { + /** @var ReturnTag $return */ + $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..f6582bb4 --- /dev/null +++ b/test/Generator/InterfaceGeneratorTest.php @@ -0,0 +1,453 @@ +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..eec32fb5 --- /dev/null +++ b/test/Generator/MethodDeclarationGeneratorTest.php @@ -0,0 +1,151 @@ +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()); + } + + 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); + } +} 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 @@ +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..1ceb3b38 --- /dev/null +++ b/test/Reflection/MethodDeclarationReflectionTest.php @@ -0,0 +1,106 @@ +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 @@ +