.laminas-ci.json000064400000000100147133776660007545 0ustar00{ "ignore_php_platform_requirements": { "8.4": true } } COPYRIGHT.md000064400000000133147133776660006457 0ustar00Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) LICENSE.md000064400000002732147133776660006200 0ustar00Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Laminas Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. README.md000064400000007061147133776660006053 0ustar00# laminas-code [![Continuous Integration](https://github.com/laminas/laminas-code/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/laminas/laminas-code/actions/workflows/continuous-integration.yml) [![Type Coverage](https://shepherd.dev/github/laminas/laminas-code/coverage.svg)](https://shepherd.dev/github/laminas/laminas-code) > ## πŸ‡·πŸ‡Ί Русским Π³Ρ€Π°ΠΆΠ΄Π°Π½Π°ΠΌ > > ΠœΡ‹, участники Laminas, Ρ€ΠΎΠ΄ΠΈΠ»ΠΈΡΡŒ ΠΈ ΠΆΠΈΠ²Π΅ΠΌ Π² Ρ€Π°Π·Π½Ρ‹Ρ… странах. Π£ ΠΌΠ½ΠΎΠ³ΠΈΡ… ΠΈΠ· нас Π΅ΡΡ‚ΡŒ Π΄Ρ€ΡƒΠ·ΡŒΡ, родствСнники ΠΈ ΠΊΠΎΠ»Π»Π΅Π³ΠΈ ΠΊΠ°ΠΊ Π² России, Ρ‚Π°ΠΊ ΠΈ Π² Π£ΠΊΡ€Π°ΠΈΠ½Π΅. НСкоторыС ΠΈΠ· нас Ρ€ΠΎΠ΄ΠΈΠ»ΠΈΡΡŒ Π² России. НСкоторыС ΠΈΠ· нас ΠΆΠΈΠ²ΡƒΡ‚ Π² России. Π£ Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π±Π°Π±ΡƒΡˆΠΊΠΈ ΠΈ Π΄Π΅Π΄ΡƒΡˆΠΊΠΈ ΡΡ€Π°ΠΆΠ°Π»ΠΈΡΡŒ с Ρ„Π°ΡˆΠΈΡΡ‚Π°ΠΌΠΈ Π²ΠΎ Π’Ρ‚ΠΎΡ€ΠΎΠΉ ΠΌΠΈΡ€ΠΎΠ²ΠΎΠΉ Π²ΠΎΠΉΠ½Π΅. Π—Π΄Π΅ΡΡŒ Π½ΠΈΠΊΡ‚ΠΎ Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Ρ„Π°ΡˆΠΈΠ·ΠΌ. > > Π£ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΈΠ· нас Π΅ΡΡ‚ΡŒ украинская родствСнница, которая спаслась ΠΈΠ· Π΄ΠΎΠΌΠ° вмСстС с сыном. ПоСзд задСрТался ΠΈΠ·-Π·Π° Π±ΠΎΠΌΠ±Π΅ΠΆΠΊΠΈ Π½Π° Π΄ΠΎΡ€ΠΎΠ³Π΅ Π²ΠΏΠ΅Ρ€Π΅Π΄ΠΈ. Π£ нас Π΅ΡΡ‚ΡŒ Π΄Ρ€ΡƒΠ·ΡŒΡ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ прячутся Π² Π±ΠΎΠΌΠ±ΠΎΡƒΠ±Π΅ΠΆΠΈΡ‰Π°Ρ…. ΠœΡ‹ с Ρ‚Ρ€Π΅Π²ΠΎΠ³ΠΎΠΉ ΠΆΠ΄Π΅ΠΌ вСсточки ΠΎΡ‚ Π½ΠΈΡ… послС Π²ΠΎΠ·Π΄ΡƒΡˆΠ½Ρ‹Ρ… Π½Π°Π»Π΅Ρ‚ΠΎΠ², ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ бСспорядочно наносят ΡƒΠ΄Π°Ρ€Ρ‹ ΠΈ ΠΏΠΎΠΏΠ°Π΄Π°ΡŽΡ‚ ΠΏΠΎ Π±ΠΎΠ»ΡŒΠ½ΠΈΡ†Π°ΠΌ, школам, дСтским садам ΠΈ Π΄ΠΎΠΌΠ°ΠΌ. ΠœΡ‹ Π½Π΅ Π±Π΅Ρ€Π΅ΠΌ это ΠΈΠ· ΠΊΠ°ΠΊΠΈΡ…-Π»ΠΈΠ±ΠΎ БМИ. ΠœΡ‹ наблюдаСм это Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ. > > Π’Ρ‹ довСряСтС Π½Π°ΠΌ достаточно, Ρ‡Ρ‚ΠΎΠ± ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ наши ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡ‹, ΠΈ ΠΌΡ‹ просим вас Π΄ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒΡΡ Π½Π°ΠΌ вновь. ΠœΡ‹ нуТдаСмся Π² ΠΏΠΎΠΌΠΎΡ‰ΠΈ. Π’Ρ‹Ρ…ΠΎΠ΄ΠΈΡ‚Π΅ ΠΈ протСстуйтС ΠΏΡ€ΠΎΡ‚ΠΈΠ² этой бСсполСзной Π²ΠΎΠΉΠ½Ρ‹. ΠžΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚Π΅ ΠΊΡ€ΠΎΠ²ΠΎΠΏΡ€ΠΎΠ»ΠΈΡ‚ΠΈΠ΅. Π‘ΠΊΠ°ΠΆΠΈΡ‚Π΅ "НСт Π²ΠΎΠΉΠ½Π΅!" > > ## πŸ‡ΊπŸ‡Έ To Citizens of Russia > > We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism. > > One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences. > > You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!" `Laminas\Code\Generator` provides facilities to generate arbitrary code using an object-oriented interface, both to create new code as well as to update existing code. While the current implementation is limited to generating PHP code, you can easily extend the base class in order to provide code generation for other tasks: JavaScript, configuration files, apache vhosts, etc. - File issues at https://github.com/laminas/laminas-code/issues - Documentation is at https://docs.laminas.dev/laminas-code/ - Migration documentation from v2 to v3 is at https://docs.laminas.dev/laminas-code/migration/ composer.json000064400000003703147133776660007315 0ustar00{ "name": "laminas/laminas-code", "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", "keywords": [ "laminas", "laminasframework", "code" ], "homepage": "https://laminas.dev", "license": "BSD-3-Clause", "require": { "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { "ext-phar": "*", "doctrine/annotations": "^2.0.1", "laminas/laminas-coding-standard": "^3.0.0", "laminas/laminas-stdlib": "^3.18.0", "phpunit/phpunit": "^10.5.37", "psalm/plugin-phpunit": "^0.19.0", "vimeo/psalm": "^5.15.0" }, "suggest": { "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", "laminas/laminas-stdlib": "Laminas\\Stdlib component" }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true }, "platform": { "php": "8.1.99" }, "sort-packages": true }, "autoload": { "psr-4": { "Laminas\\Code\\": "src/" } }, "autoload-dev": { "psr-4": { "LaminasTest\\Code\\": "test/" } }, "scripts": { "check": [ "@cs-check", "@test" ], "cs-check": "phpcs", "cs-fix": "phpcbf", "static-analysis": "psalm --shepherd --stats", "test": "phpunit --colors=always", "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" }, "support": { "issues": "https://github.com/laminas/laminas-code/issues", "forum": "https://discourse.laminas.dev", "chat": "https://laminas.dev/chat", "source": "https://github.com/laminas/laminas-code", "docs": "https://docs.laminas.dev/laminas-code/", "rss": "https://github.com/laminas/laminas-code/releases.atom" } } src/DeclareStatement.php000064400000005225147133776660011320 0ustar00 'integer', self::STRICT_TYPES => 'integer', self::ENCODING => 'string', ]; protected string $directive; /** @var int|string */ protected $value; /** @param int|string $value */ private function __construct(string $directive, $value) { $this->directive = $directive; $this->value = $value; } public function getDirective(): string { return $this->directive; } /** * @return int|string */ public function getValue() { return $this->value; } public static function ticks(int $value): self { return new self(self::TICKS, $value); } public static function strictTypes(int $value): self { return new self(self::STRICT_TYPES, $value); } public static function encoding(string $value): self { return new self(self::ENCODING, $value); } /** * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. */ public static function fromArray(array $config): self { $directive = key($config); $value = $config[$directive]; if (! isset(self::ALLOWED[$directive])) { throw new InvalidArgumentException( sprintf( 'Declare directive must be one of: %s.', implode(', ', array_keys(self::ALLOWED)) ) ); } if (gettype($value) !== self::ALLOWED[$directive]) { throw new InvalidArgumentException( sprintf( 'Declare value invalid. Expected %s, got %s.', self::ALLOWED[$directive], gettype($value) ) ); } $method = str_replace('_', '', lcfirst(ucwords($directive, '_'))); return self::{$method}($value); } public function getStatement(): string { $value = is_string($this->value) ? '\'' . $this->value . '\'' : $this->value; return sprintf('declare(%s=%s);', $this->directive, $value); } } src/Exception/BadMethodCallException.php000064400000000215147133776660014326 0ustar00setOptions($options); } } /** * @param bool $isSourceDirty * @return static */ public function setSourceDirty($isSourceDirty = true) { $this->isSourceDirty = (bool) $isSourceDirty; return $this; } /** * @return bool */ public function isSourceDirty() { return $this->isSourceDirty; } /** * @param string $indentation * @return static */ public function setIndentation($indentation) { $this->indentation = (string) $indentation; return $this; } /** * @return string */ public function getIndentation() { return $this->indentation; } /** * @param ?string $sourceContent * @return static */ public function setSourceContent($sourceContent) { $this->sourceContent = (string) $sourceContent; return $this; } /** * @return ?string */ public function getSourceContent() { return $this->sourceContent; } /** * @param array|Traversable $options * @throws Exception\InvalidArgumentException * @return static */ public function setOptions($options) { if (! is_array($options) && ! $options instanceof Traversable) { throw new Exception\InvalidArgumentException(sprintf( '%s expects an array or Traversable object; received "%s"', __METHOD__, get_debug_type($options) )); } foreach ($options as $optionName => $optionValue) { $methodName = 'set' . $optionName; if (method_exists($this, $methodName)) { $this->{$methodName}($optionValue); } } return $this; } } src/Generator/AbstractMemberGenerator.php000064400000012555147133776660014570 0ustar00flags = $flags; return $this; } /** * @param int $flag * @return static */ public function addFlag($flag) { $this->setFlags($this->flags | $flag); return $this; } /** * @param int $flag * @return static */ public function removeFlag($flag) { $this->setFlags($this->flags & ~$flag); return $this; } /** * @param bool $isAbstract * @return static */ public function setAbstract($isAbstract) { return $isAbstract ? $this->addFlag(self::FLAG_ABSTRACT) : $this->removeFlag(self::FLAG_ABSTRACT); } /** * @return bool */ public function isAbstract() { return (bool) ($this->flags & self::FLAG_ABSTRACT); } /** * @param bool $isInterface * @return static */ public function setInterface($isInterface) { return $isInterface ? $this->addFlag(self::FLAG_INTERFACE) : $this->removeFlag(self::FLAG_INTERFACE); } /** * @return bool */ public function isInterface() { return (bool) ($this->flags & self::FLAG_INTERFACE); } /** * @param bool $isFinal * @return static */ public function setFinal($isFinal) { return $isFinal ? $this->addFlag(self::FLAG_FINAL) : $this->removeFlag(self::FLAG_FINAL); } /** * @return bool */ public function isFinal() { return (bool) ($this->flags & self::FLAG_FINAL); } /** * @param bool $isStatic * @return static */ public function setStatic($isStatic) { return $isStatic ? $this->addFlag(self::FLAG_STATIC) : $this->removeFlag(self::FLAG_STATIC); } /** * @return bool */ public function isStatic() { return (bool) ($this->flags & self::FLAG_STATIC); // is FLAG_STATIC in flags } /** * @param string $visibility * @return static */ public function setVisibility($visibility) { switch ($visibility) { case self::VISIBILITY_PUBLIC: $this->removeFlag(self::FLAG_PRIVATE | self::FLAG_PROTECTED); // remove both $this->addFlag(self::FLAG_PUBLIC); break; case self::VISIBILITY_PROTECTED: $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PRIVATE); // remove both $this->addFlag(self::FLAG_PROTECTED); break; case self::VISIBILITY_PRIVATE: $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PROTECTED); // remove both $this->addFlag(self::FLAG_PRIVATE); break; } return $this; } /** * @psalm-return static::VISIBILITY_* */ public function getVisibility() { switch (true) { case $this->flags & self::FLAG_PROTECTED: return self::VISIBILITY_PROTECTED; case $this->flags & self::FLAG_PRIVATE: return self::VISIBILITY_PRIVATE; default: return self::VISIBILITY_PUBLIC; } } /** * @param string $name * @return static */ public function setName($name) { $this->name = (string) $name; return $this; } /** * @return string */ public function getName() { return $this->name; } /** * @param DocBlockGenerator|string $docBlock * @throws Exception\InvalidArgumentException * @return static */ public function setDocBlock($docBlock) { if (is_string($docBlock)) { $docBlock = new DocBlockGenerator($docBlock); } elseif (! $docBlock instanceof DocBlockGenerator) { throw new Exception\InvalidArgumentException(sprintf( '%s is expecting either a string, array or an instance of %s\DocBlockGenerator', __METHOD__, __NAMESPACE__ )); } $this->docBlock = $docBlock; return $this; } public function removeDocBlock(): void { $this->docBlock = null; } /** * @return DocBlockGenerator|null */ public function getDocBlock() { return $this->docBlock; } } src/Generator/BodyGenerator.php000064400000001055147133776660012563 0ustar00content = (string) $content; return $this; } /** * @return string */ public function getContent() { return $this->content; } /** * @return string */ public function generate() { return $this->getContent(); } } src/Generator/ClassGenerator.php000064400000076343147133776660012747 0ustar00 */ protected array $implementedInterfaces = []; /** @var PropertyGenerator[] */ protected array $properties = []; /** @var PropertyGenerator[] */ protected array $constants = []; /** @var MethodGenerator[] */ protected array $methods = []; /** @var TraitUsageGenerator Object to encapsulate trait usage logic */ protected TraitUsageGenerator $traitUsageGenerator; /** * Build a Code Generation Php Object from a Class Reflection * * @return static */ public static function fromReflection(ClassReflection $classReflection) { $cg = new static($classReflection->getName()); $cg->setSourceContent($cg->getSourceContent()); $cg->setSourceDirty(false); $docBlock = $classReflection->getDocBlock(); if ($docBlock) { $cg->setDocBlock(DocBlockGenerator::fromReflection($docBlock)); } $cg->setAbstract($classReflection->isAbstract()); $cg->setFinal($classReflection->isFinal()); if (method_exists($classReflection, 'isReadonly')) { $cg->setReadonly((bool) $classReflection->isReadonly()); } // set the namespace if ($classReflection->inNamespace()) { $cg->setNamespaceName($classReflection->getNamespaceName()); } $parentClass = $classReflection->getParentClass(); $interfaces = $classReflection->getInterfaces(); if ($parentClass) { $cg->setExtendedClass($parentClass->getName()); $interfaces = array_diff($interfaces, $parentClass->getInterfaces()); } $interfaceNames = []; foreach ($interfaces as $interface) { $interfaceNames[] = $interface->getName(); } $cg->setImplementedInterfaces($interfaceNames); $properties = []; foreach ($classReflection->getProperties() as $reflectionProperty) { if ($reflectionProperty->getDeclaringClass()->getName() == $classReflection->getName()) { $properties[] = PropertyGenerator::fromReflection($reflectionProperty); } } $cg->addProperties($properties); $constants = []; foreach ($classReflection->getReflectionConstants() as $constReflection) { $constants[] = new PropertyGenerator( $constReflection->getName(), new PropertyValueGenerator($constReflection->getValue()), $constReflection->isFinal() ? [PropertyGenerator::FLAG_CONSTANT, PropertyGenerator::FLAG_FINAL] : [PropertyGenerator::FLAG_CONSTANT] ); } $cg->addConstants($constants); $methods = []; foreach ($classReflection->getMethods() as $reflectionMethod) { $className = $cg->getName(); $namespaceName = $cg->getNamespaceName(); if ($namespaceName !== null) { $className = $namespaceName . '\\' . $className; } if ($reflectionMethod->getDeclaringClass()->getName() == $className) { $method = MethodGenerator::fromReflection($reflectionMethod); if (self::CONSTRUCTOR_NAME === strtolower($method->getName())) { foreach ($method->getParameters() as $parameter) { if ($parameter instanceof PromotedParameterGenerator) { $cg->removeProperty($parameter->getName()); } } } $methods[] = $method; } } $cg->addMethods($methods); return $cg; } /** * Generate from array * * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @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 ClassGenerator::FLAG_ABSTRACT ClassGenerator::FLAG_FINAL * @configkey extendedclass string Class which this class is extending * @configkey implementedinterfaces * @configkey properties * @configkey methods * @throws Exception\InvalidArgumentException * @return static */ public static function fromArray(array $array) { if (! isset($array['name'])) { throw new Exception\InvalidArgumentException( 'Class generator requires that a name is provided for this object' ); } $cg = new static($array['name']); foreach ($array as $name => $value) { // normalize key switch (strtolower(str_replace(['.', '-', '_'], '', $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 'extendedclass': $cg->setExtendedClass($value); break; case 'implementedinterfaces': $cg->setImplementedInterfaces($value); break; case 'properties': $cg->addProperties($value); break; case 'methods': $cg->addMethods($value); break; } } return $cg; } /** * @param string $name * @param string $namespaceName * @param int|int[]|null $flags * @param class-string|null $extends * @param string[] $interfaces * @psalm-param array $interfaces * @param PropertyGenerator[]|string[]|array[] $properties * @param MethodGenerator[]|string[]|array[] $methods * @param DocBlockGenerator $docBlock */ public function __construct( $name = null, $namespaceName = null, $flags = null, $extends = null, array $interfaces = [], array $properties = [], array $methods = [], $docBlock = null ) { $this->traitUsageGenerator = new TraitUsageGenerator($this); if ($name !== null) { $this->setName($name); } if ($namespaceName !== null) { $this->setNamespaceName($namespaceName); } if ($flags !== null) { $this->setFlags($flags); } if ($properties !== []) { $this->addProperties($properties); } if ($extends !== null) { $this->setExtendedClass($extends); } if ($interfaces !== []) { $this->setImplementedInterfaces($interfaces); } if ($methods !== []) { $this->addMethods($methods); } if ($docBlock !== null) { $this->setDocBlock($docBlock); } } /** * @param string $name * @return static */ public function setName($name) { if (str_contains($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 static */ public function setNamespaceName($namespaceName) { $this->namespaceName = $namespaceName; return $this; } /** * @return ?string */ public function getNamespaceName() { return $this->namespaceName; } /** * @return static */ public function setContainingFileGenerator(FileGenerator $fileGenerator) { $this->containingFileGenerator = $fileGenerator; return $this; } /** * @return ?FileGenerator */ public function getContainingFileGenerator() { return $this->containingFileGenerator; } /** * @return static */ public function setDocBlock(DocBlockGenerator $docBlock) { $this->docBlock = $docBlock; return $this; } /** * @return ?DocBlockGenerator */ public function getDocBlock() { return $this->docBlock; } /** * @param int[]|int $flags * @return static */ 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 int $flag * @return static */ public function addFlag($flag) { $this->setFlags($this->flags | $flag); return $this; } /** * @param int $flag * @return static */ public function removeFlag($flag) { $this->setFlags($this->flags & ~$flag); return $this; } /** * @param bool $isAbstract * @return static */ public function setAbstract($isAbstract) { return $isAbstract ? $this->addFlag(self::FLAG_ABSTRACT) : $this->removeFlag(self::FLAG_ABSTRACT); } /** * @return bool */ public function isAbstract() { return (bool) ($this->flags & self::FLAG_ABSTRACT); } /** * @param bool $isFinal * @return static */ public function setFinal($isFinal) { return $isFinal ? $this->addFlag(self::FLAG_FINAL) : $this->removeFlag(self::FLAG_FINAL); } /** * @return bool */ public function isFinal() { return (bool) ($this->flags & self::FLAG_FINAL); } public function setReadonly(bool $isReadonly): self { return $isReadonly ? $this->addFlag(self::FLAG_READONLY) : $this->removeFlag(self::FLAG_READONLY); } public function isReadonly(): bool { return (bool) ($this->flags & self::FLAG_READONLY); } /** * @param ?string $extendedClass * @psalm-param ?class-string $extendedClass * @return static */ public function setExtendedClass($extendedClass) { $this->extendedClass = $extendedClass; return $this; } /** * @return ?string * @psalm-return ?class-string */ public function getExtendedClass() { return $this->extendedClass; } /** * @return bool */ public function hasExtentedClass() { return ! empty($this->extendedClass); } /** * @return static */ public function removeExtentedClass() { $this->setExtendedClass(null); return $this; } /** * @param string[] $implementedInterfaces * @psalm-param array $implementedInterfaces * @return static */ public function setImplementedInterfaces(array $implementedInterfaces) { $this->implementedInterfaces = $implementedInterfaces; return $this; } /** * @return string[] * @psalm-return array */ public function getImplementedInterfaces() { return $this->implementedInterfaces; } /** * @param string $implementedInterface * @psalm-param class-string $implementedInterface * @return bool */ public function hasImplementedInterface($implementedInterface) { $interfaceType = TypeGenerator::fromTypeString($implementedInterface); return (bool) array_filter( array_map([TypeGenerator::class, 'fromTypeString'], $this->implementedInterfaces), static fn (TypeGenerator $interface): bool => $interfaceType->equals($interface) ); } /** * @param string $implementedInterface * @psalm-param class-string $implementedInterface * @return static */ public function removeImplementedInterface($implementedInterface) { $interfaceType = TypeGenerator::fromTypeString($implementedInterface); $this->implementedInterfaces = array_filter( $this->implementedInterfaces, static fn (string $interface): bool => ! TypeGenerator::fromTypeString($interface)->equals($interfaceType) ); return $this; } /** * @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 static */ public function removeConstant($constantName) { unset($this->constants[$constantName]); return $this; } /** * @param string $constantName * @return bool */ public function hasConstant($constantName) { return isset($this->constants[$constantName]); } /** * Add constant from PropertyGenerator * * @throws Exception\InvalidArgumentException * @return static */ public function addConstantFromGenerator(PropertyGenerator $constant) { $constantName = $constant->getName(); if (isset($this->constants[$constantName])) { throw new Exception\InvalidArgumentException(sprintf( 'A constant by name %s already exists in this class.', $constantName )); } if (! $constant->isConst()) { throw new Exception\InvalidArgumentException(sprintf( 'The value %s is not defined as a constant.', $constantName )); } $this->constants[$constantName] = $constant; return $this; } /** * Add Constant * * @param non-empty-string $name * @param mixed $value Scalar * @return static * @throws Exception\InvalidArgumentException */ public function addConstant($name, $value, bool $isFinal = false) { if (empty($name) || ! is_string($name)) { throw new Exception\InvalidArgumentException(sprintf( '%s expects string for name', __METHOD__ )); } $this->validateConstantValue($value); return $this->addConstantFromGenerator( new PropertyGenerator( $name, new PropertyValueGenerator($value), $isFinal ? PropertyGenerator::FLAG_CONSTANT | PropertyGenerator::FLAG_FINAL : PropertyGenerator::FLAG_CONSTANT ) ); } /** * @param PropertyGenerator[]|array[] $constants * @return static */ public function addConstants(array $constants) { foreach ($constants as $constant) { if ($constant instanceof PropertyGenerator) { $this->addPropertyFromGenerator($constant); } else { if (is_array($constant)) { $this->addConstant(...array_values($constant)); } } } return $this; } /** * @param PropertyGenerator[]|string[]|array[] $properties * @return static */ public function addProperties(array $properties) { foreach ($properties as $property) { if ($property instanceof PropertyGenerator) { $this->addPropertyFromGenerator($property); } elseif (is_string($property)) { $this->addProperty($property); } else { $this->addProperty(...array_values($property)); } } return $this; } /** * Add Property from scalars * * @param string $name * @param string|array $defaultValue * @param int $flags * @throws Exception\InvalidArgumentException * @return static */ public function addProperty($name, $defaultValue = null, $flags = PropertyGenerator::FLAG_PUBLIC) { if (! is_string($name)) { throw new Exception\InvalidArgumentException(sprintf( '%s::%s expects string for name', static::class, __FUNCTION__ )); } // backwards compatibility // @todo remove this on next major version if ($flags === PropertyGenerator::FLAG_CONSTANT) { return $this->addConstant($name, $defaultValue); } return $this->addPropertyFromGenerator(new PropertyGenerator($name, $defaultValue, $flags)); } /** * Add property from PropertyGenerator * * @throws Exception\InvalidArgumentException * @return static */ public function addPropertyFromGenerator(PropertyGenerator $property) { $propertyName = $property->getName(); if (isset($this->properties[$propertyName])) { throw new Exception\InvalidArgumentException(sprintf( 'A property by name %s already exists in this class.', $propertyName )); } // backwards compatibility // @todo remove this on next major version if ($property->isConst()) { return $this->addConstantFromGenerator($property); } $this->properties[$propertyName] = $property; return $this; } /** * @return PropertyGenerator[] */ public function getProperties() { return $this->properties; } /** * @param string $propertyName * @return PropertyGenerator|false */ public function getProperty($propertyName) { foreach ($this->getProperties() as $property) { if ($property->getName() == $propertyName) { return $property; } } return false; } /** @inheritDoc */ public function addUse($use, $useAlias = null) { $this->traitUsageGenerator->addUse($use, $useAlias); return $this; } /** * @param string $use * @return bool */ public function hasUse($use) { return $this->traitUsageGenerator->hasUse($use); } /** * @param string $use * @return static */ public function removeUse($use) { $this->traitUsageGenerator->removeUse($use); return $this; } /** * @param string $use * @return bool */ public function hasUseAlias($use) { return $this->traitUsageGenerator->hasUseAlias($use); } /** * @param string $use * @return static */ public function removeUseAlias($use) { $this->traitUsageGenerator->removeUseAlias($use); return $this; } /** @inheritDoc */ public function getUses() { return $this->traitUsageGenerator->getUses(); } /** * @param string $propertyName * @return static */ public function removeProperty($propertyName) { unset($this->properties[$propertyName]); return $this; } /** * @param string $propertyName * @return bool */ public function hasProperty($propertyName) { return isset($this->properties[$propertyName]); } /** * @param MethodGenerator[]|string[]|array[] $methods * @return static */ public function addMethods(array $methods) { foreach ($methods as $method) { if ($method instanceof MethodGenerator) { $this->addMethodFromGenerator($method); } elseif (is_string($method)) { $this->addMethod($method); } else { $this->addMethod(...array_values($method)); } } return $this; } /** * Add Method from scalars * * @param non-empty-string $name * @param ParameterGenerator[]|array[]|string[] $parameters * @param int $flags * @param string $body * @param string $docBlock * @return static * @throws Exception\InvalidArgumentException */ public function addMethod( $name, array $parameters = [], $flags = MethodGenerator::FLAG_PUBLIC, $body = null, $docBlock = null ) { if (! is_string($name)) { throw new Exception\InvalidArgumentException(sprintf( '%s::%s expects string for name', static::class, __FUNCTION__ )); } return $this->addMethodFromGenerator(new MethodGenerator($name, $parameters, $flags, $body, $docBlock)); } /** * Add Method from MethodGenerator * * @throws Exception\InvalidArgumentException * @return static */ public function addMethodFromGenerator(MethodGenerator $method) { $methodName = $method->getName(); if ($this->hasMethod($methodName)) { throw new Exception\InvalidArgumentException(sprintf( 'A method by name %s already exists in this class.', $methodName )); } if (self::CONSTRUCTOR_NAME !== strtolower($methodName)) { foreach ($method->getParameters() as $parameter) { if ($parameter instanceof PromotedParameterGenerator) { throw new Exception\InvalidArgumentException( 'Promoted parameter can only be added to constructor.' ); } } } $this->methods[strtolower($methodName)] = $method; return $this; } /** * @return MethodGenerator[] */ public function getMethods() { return $this->methods; } /** * @param string $methodName * @return MethodGenerator|false */ public function getMethod($methodName) { return $this->hasMethod($methodName) ? $this->methods[strtolower($methodName)] : false; } /** * @param string $methodName * @return static */ public function removeMethod($methodName) { unset($this->methods[strtolower($methodName)]); return $this; } /** * @param string $methodName * @return bool */ public function hasMethod($methodName) { return isset($this->methods[strtolower($methodName)]); } /** * @inheritDoc */ public function addTrait($trait) { $this->traitUsageGenerator->addTrait($trait); return $this; } /** * @inheritDoc */ public function addTraits(array $traits) { $this->traitUsageGenerator->addTraits($traits); return $this; } /** * @inheritDoc */ public function hasTrait($traitName) { return $this->traitUsageGenerator->hasTrait($traitName); } /** * @inheritDoc */ public function getTraits() { return $this->traitUsageGenerator->getTraits(); } /** * @inheritDoc */ public function removeTrait($traitName) { return $this->traitUsageGenerator->removeTrait($traitName); } /** * @inheritDoc */ public function addTraitAlias($method, $alias, $visibility = null) { $this->traitUsageGenerator->addTraitAlias($method, $alias, $visibility); return $this; } /** * @inheritDoc */ public function getTraitAliases() { return $this->traitUsageGenerator->getTraitAliases(); } /** * @inheritDoc */ public function addTraitOverride($method, $traitsToReplace) { $this->traitUsageGenerator->addTraitOverride($method, $traitsToReplace); return $this; } /** * @inheritDoc */ public function removeTraitOverride($method, $overridesToRemove = null) { $this->traitUsageGenerator->removeTraitOverride($method, $overridesToRemove); return $this; } /** * @inheritDoc */ public function getTraitOverrides() { return $this->traitUsageGenerator->getTraitOverrides(); } /** * @return bool */ public function isSourceDirty() { if (($docBlock = $this->getDocBlock()) && $docBlock->isSourceDirty()) { return true; } foreach ($this->getProperties() as $property) { if ($property->isSourceDirty()) { return true; } } foreach ($this->getMethods() as $method) { if ($method->isSourceDirty()) { return true; } } return parent::isSourceDirty(); } /** * @inheritDoc */ 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) { $output .= 'use ' . $use . ';' . self::LINE_FEED; } $output .= self::LINE_FEED; } if (null !== ($docBlock = $this->getDocBlock())) { $docBlock->setIndentation(''); $output .= $docBlock->generate(); } if ($this->isAbstract()) { $output .= 'abstract '; } elseif ($this->isFinal()) { $output .= 'final '; } if ($this->isReadonly()) { $output .= 'readonly '; } $output .= static::OBJECT_TYPE . ' ' . $this->getName(); if (! empty($this->extendedClass)) { $output .= ' extends ' . $this->generateShortOrCompleteClassname($this->extendedClass); } $implemented = $this->getImplementedInterfaces(); if (! empty($implemented)) { $implemented = array_map($this->generateShortOrCompleteClassname(...), $implemented); $output .= ' ' . static::IMPLEMENTS_KEYWORD . ' ' . implode(', ', $implemented); } $output .= self::LINE_FEED . '{' . self::LINE_FEED; $traitUseOutput = rtrim($this->traitUsageGenerator->generate(), self::LINE_FEED); $constants = []; $properties = []; $methods = []; foreach ($this->getConstants() as $constant) { $constants[] = $constant->generate(); } foreach ($this->getProperties() as $property) { $properties[] = $property->generate(); } foreach ($this->getMethods() as $method) { $methods[] = $method->generate(); } $contents = rtrim( implode( self::LINE_FEED . self::LINE_FEED, array_filter([ $traitUseOutput, implode(self::LINE_FEED . self::LINE_FEED, $constants), implode(self::LINE_FEED . self::LINE_FEED, $properties), implode(self::LINE_FEED, $methods), ]) ), self::LINE_FEED ); return $output . $contents . ($contents === '' ? '' : self::LINE_FEED) . '}' . self::LINE_FEED; } /** * @throws Exception\InvalidArgumentException */ private function validateConstantValue(mixed $value): void { if (null === $value || is_scalar($value)) { return; } if (is_array($value)) { array_walk($value, $this->validateConstantValue(...)); return; } throw new Exception\InvalidArgumentException(sprintf( 'Expected value for constant, value must be a "scalar" or "null", "%s" found', get_debug_type($value) )); } private function generateShortOrCompleteClassname(string $fqnClassName): string { $fqnClassName = ltrim($fqnClassName, '\\'); $parts = explode('\\', $fqnClassName); $className = array_pop($parts); $classNamespace = implode('\\', $parts); $currentNamespace = (string) $this->getNamespaceName(); if ($this->hasUseAlias($fqnClassName)) { return $this->traitUsageGenerator->getUseAlias($fqnClassName); } if ($this->hasUseAlias($classNamespace)) { $namespaceAlias = $this->traitUsageGenerator->getUseAlias($classNamespace); return $namespaceAlias . '\\' . $className; } if ($this->traitUsageGenerator->isUseAlias($fqnClassName)) { return $fqnClassName; } if ($this->traitUsageGenerator->isUseAlias($classNamespace)) { return $fqnClassName; } if ($classNamespace === $currentNamespace || in_array($fqnClassName, $this->getUses())) { return $className; } return '\\' . $fqnClassName; } } src/Generator/DocBlock/Tag.php000064400000002156147133776660012215 0ustar00initializeDefaultTags(); return $tagManager->createTagFromReflection($reflectionTag); } /** * @deprecated Deprecated in 2.3. Use GenericTag::setContent() instead * * @param string $description * @return Tag */ public function setDescription($description) { return $this->setContent($description); } /** * @deprecated Deprecated in 2.3. Use GenericTag::getContent() instead * * @return string|null */ public function getDescription() { return $this->getContent(); } } src/Generator/DocBlock/Tag/AbstractTypeableTag.php000064400000003571147133776660016104 0ustar00setTypes($types); } if (! empty($description)) { $this->setDescription($description); } } /** * @param string $description * @return AbstractTypeableTag */ public function setDescription($description) { $this->description = $description; return $this; } /** * @return string|null */ public function getDescription() { return $this->description; } /** * Array of types or string with types delimited by pipe (|) * e.g. array('int', 'null') or "int|null" * * @param string[]|string $types * @return AbstractTypeableTag */ public function setTypes($types) { if (is_string($types)) { $types = explode('|', $types); } $this->types = $types; return $this; } /** * @return string[] */ public function getTypes() { return $this->types; } /** * @param string $delimiter * @return string */ public function getTypesAsString($delimiter = '|') { return implode($delimiter, $this->types); } } src/Generator/DocBlock/Tag/AuthorTag.php000064400000004153147133776660014112 0ustar00setAuthorName($authorName); } if (! empty($authorEmail)) { $this->setAuthorEmail($authorEmail); } } /** * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead * * @return AuthorTag */ public static function fromReflection(ReflectionTagInterface $reflectionTag) { $tagManager = new TagManager(); $tagManager->initializeDefaultTags(); return $tagManager->createTagFromReflection($reflectionTag); } /** @return 'author' */ public function getName() { return 'author'; } /** * @param string $authorEmail * @return AuthorTag */ public function setAuthorEmail($authorEmail) { $this->authorEmail = $authorEmail; return $this; } /** @return string|null */ public function getAuthorEmail() { return $this->authorEmail; } /** * @param string $authorName * @return AuthorTag */ public function setAuthorName($authorName) { $this->authorName = $authorName; return $this; } /** @return string|null */ public function getAuthorName() { return $this->authorName; } /** @return non-empty-string */ public function generate() { return '@author' . (! empty($this->authorName) ? ' ' . $this->authorName : '') . (! empty($this->authorEmail) ? ' <' . $this->authorEmail . '>' : ''); } } src/Generator/DocBlock/Tag/GenericTag.php000064400000002652147133776660014226 0ustar00setName($name); } if (! empty($content)) { $this->setContent($content); } } /** * @param string $name * @return $this */ public function setName($name) { $this->name = ltrim($name, '@'); return $this; } /** @return string|null */ public function getName() { return $this->name; } /** * @param string $content * @return $this */ public function setContent($content) { $this->content = $content; return $this; } /** @return string|null */ public function getContent() { return $this->content; } /** @return non-empty-string */ public function generate() { return '@' . $this->name . (! empty($this->content) ? ' ' . $this->content : ''); } } src/Generator/DocBlock/Tag/LicenseTag.php000064400000003755147133776660014241 0ustar00setUrl($url); } if (! empty($licenseName)) { $this->setLicenseName($licenseName); } } /** * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead * * @return ReturnTag */ public static function fromReflection(ReflectionTagInterface $reflectionTag) { $tagManager = new TagManager(); $tagManager->initializeDefaultTags(); return $tagManager->createTagFromReflection($reflectionTag); } /** @return 'license' */ public function getName() { return 'license'; } /** * @param string $url * @return LicenseTag */ public function setUrl($url) { $this->url = $url; return $this; } /** @return string|null */ public function getUrl() { return $this->url; } /** * @param string $name * @return LicenseTag */ public function setLicenseName($name) { $this->licenseName = $name; return $this; } /** @return string|null */ public function getLicenseName() { return $this->licenseName; } /** @return non-empty-string */ public function generate() { return '@license' . (! empty($this->url) ? ' ' . $this->url : '') . (! empty($this->licenseName) ? ' ' . $this->licenseName : ''); } } src/Generator/DocBlock/Tag/MethodTag.php000064400000003500147133776660014063 0ustar00setMethodName($methodName); } $this->setIsStatic((bool) $isStatic); parent::__construct($types, $description); } /** * @return string */ public function getName() { return 'method'; } /** * @param bool $isStatic * @return MethodTag */ public function setIsStatic($isStatic) { $this->isStatic = $isStatic; return $this; } /** * @return bool */ public function isStatic() { return $this->isStatic; } /** * @param non-empty-string $methodName * @return MethodTag */ public function setMethodName($methodName) { $this->methodName = rtrim($methodName, ')('); return $this; } /** @return string|null */ public function getMethodName() { return $this->methodName; } /** @return non-empty-string */ public function generate() { return '@method' . ($this->isStatic ? ' static' : '') . (! empty($this->types) ? ' ' . $this->getTypesAsString() : '') . (! empty($this->methodName) ? ' ' . $this->methodName . '()' : '') . (! empty($this->description) ? ' ' . $this->description : ''); } } src/Generator/DocBlock/Tag/ParamTag.php000064400000005332147133776660013710 0ustar00setVariableName($variableName); } parent::__construct($types, $description); } /** * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead * * @return ParamTag */ public static function fromReflection(ReflectionTagInterface $reflectionTag) { $tagManager = new TagManager(); $tagManager->initializeDefaultTags(); return $tagManager->createTagFromReflection($reflectionTag); } /** * @return string */ public function getName() { return 'param'; } /** * @param string $variableName * @return ParamTag */ public function setVariableName($variableName) { $this->variableName = ltrim($variableName, '$'); return $this; } /** * @return string */ public function getVariableName() { return $this->variableName; } /** * @deprecated Deprecated in 2.3. Use setTypes() instead * * @param string $datatype * @return ParamTag */ public function setDatatype($datatype) { return $this->setTypes($datatype); } /** * @deprecated Deprecated in 2.3. Use getTypes() or getTypesAsString() instead * * @return string */ public function getDatatype() { return $this->getTypesAsString(); } /** * @deprecated Deprecated in 2.3. Use setVariableName() instead * * @param string $paramName * @return ParamTag */ public function setParamName($paramName) { return $this->setVariableName($paramName); } /** * @deprecated Deprecated in 2.3. Use getVariableName() instead * * @return string */ public function getParamName() { return $this->getVariableName(); } /** * @return string */ public function generate() { return '@param' . (! empty($this->types) ? ' ' . $this->getTypesAsString() : '') . (! empty($this->variableName) ? ' $' . $this->variableName : '') . (! empty($this->description) ? ' ' . $this->description : ''); } } src/Generator/DocBlock/Tag/PropertyTag.php000064400000002530147133776660014471 0ustar00setPropertyName($propertyName); } parent::__construct($types, $description); } /** * @return string */ public function getName() { return 'property'; } /** * @param string $propertyName * @return self */ public function setPropertyName($propertyName) { $this->propertyName = ltrim($propertyName, '$'); return $this; } /** * @return string|null */ public function getPropertyName() { return $this->propertyName; } /** * @return string */ public function generate() { return '@property' . (! empty($this->types) ? ' ' . $this->getTypesAsString() : '') . (! empty($this->propertyName) ? ' $' . $this->propertyName : '') . (! empty($this->description) ? ' ' . $this->description : ''); } } src/Generator/DocBlock/Tag/ReturnTag.php000064400000002614147133776660014127 0ustar00initializeDefaultTags(); return $tagManager->createTagFromReflection($reflectionTag); } /** * @return string */ public function getName() { return 'return'; } /** * @deprecated Deprecated in 2.3. Use setTypes() instead * * @param string $datatype * @return ReturnTag */ public function setDatatype($datatype) { return $this->setTypes($datatype); } /** * @deprecated Deprecated in 2.3. Use getTypes() or getTypesAsString() instead * * @return string */ public function getDatatype() { return $this->getTypesAsString(); } /** * @return string */ public function generate() { return '@return ' . $this->getTypesAsString() . (! empty($this->description) ? ' ' . $this->description : ''); } } src/Generator/DocBlock/Tag/TagInterface.php000064400000000245147133776660014546 0ustar00types) ? ' ' . $this->getTypesAsString() : '') . (! empty($this->description) ? ' ' . $this->description : ''); } } src/Generator/DocBlock/Tag/VarTag.php000064400000002604147133776660013377 0ustar00variableName = ltrim($variableName, '$'); } parent::__construct($types, $description); } /** @inheritDoc */ public function getName(): string { return 'var'; } /** * @internal this code is only public for compatibility with the * * @see \Laminas\Code\Generator\DocBlock\TagManager, which * uses setters */ public function setVariableName(?string $variableName): void { if (null !== $variableName) { $this->variableName = ltrim($variableName, '$'); } } public function getVariableName(): ?string { return $this->variableName; } /** @inheritDoc */ public function generate(): string { return '@var' . (! empty($this->types) ? ' ' . $this->getTypesAsString() : '') . (null !== $this->variableName ? ' $' . $this->variableName : '') . (! empty($this->description) ? ' ' . $this->description : ''); } } src/Generator/DocBlock/TagManager.php000064400000004670147133776660013513 0ustar00addPrototype(new Tag\ParamTag()); $this->addPrototype(new Tag\ReturnTag()); $this->addPrototype(new Tag\MethodTag()); $this->addPrototype(new Tag\PropertyTag()); $this->addPrototype(new Tag\AuthorTag()); $this->addPrototype(new Tag\LicenseTag()); $this->addPrototype(new Tag\ThrowsTag()); $this->addPrototype(new Tag\VarTag()); $this->setGenericPrototype(new Tag\GenericTag()); } /** * @return TagInterface */ public function createTagFromReflection(ReflectionTagInterface $reflectionTag) { $tagName = $reflectionTag->getName(); /** @var TagInterface $newTag */ $newTag = $this->getClonedPrototype($tagName); // transport any properties via accessors and mutators from reflection to codegen object $reflectionClass = new ReflectionClass($reflectionTag); foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { if (str_starts_with($method->getName(), 'get')) { $propertyName = substr($method->getName(), 3); if (method_exists($newTag, 'set' . $propertyName)) { $newTag->{'set' . $propertyName}($reflectionTag->{'get' . $propertyName}()); } } elseif (str_starts_with($method->getName(), 'is')) { $propertyName = ucfirst($method->getName()); if (method_exists($newTag, 'set' . $propertyName)) { $newTag->{'set' . $propertyName}($reflectionTag->{$method->getName()}()); } } } return $newTag; } } src/Generator/DocBlockGenerator.php000064400000015503147133776660013351 0ustar00setSourceContent($reflectionDocBlock->getContents()); $docBlock->setSourceDirty(false); $docBlock->setShortDescription($reflectionDocBlock->getShortDescription()); $docBlock->setLongDescription($reflectionDocBlock->getLongDescription()); foreach ($reflectionDocBlock->getTags() as $tag) { $docBlock->setTag(self::getTagManager()->createTagFromReflection($tag)); } return $docBlock; } /** * Generate from array * * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @configkey shortdescription string The short description for this doc block * @configkey longdescription string The long description for this doc block * @configkey tags array * @throws Exception\InvalidArgumentException * @return DocBlockGenerator */ public static function fromArray(array $array) { $docBlock = new static(); foreach ($array as $name => $value) { // normalize key switch (strtolower(str_replace(['.', '-', '_'], '', $name))) { case 'shortdescription': $docBlock->setShortDescription($value); break; case 'longdescription': $docBlock->setLongDescription($value); break; case 'tags': $docBlock->setTags($value); break; } } return $docBlock; } /** * @return TagManager */ protected static function getTagManager() { if (! isset(static::$tagManager)) { static::$tagManager = new TagManager(); static::$tagManager->initializeDefaultTags(); } return static::$tagManager; } /** * @param ?string $shortDescription * @param ?string $longDescription * @param array[]|TagInterface[] $tags */ public function __construct($shortDescription = null, $longDescription = null, array $tags = []) { if ($shortDescription) { $this->setShortDescription($shortDescription); } if ($longDescription) { $this->setLongDescription($longDescription); } if ($tags) { $this->setTags($tags); } } /** * @param string $shortDescription * @return DocBlockGenerator */ public function setShortDescription($shortDescription) { $this->shortDescription = $shortDescription; return $this; } /** * @return string */ public function getShortDescription() { return $this->shortDescription; } /** * @param string $longDescription * @return DocBlockGenerator */ public function setLongDescription($longDescription) { $this->longDescription = $longDescription; return $this; } /** * @return string */ public function getLongDescription() { return $this->longDescription; } /** * @param array[]|TagInterface[] $tags * @return DocBlockGenerator */ public function setTags(array $tags) { foreach ($tags as $tag) { $this->setTag($tag); } return $this; } /** * @param array|TagInterface $tag * @throws Exception\InvalidArgumentException * @return DocBlockGenerator */ public function setTag($tag) { if (is_array($tag)) { // use deprecated Tag class for backward compatibility to old array-keys $genericTag = new Tag(); $genericTag->setOptions($tag); $tag = $genericTag; } elseif (! $tag instanceof TagInterface) { throw new Exception\InvalidArgumentException(sprintf( '%s expects either an array of method options or an instance of %s\DocBlock\Tag\TagInterface', __METHOD__, __NAMESPACE__ )); } $this->tags[] = $tag; return $this; } /** * @return TagInterface[] */ public function getTags() { return $this->tags; } /** * @param bool $value * @return DocBlockGenerator */ public function setWordWrap($value) { $this->wordwrap = (bool) $value; return $this; } /** * @return bool */ public function getWordWrap() { return $this->wordwrap; } /** * @return string */ public function generate() { if (! $this->isSourceDirty()) { return $this->docCommentize(trim($this->getSourceContent() ?? '')); } $output = ''; if ($sd = $this->getShortDescription()) { $output .= $sd . self::LINE_FEED . self::LINE_FEED; } if ($ld = $this->getLongDescription()) { $output .= $ld . self::LINE_FEED . self::LINE_FEED; } /** @var GeneratorInterface $tag */ foreach ($this->getTags() as $tag) { $output .= $tag->generate() . self::LINE_FEED; } return $this->docCommentize(trim($output)); } /** * @param string $content * @return string */ protected function docCommentize($content) { $indent = $this->getIndentation(); $output = $indent . '/**' . self::LINE_FEED; $content = $this->getWordWrap() == true ? wordwrap($content, 80, self::LINE_FEED) : $content; $lines = explode(self::LINE_FEED, $content); foreach ($lines as $line) { $output .= $indent . ' *'; if ($line) { $output .= ' ' . $line; } $output .= self::LINE_FEED; } $output .= $indent . ' */' . self::LINE_FEED; return $output; } } src/Generator/EnumGenerator/Cases/BackedCases.php000064400000002324147133776660015760 0ustar00 $cases */ private function __construct(public readonly string $type, public readonly array $cases) { } /** * @param array|array $backedCases * @param 'int'|'string' $type */ public static function fromCasesWithType(array $backedCases, string $type): self { if (! ($type === 'int' || $type === 'string')) { throw new InvalidArgumentException(sprintf( '"%s" is not a valid type for Enums, only "int" and "string" types are allowed.', $type )); } $cases = []; foreach ($backedCases as $case => $value) { if ($type === 'string') { $value = sprintf("'%s'", $value); } $cases[] = $case . ' = ' . $value; } return new self($type, $cases); } } src/Generator/EnumGenerator/Cases/CaseFactory.php000064400000004370147133776660016036 0ustar00, * }|array{ * name: non-empty-string, * backedCases: array{ * type: 'int', * cases: array, * }|array{ * type: 'string', * cases: array, * }, * } $options * @return BackedCases|PureCases */ public static function fromOptions(array $options) { if (array_key_exists('pureCases', $options) && ! array_key_exists('backedCases', $options)) { return PureCases::fromCases($options['pureCases']); } assert(! array_key_exists('pureCases', $options) && array_key_exists('backedCases', $options)); return BackedCases::fromCasesWithType($options['backedCases']['cases'], $options['backedCases']['type']); } /** * @return BackedCases|PureCases */ public static function fromReflectionCases(ReflectionEnum $enum) { $backingType = $enum->getBackingType(); if ($backingType === null) { return PureCases::fromCases(array_map( /** @return non-empty-string */ static fn(ReflectionEnumUnitCase $singleCase): string => $singleCase->getName(), $enum->getCases() )); } assert($backingType instanceof ReflectionNamedType); $cases = $enum->getCases(); return BackedCases::fromCasesWithType( array_combine( array_map( /** @return non-empty-string */ static fn(ReflectionEnumBackedCase $case): string => $case->getName(), $cases ), array_map(static fn(ReflectionEnumBackedCase $case): string|int => $case->getBackingValue(), $cases), ), $backingType->getName() ); } } src/Generator/EnumGenerator/Cases/PureCases.php000064400000000656147133776660015530 0ustar00 $cases */ private function __construct(public readonly array $cases) { } /** * @param list $pureCases */ public static function fromCases(array $pureCases): self { return new self($pureCases); } } src/Generator/EnumGenerator/EnumGenerator.php000064400000004753147133776660015355 0ustar00name = $name; $this->cases = $cases; } public function generate(): string { $output = ''; if (null !== $this->name->getNamespace()) { $output .= 'namespace ' . $this->name->getNamespace() . ';' . self::LINE_FEED . self::LINE_FEED; } return $output . 'enum ' . $this->name->getName() . $this->retrieveType() . ' {' . self::LINE_FEED . $this->retrieveCases() . '}' . self::LINE_FEED; } private function retrieveType(): string { if ($this->cases instanceof BackedCases) { return ': ' . $this->cases->type; } return ''; } private function retrieveCases(): string { return implode( '', array_map( fn (string $case): string => self::INDENTATION . 'case ' . $case . ';' . self::LINE_FEED, $this->cases->cases ) ); } /** * @psalm-param array{ * name: non-empty-string, * pureCases: list, * }|array{ * name: non-empty-string, * backedCases: array{ * type: 'int'|'string', * cases: array, * }, * } $options */ public static function withConfig(array $options): self { return new self( Name::fromFullyQualifiedClassName($options['name']), CaseFactory::fromOptions($options), ); } public static function fromReflection(ReflectionEnum $enum): self { return new self( Name::fromFullyQualifiedClassName($enum->getName()), CaseFactory::fromReflectionCases($enum), ); } } src/Generator/EnumGenerator/Name.php000064400000001617147133776660013456 0ustar00name = $name; $this->namespace = $namespace; } public function getName(): string { return $this->name; } public function getNamespace(): ?string { return $this->namespace; } public static function fromFullyQualifiedClassName(string $name): self { $namespace = null; $nsPosition = strrpos($name, '\\'); if (false !== $nsPosition) { $namespace = substr($name, 0, $nsPosition); $name = substr($name, $nsPosition + 1); } return new self($name, $namespace); } } src/Generator/Exception/ClassNotFoundException.php000064400000000220147133776660016347 0ustar00 */ class FileGenerator extends AbstractGenerator { protected string $filename = ''; protected ?DocBlockGenerator $docBlock = null; /** @var string[] */ protected array $requiredFiles = []; protected string $namespace = ''; /** @psalm-var list */ protected array $uses = []; /** * @var ClassGenerator[] * @psalm-var array */ protected array $classes = []; protected string $body = ''; /** * @var DeclareStatement[] * @psalm-var array */ protected array $declares = []; /** * Passes $options to {@link setOptions()}. * * @param array|Traversable|null $options */ public function __construct($options = null) { if (null !== $options) { $this->setOptions($options); } } /** * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @return FileGenerator */ public static function fromArray(array $values) { $fileGenerator = new static(); foreach ($values as $name => $value) { switch (strtolower(str_replace(['.', '-', '_'], '', $name))) { case 'filename': $fileGenerator->setFilename($value); break; case 'class': $fileGenerator->setClass( $value instanceof ClassGenerator ? $value : ClassGenerator::fromArray($value) ); break; case 'requiredfiles': $fileGenerator->setRequiredFiles($value); break; case 'declares': $fileGenerator->setDeclares( array_map(static fn($directive, $value) => DeclareStatement::fromArray([$directive => $value]), array_keys($value), $value) ); break; default: if (property_exists($fileGenerator, $name)) { $fileGenerator->{$name} = $value; } elseif (method_exists($fileGenerator, 'set' . $name)) { $fileGenerator->{'set' . $name}($value); } } } return $fileGenerator; } /** * @param DocBlockGenerator|array|string $docBlock * @throws Exception\InvalidArgumentException * @return FileGenerator */ public function setDocBlock($docBlock) { if (is_string($docBlock)) { $docBlock = ['shortDescription' => $docBlock]; } if (is_array($docBlock)) { $docBlock = new DocBlockGenerator($docBlock); } elseif (! $docBlock instanceof DocBlockGenerator) { throw new Exception\InvalidArgumentException(sprintf( '%s is expecting either a string, array or an instance of %s\DocBlockGenerator', __METHOD__, __NAMESPACE__ )); } $this->docBlock = $docBlock; return $this; } /** * @return ?DocBlockGenerator */ public function getDocBlock() { return $this->docBlock; } /** * @param string[] $requiredFiles * @return FileGenerator */ public function setRequiredFiles(array $requiredFiles) { $this->requiredFiles = $requiredFiles; return $this; } /** * @return string[] */ public function getRequiredFiles() { return $this->requiredFiles; } /** * @return string */ public function getNamespace() { return $this->namespace; } /** * @param string $namespace * @return FileGenerator */ public function setNamespace($namespace) { $this->namespace = (string) $namespace; return $this; } /** * Returns an array with the first element the use statement, second is the as part. * If $withResolvedAs is set to true, there will be a third element that is the * "resolved" as statement, as the second part is not required in use statements * * @param bool $withResolvedAs * @return array * @psalm-return array */ public function getUses($withResolvedAs = false) { $uses = $this->uses; if ($withResolvedAs) { for ($useIndex = 0, $count = count($uses); $useIndex < $count; $useIndex++) { if ($uses[$useIndex][1] == '') { if (($lastSeparator = strrpos($uses[$useIndex][0], '\\')) !== false) { $uses[$useIndex][2] = substr($uses[$useIndex][0], $lastSeparator + 1); } else { $uses[$useIndex][2] = $uses[$useIndex][0]; } } else { $uses[$useIndex][2] = $uses[$useIndex][1]; } } } return $uses; } /** * @param InputUses $uses * @return FileGenerator */ public function setUses(array $uses) { foreach ($uses as $use) { $use = (array) $use; if (array_key_exists('use', $use) && array_key_exists('as', $use)) { $this->setUse($use['use'], $use['as']); } elseif (count($use) === 2) { [$import, $alias] = $use; $this->setUse($import, $alias); } else { $this->setUse(current($use)); } } return $this; } /** * @param non-empty-string $use * @param null|non-empty-string $as * @return FileGenerator */ public function setUse($use, $as = null) { if (! in_array([$use, $as], $this->uses)) { $this->uses[] = [$use, $as]; } return $this; } /** * @param array[]|string[]|ClassGenerator[] $classes * @return FileGenerator */ public function setClasses(array $classes) { foreach ($classes as $class) { $this->setClass($class); } return $this; } /** * @param string|null $name * @return ClassGenerator * @throws ClassNotFoundException */ public function getClass($name = null) { if ($name === null) { reset($this->classes); $class = current($this->classes); if (false === $class) { throw new ClassNotFoundException('No class is set'); } return $class; } if (false === array_key_exists($name, $this->classes)) { throw new ClassNotFoundException(sprintf('Class %s is not set', $name)); } return $this->classes[$name]; } /** * @param array|string|ClassGenerator $class * @throws Exception\InvalidArgumentException * @return FileGenerator */ public function setClass($class) { if (is_array($class)) { $class = ClassGenerator::fromArray($class); } elseif (is_string($class)) { $class = new ClassGenerator($class); } elseif (! $class instanceof ClassGenerator) { throw new Exception\InvalidArgumentException(sprintf( '%s is expecting either a string, array or an instance of %s\ClassGenerator', __METHOD__, __NAMESPACE__ )); } // @todo check for dup here $className = $class->getName(); $this->classes[$className] = $class; return $this; } /** * @param string $filename * @return FileGenerator */ public function setFilename($filename) { $this->filename = (string) $filename; return $this; } /** * @return string */ public function getFilename() { return $this->filename; } /** * @return ClassGenerator[] */ public function getClasses() { return $this->classes; } /** * @param string $body * @return FileGenerator */ public function setBody($body) { $this->body = (string) $body; return $this; } /** * @return string */ public function getBody() { return $this->body; } /** * @param DeclareStatement[] $declares * @return static */ public function setDeclares(array $declares) { foreach ($declares as $declare) { if (! $declare instanceof DeclareStatement) { throw new InvalidArgumentException(sprintf( '%s is expecting an array of %s objects', __METHOD__, DeclareStatement::class )); } if (! array_key_exists($declare->getDirective(), $this->declares)) { $this->declares[$declare->getDirective()] = $declare; } } return $this; } /** * @return bool */ public function isSourceDirty() { $docBlock = $this->getDocBlock(); if ($docBlock && $docBlock->isSourceDirty()) { return true; } foreach ($this->classes as $class) { if ($class->isSourceDirty()) { return true; } } return parent::isSourceDirty(); } /** * @return string */ public function generate() { if ($this->isSourceDirty() === false) { return $this->sourceContent ?? ''; } $output = ''; // @note body gets populated when FileGenerator created // from a file. @see fromReflection and may also be set // via FileGenerator::setBody $body = $this->getBody(); // start with the body (if there), or open tag if (preg_match('#(?:\s*)<\?php#', $body) == false) { $output = 'getDocBlock())) { $docBlock->setIndentation(''); if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $output)) { // @codingStandardsIgnoreStart $output = preg_replace('#/\* Laminas_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $docBlock->generate(), $output, 1); // @codingStandardsIgnoreEnd } else { $output .= $docBlock->generate() . self::LINE_FEED; } } // newline $output .= self::LINE_FEED; // declares, if any if ($this->declares) { $declareStatements = ''; foreach ($this->declares as $declare) { $declareStatements .= $declare->getStatement() . self::LINE_FEED; } if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-DeclaresMarker \*/#m', $output)) { $output = preg_replace( '#/\* Laminas_Code_Generator_FileGenerator-DeclaresMarker \*/#m', $declareStatements, $output, 1 ); } else { $output .= $declareStatements; } $output .= self::LINE_FEED; } // namespace, if any $namespace = $this->getNamespace(); if ($namespace) { $namespace = sprintf('namespace %s;%s', $namespace, str_repeat(self::LINE_FEED, 2)); if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-NamespaceMarker \*/#m', $output)) { $output = preg_replace( '#/\* Laminas_Code_Generator_FileGenerator-NamespaceMarker \*/#m', $namespace, $output, 1 ); } else { $output .= $namespace; } } // process required files // @todo marker replacement for required files $requiredFiles = $this->getRequiredFiles(); if (! empty($requiredFiles)) { foreach ($requiredFiles as $requiredFile) { $output .= 'require_once \'' . $requiredFile . '\';' . self::LINE_FEED; } $output .= self::LINE_FEED; } $classes = $this->getClasses(); $classUses = []; //build uses array foreach ($classes as $class) { //check for duplicate use statements $classUses = array_merge($classUses, $class->getUses()); } // process import statements $uses = $this->getUses(); if (! empty($uses)) { $useOutput = ''; foreach ($uses as $use) { [$import, $alias] = $use; if (null === $alias) { $tempOutput = sprintf('%s', $import); } else { $tempOutput = sprintf('%s as %s', $import, $alias); } //don't duplicate use statements if (! in_array($tempOutput, $classUses)) { $useOutput .= 'use ' . $tempOutput . ';'; $useOutput .= self::LINE_FEED; } } $useOutput .= self::LINE_FEED; if (preg_match('#/\* Laminas_Code_Generator_FileGenerator-UseMarker \*/#m', $output)) { $output = preg_replace( '#/\* Laminas_Code_Generator_FileGenerator-UseMarker \*/#m', $useOutput, $output, 1 ); } else { $output .= $useOutput; } } // process classes if (! empty($classes)) { foreach ($classes as $class) { // @codingStandardsIgnoreStart $regex = str_replace('&', $class->getName(), '/\* Laminas_Code_Generator_Php_File-ClassMarker: \{[A-Za-z0-9\\\]+?&\} \*/'); // @codingStandardsIgnoreEnd if (preg_match('#' . $regex . '#m', $output)) { $output = preg_replace('#' . $regex . '#', $class->generate(), $output, 1); } else { if ($namespace) { $class->setNamespaceName(null); } $output .= $class->generate() . self::LINE_FEED; } } } if (! empty($body)) { // add an extra space between classes and if (! empty($classes)) { $output .= self::LINE_FEED; } $output .= $body; } return $output; } /** * @return FileGenerator * @throws Exception\RuntimeException */ public function write() { if ($this->filename == '' || ! is_writable(dirname($this->filename))) { throw new Exception\RuntimeException('This code generator object is not writable.'); } file_put_contents($this->filename, $this->generate()); return $this; } } src/Generator/GeneratorInterface.php000064400000000205147133776660013562 0ustar00isInterface()) { throw new Exception\InvalidArgumentException(sprintf( 'Class %s is not a interface', $classReflection->getName() )); } // class generator $cg = new static($classReflection->getName()); $methods = []; $cg->setSourceContent($cg->getSourceContent()); $cg->setSourceDirty(false); $docBlock = $classReflection->getDocBlock(); if ($docBlock) { $cg->setDocBlock(DocBlockGenerator::fromReflection($docBlock)); } // set the namespace if ($classReflection->inNamespace()) { $cg->setNamespaceName($classReflection->getNamespaceName()); } foreach ($classReflection->getMethods() as $reflectionMethod) { $className = $cg->getName(); $namespaceName = $cg->getNamespaceName(); if ($namespaceName !== null) { $className = $namespaceName . '\\' . $className; } if ($reflectionMethod->getDeclaringClass()->getName() == $className) { $methods[] = MethodGenerator::fromReflection($reflectionMethod); } } foreach ($classReflection->getConstants() as $name => $value) { $cg->addConstant($name, $value); } $cg->addMethods($methods); return $cg; } /** * Generate from array * * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @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 constants * @configkey methods * @throws Exception\InvalidArgumentException * @return static */ public static function fromArray(array $array) { if (! isset($array['name'])) { throw new Exception\InvalidArgumentException( 'Class generator requires that a name is provided for this object' ); } $cg = new static($array['name']); foreach ($array as $name => $value) { // normalize key switch (strtolower(str_replace(['.', '-', '_'], '', $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 'methods': $cg->addMethods($value); break; case 'constants': $cg->addConstants($value); break; } } return $cg; } /** @inheritDoc */ public function addPropertyFromGenerator(PropertyGenerator $property) { return $this; } /** @inheritDoc */ public function addMethodFromGenerator(MethodGenerator $method) { $method->setInterface(true); return parent::addMethodFromGenerator($method); } /** @inheritDoc */ public function setExtendedClass($extendedClass) { return $this; } /** @inheritDoc */ public function setAbstract($isAbstract) { return $this; } } src/Generator/MethodGenerator.php000064400000026556147133776660013123 0ustar00setSourceContent($reflectionMethod->getContents(false)); $method->setSourceDirty(false); if ($reflectionMethod->getDocComment() != '') { $method->setDocBlock(DocBlockGenerator::fromReflection($reflectionMethod->getDocBlock())); } $method->setBody(static::clearBodyIndention($reflectionMethod->getBody())); return $method; } /** * Returns a MethodGenerator based on a MethodReflection with only the signature copied. * * This is similar to fromReflection() but without the method body and phpdoc as this is quite heavy to copy. * It's for example useful when creating proxies where you normally change the method body anyway. */ public static function copyMethodSignature(MethodReflection $reflectionMethod): MethodGenerator { $method = new static(); $declaringClass = $reflectionMethod->getDeclaringClass(); $method->returnType = TypeGenerator::fromReflectionType($reflectionMethod->getReturnType(), $declaringClass); $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->setInterface($declaringClass->isInterface()); $method->setStatic($reflectionMethod->isStatic()); $method->setReturnsReference($reflectionMethod->returnsReference()); $method->setName($reflectionMethod->getName()); foreach ($reflectionMethod->getParameters() as $reflectionParameter) { $method->setParameter( $reflectionParameter->isPromoted() ? PromotedParameterGenerator::fromReflection($reflectionParameter) : ParameterGenerator::fromReflection($reflectionParameter) ); } 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("\n", $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("\n", $lines); return $body; } /** * Generate from array * * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @configkey name string [required] Class Name * @configkey docblock string The DocBlock information * @configkey flags int Flags, one of self::FLAG_ABSTRACT, self::FLAG_FINAL * @configkey parameters string Class which this class is extending * @configkey body string * @configkey returntype string * @configkey returnsreference bool * @configkey abstract bool * @configkey final bool * @configkey static bool * @configkey visibility string * @throws Exception\InvalidArgumentException * @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(['.', '-', '_'], '', $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 'interface': $method->setInterface($value); break; case 'static': $method->setStatic($value); break; case 'visibility': $method->setVisibility($value); break; case 'returntype': $method->setReturnType($value); break; case 'returnsreference': $method->setReturnsReference((bool) $value); } } return $method; } /** * @param ?string $name * @param ParameterGenerator[]|array[]|string[] $parameters * @param int|int[] $flags * @param ?string $body * @param DocBlockGenerator|string|null $docBlock */ public function __construct( $name = null, array $parameters = [], $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 ParameterGenerator[]|array[]|string[] $parameters * @return MethodGenerator */ public function setParameters(array $parameters) { foreach ($parameters as $parameter) { $this->setParameter($parameter); } $this->sortParameters(); 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; $this->sortParameters(); 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; } /** * @param string|null $returnType * @return MethodGenerator */ public function setReturnType($returnType = null) { $this->returnType = null === $returnType ? null : TypeGenerator::fromTypeString($returnType); return $this; } /** * @return TypeGenerator|null */ public function getReturnType() { return $this->returnType; } /** * @param bool $returnsReference * @return MethodGenerator */ public function setReturnsReference($returnsReference) { $this->returnsReference = (bool) $returnsReference; return $this; } public function returnsReference(): bool { return $this->returnsReference; } /** * Sort parameters by their position */ private function sortParameters(): void { uasort( $this->parameters, static fn(ParameterGenerator $item1, ParameterGenerator $item2) => $item1->getPosition() <=> $item2->getPosition() ); } /** * @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->returnsReference ? '& ' : '') . $this->getName() . '('; $output .= implode(', ', array_map( static fn (ParameterGenerator $parameter): string => $parameter->generate(), $this->getParameters() )); $output .= ')'; if ($this->returnType) { $output .= ' : ' . $this->returnType->generate(); } if ($this->isAbstract()) { return $output . ';'; } if ($this->isInterface()) { 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; } /** @return string */ public function __toString() { return $this->generate(); } } src/Generator/ParameterGenerator.php000064400000020061147133776660013604 0ustar00setName($reflectionParameter->getName()); $param->type = TypeGenerator::fromReflectionType( $reflectionParameter->getType(), $reflectionParameter->getDeclaringClass() ); $param->setPosition($reflectionParameter->getPosition()); $variadic = $reflectionParameter->isVariadic(); $param->setVariadic($variadic); if (! $variadic && ($reflectionParameter->isOptional() || $reflectionParameter->isDefaultValueAvailable())) { try { $param->setDefaultValue($reflectionParameter->getDefaultValue()); } catch (ReflectionException $e) { $param->setDefaultValue(null); } } $param->setPassedByReference($reflectionParameter->isPassedByReference()); return $param; } /** * Generate from array * * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @configkey name string [required] Class Name * @configkey type string * @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator * @configkey passedbyreference bool * @configkey position int * @configkey sourcedirty bool * @configkey indentation string * @configkey sourcecontent string * @configkey omitdefaultvalue bool * @throws Exception\InvalidArgumentException * @return ParameterGenerator */ public static function fromArray(array $array) { if (! isset($array['name'])) { throw new Exception\InvalidArgumentException( 'Parameter generator requires that a name is provided for this object' ); } $param = new static($array['name']); foreach ($array as $name => $value) { // normalize key switch (strtolower(str_replace(['.', '-', '_'], '', $name))) { case 'type': $param->setType($value); break; case 'defaultvalue': $param->setDefaultValue($value); break; case 'passedbyreference': $param->setPassedByReference($value); break; case 'position': $param->setPosition($value); break; case 'sourcedirty': $param->setSourceDirty($value); break; case 'indentation': $param->setIndentation($value); break; case 'sourcecontent': $param->setSourceContent($value); break; case 'omitdefaultvalue': $param->omitDefaultValue($value); break; } } return $param; } /** * @param ?string $name * @param ?string $type * @param mixed $defaultValue * @param ?int $position * @param bool $passByReference */ public function __construct( $name = null, $type = null, $defaultValue = null, $position = null, $passByReference = false ) { if (null !== $name) { $this->setName($name); } if (null !== $type) { $this->setType($type); } if (null !== $defaultValue) { $this->setDefaultValue($defaultValue); } if (null !== $position) { $this->setPosition($position); } if (false !== $passByReference) { $this->setPassedByReference(true); } } /** * @param string $type * @return ParameterGenerator */ public function setType($type) { $this->type = TypeGenerator::fromTypeString($type); return $this; } /** @return string|null */ public function getType() { return $this->type ? $this->type->__toString() : null; } /** * @param string $name * @return ParameterGenerator */ public function setName($name) { $this->name = (string) $name; return $this; } /** * @return string */ public function getName() { return $this->name; } /** * Set the default value of the parameter. * * Certain variables are difficult to express * * @param mixed $defaultValue * @return ParameterGenerator */ public function setDefaultValue($defaultValue) { if ($this->variadic) { throw new Exception\InvalidArgumentException('Variadic parameter cannot have a default value'); } $this->defaultValue = $defaultValue instanceof ValueGenerator ? $defaultValue : new ValueGenerator($defaultValue); return $this; } /** * @return ?ValueGenerator */ public function getDefaultValue() { return $this->defaultValue; } /** * @param int $position * @return ParameterGenerator */ public function setPosition($position) { $this->position = (int) $position; return $this; } /** * @return int */ public function getPosition() { return $this->position; } /** * @return bool */ public function getPassedByReference() { return $this->passedByReference; } /** * @param bool $passedByReference * @return ParameterGenerator */ public function setPassedByReference($passedByReference) { $this->passedByReference = (bool) $passedByReference; return $this; } /** * @param bool $variadic * @return ParameterGenerator */ public function setVariadic($variadic) { $this->variadic = (bool) $variadic; if (true === $this->variadic && isset($this->defaultValue)) { throw new Exception\InvalidArgumentException('Variadic parameter cannot have a default value'); } return $this; } /** * @return bool */ public function getVariadic() { return $this->variadic; } /** * @return string */ public function generate() { $output = $this->generateTypeHint(); if (true === $this->passedByReference) { $output .= '&'; } if ($this->variadic) { $output .= '... '; } $output .= '$' . $this->name; if ($this->omitDefaultValue) { return $output; } if ($this->defaultValue instanceof ValueGenerator) { $output .= ' = '; $this->defaultValue->setOutputMode(ValueGenerator::OUTPUT_SINGLE_LINE); $output .= $this->defaultValue; } return $output; } /** * @return string */ private function generateTypeHint() { if (null === $this->type) { return ''; } return $this->type->generate() . ' '; } /** * @return ParameterGenerator */ public function omitDefaultValue(bool $omit = true) { $this->omitDefaultValue = $omit; return $this; } } src/Generator/PromotedParameterGenerator.php000064400000005503147133776660015322 0ustar00visibility = $visibility; } /** @psalm-return non-empty-string */ public function generate(): string { return $this->visibility . ' ' . parent::generate(); } public static function fromReflection(ParameterReflection $reflectionParameter): self { if (! $reflectionParameter->isPromoted()) { throw new RuntimeException( sprintf('Can not create "%s" from unprompted reflection.', self::class) ); } $visibility = self::VISIBILITY_PUBLIC; if ($reflectionParameter->isProtectedPromoted()) { $visibility = self::VISIBILITY_PROTECTED; } elseif ($reflectionParameter->isPrivatePromoted()) { $visibility = self::VISIBILITY_PRIVATE; } return self::fromParameterGeneratorWithVisibility( parent::fromReflection($reflectionParameter), $visibility ); } /** @psalm-param PromotedParameterGenerator::VISIBILITY_* $visibility */ public static function fromParameterGeneratorWithVisibility(ParameterGenerator $generator, string $visibility): self { $name = $generator->getName(); $type = $generator->getType(); if ('' === $name) { throw new \Laminas\Code\Generator\Exception\RuntimeException( 'Name of promoted parameter must be non-empty-string.' ); } if ('' === $type) { throw new \Laminas\Code\Generator\Exception\RuntimeException( 'Type of promoted parameter must be non-empty-string.' ); } return new self( $name, $type, $visibility, $generator->getPosition(), $generator->getPassedByReference() ); } } src/Generator/PropertyGenerator.php000064400000024541147133776660013517 0ustar00setName($name); } if (null !== $defaultValue) { $this->setDefaultValue($defaultValue); } if ($flags !== self::FLAG_PUBLIC) { $this->setFlags($flags); } $this->type = $type; } /** @return static */ public static function fromReflection(PropertyReflection $reflectionProperty) { $property = new static(); $property->setName($reflectionProperty->getName()); $allDefaultProperties = $reflectionProperty->getDeclaringClass()->getDefaultProperties(); $defaultValue = $allDefaultProperties[$reflectionProperty->getName()] ?? null; $property->setDefaultValue($defaultValue); if ($defaultValue === null) { $property->omitDefaultValue = true; } $docBlock = $reflectionProperty->getDocBlock(); if ($docBlock) { $property->setDocBlock(DocBlockGenerator::fromReflection($docBlock)); } if ($reflectionProperty->isStatic()) { $property->setStatic(true); } if ($reflectionProperty->isReadonly()) { $property->setReadonly(true); } if ($reflectionProperty->isPrivate()) { $property->setVisibility(self::VISIBILITY_PRIVATE); } elseif ($reflectionProperty->isProtected()) { $property->setVisibility(self::VISIBILITY_PROTECTED); } else { $property->setVisibility(self::VISIBILITY_PUBLIC); } $property->setType(TypeGenerator::fromReflectionType( $reflectionProperty->getType(), $reflectionProperty->getDeclaringClass() )); $property->setSourceDirty(false); return $property; } /** * Generate from array * * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @configkey name string [required] Class Name * @configkey const bool * @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator * @configkey flags int * @configkey abstract bool * @configkey final bool * @configkey static bool * @configkey visibility string * @configkey omitdefaultvalue bool * @configkey readonly bool * @configkey type null|TypeGenerator * @return static * @throws Exception\InvalidArgumentException */ public static function fromArray(array $array) { if (! isset($array['name'])) { throw new Exception\InvalidArgumentException( 'Property generator requires that a name is provided for this object' ); } $property = new static($array['name']); foreach ($array as $name => $value) { // normalize key switch (strtolower(str_replace(['.', '-', '_'], '', $name))) { case 'const': $property->setConst($value); break; case 'defaultvalue': $property->setDefaultValue($value); break; case 'docblock': $docBlock = $value instanceof DocBlockGenerator ? $value : DocBlockGenerator::fromArray($value); $property->setDocBlock($docBlock); break; case 'flags': $property->setFlags($value); break; case 'abstract': $property->setAbstract($value); break; case 'final': $property->setFinal($value); break; case 'static': $property->setStatic($value); break; case 'visibility': $property->setVisibility($value); break; case 'omitdefaultvalue': $property->omitDefaultValue($value); break; case 'readonly': if (! is_bool($value)) { throw new Exception\InvalidArgumentException(sprintf( '%s is expecting boolean on key %s. Got %s', __METHOD__, $name, get_debug_type($value) )); } $property->setReadonly($value); break; case 'type': if (! $value instanceof TypeGenerator) { throw new Exception\InvalidArgumentException(sprintf( '%s is expecting %s on key %s. Got %s', __METHOD__, TypeGenerator::class, $name, get_debug_type($value) )); } $property->setType($value); break; } } return $property; } /** * @param bool $const * @return PropertyGenerator */ public function setConst($const) { if (true === $const) { $this->setFlags(self::FLAG_CONSTANT); return $this; } $this->removeFlag(self::FLAG_CONSTANT); return $this; } /** * @return bool */ public function isConst() { return (bool) ($this->flags & self::FLAG_CONSTANT); } public function setReadonly(bool $readonly): self { if (true === $readonly) { $this->setFlags(self::FLAG_READONLY); return $this; } $this->removeFlag(self::FLAG_READONLY); return $this; } public function isReadonly(): bool { return (bool) ($this->flags & self::FLAG_READONLY); } /** @inheritDoc */ public function setFlags($flags) { $flags = array_reduce((array) $flags, static function (int $a, int $b): int { return $a | $b; }, 0); if ($flags & self::FLAG_READONLY && $flags & self::FLAG_STATIC) { throw new Exception\RuntimeException('Modifier "readonly" in combination with "static" not permitted.'); } if ($flags & self::FLAG_READONLY && $flags & self::FLAG_CONSTANT) { throw new Exception\RuntimeException('Modifier "readonly" in combination with "constant" not permitted.'); } return parent::setFlags($flags); } /** * @return ?PropertyValueGenerator */ public function getDefaultValue() { return $this->defaultValue; } /** * @param PropertyValueGenerator|mixed $defaultValue * @param PropertyValueGenerator::TYPE_* $defaultValueType * @param PropertyValueGenerator::OUTPUT_* $defaultValueOutputMode * @return static */ public function setDefaultValue( $defaultValue, $defaultValueType = PropertyValueGenerator::TYPE_AUTO, $defaultValueOutputMode = PropertyValueGenerator::OUTPUT_MULTIPLE_LINE ) { if (! $defaultValue instanceof PropertyValueGenerator) { $defaultValue = new PropertyValueGenerator($defaultValue, $defaultValueType, $defaultValueOutputMode); } $this->defaultValue = $defaultValue; return $this; } /** * @return string * @psalm-return non-empty-string * @throws Exception\RuntimeException */ public function generate() { $name = $this->getName(); $defaultValue = $this->getDefaultValue(); $output = ''; if (($docBlock = $this->getDocBlock()) !== null) { $docBlock->setIndentation(' '); $output .= $docBlock->generate(); } if ($this->isConst()) { if ($defaultValue !== null && ! $defaultValue->isValidConstantType()) { throw new Exception\RuntimeException(sprintf( 'The property %s is said to be ' . 'constant but does not have a valid constant value.', $this->name )); } return $output . $this->indentation . ($this->isFinal() ? 'final ' : '') . $this->getVisibility() . ' const ' . $name . ' = ' . ($defaultValue !== null ? $defaultValue->generate() : 'null;'); } $type = $this->type; $output .= $this->indentation . $this->getVisibility() . ($this->isReadonly() ? ' readonly' : '') . ($this->isStatic() ? ' static' : '') . ($type ? ' ' . $type->generate() : '') . ' $' . $name; if ($this->omitDefaultValue) { return $output . ';'; } return $output . ' = ' . ($defaultValue !== null ? $defaultValue->generate() : 'null;'); } /** * @return PropertyGenerator */ public function omitDefaultValue(bool $omit = true) { $this->omitDefaultValue = $omit; return $this; } public function getType(): ?TypeGenerator { return $this->type; } public function setType(?TypeGenerator $type): void { $this->type = $type; } } src/Generator/PropertyValueGenerator.php000064400000000400147133776660014500 0ustar00getName()); $cg->setSourceContent($cg->getSourceContent()); $cg->setSourceDirty(false); if ($classReflection->getDocComment() != '') { $cg->setDocBlock(DocBlockGenerator::fromReflection($classReflection->getDocBlock())); } // set the namespace if ($classReflection->inNamespace()) { $cg->setNamespaceName($classReflection->getNamespaceName()); } $properties = []; foreach ($classReflection->getProperties() as $reflectionProperty) { if ($reflectionProperty->getDeclaringClass()->getName() == $classReflection->getName()) { $properties[] = PropertyGenerator::fromReflection($reflectionProperty); } } $cg->addProperties($properties); $methods = []; foreach ($classReflection->getMethods() as $reflectionMethod) { $className = $cg->getName(); $namespaceName = $cg->getNamespaceName(); if ($namespaceName !== null) { $className = $namespaceName . '\\' . $className; } if ($reflectionMethod->getDeclaringClass()->getName() == $className) { $methods[] = MethodGenerator::fromReflection($reflectionMethod); } } $cg->addMethods($methods); return $cg; } /** * Generate from array * * @deprecated this API is deprecated, and will be removed in the next major release. Please * use the other constructors of this class instead. * * @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 properties * @configkey methods * @throws Exception\InvalidArgumentException * @return static */ public static function fromArray(array $array) { if (! isset($array['name'])) { throw new Exception\InvalidArgumentException( 'Class generator requires that a name is provided for this object' ); } $cg = new static($array['name']); foreach ($array as $name => $value) { // normalize key switch (strtolower(str_replace(['.', '-', '_'], '', $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 'properties': $cg->addProperties($value); break; case 'methods': $cg->addMethods($value); break; } } return $cg; } /** * @inheritDoc * @param int[]|int $flags */ public function setFlags($flags) { return $this; } /** * @param int $flag * @return static */ public function addFlag($flag) { return $this; } /** * @param int $flag * @return static */ public function removeFlag($flag) { return $this; } /** * @inheritDoc */ public function setFinal($isFinal) { return $this; } /** * @param ?string $extendedClass * @return static */ public function setExtendedClass($extendedClass) { return $this; } /** * @inheritDoc */ public function setImplementedInterfaces(array $implementedInterfaces) { return $this; } /** * @inheritDoc */ public function setAbstract($isAbstract) { return $this; } } src/Generator/TraitUsageGenerator.php000064400000030022147133776660013732 0ustar00 Array of trait names */ protected array $traits = []; /** * @var array< * non-empty-string, * array{ * alias: string, * visibility: Visibility|null * } * > Array of trait aliases */ protected array $traitAliases = []; /** @var array Array of trait overrides */ protected array $traitOverrides = []; /** @var array Array of string names */ protected array $uses = []; public function __construct(ClassGenerator $classGenerator) { $this->classGenerator = $classGenerator; } /** * @inheritDoc */ public function addUse($use, $useAlias = null) { $this->removeUse($use); if (! empty($useAlias)) { $use .= ' as ' . $useAlias; } $this->uses[$use] = $use; return $this; } /** @inheritDoc */ public function getUses() { return array_values($this->uses); } /** * @param string $use * @return bool */ public function hasUse($use) { foreach ($this->uses as $key => $value) { $parts = explode(' ', $value); if ($parts[0] === $use) { return true; } } return false; } /** * @param string $use * @return bool */ public function hasUseAlias($use) { foreach ($this->uses as $key => $value) { $parts = explode(' as ', $value); if ($parts[0] === $use && count($parts) == 2) { return true; } } return false; } /** * Returns the alias of the provided FQCN */ public function getUseAlias(string $use): ?string { foreach ($this->uses as $key => $value) { $parts = explode(' as ', $key); if ($parts[0] === $use && count($parts) == 2) { return $parts[1]; } } return null; } /** * Returns true if the alias is defined in the use list */ public function isUseAlias(string $alias): bool { foreach ($this->uses as $key => $value) { $parts = explode(' as ', $key); if (count($parts) === 2 && $parts[1] === $alias) { return true; } } return false; } /** * @param string $use * @return TraitUsageGenerator */ public function removeUse($use) { foreach ($this->uses as $key => $value) { $parts = explode(' ', $value); if ($parts[0] === $use) { unset($this->uses[$value]); } } return $this; } /** * @param string $use * @return TraitUsageGenerator */ public function removeUseAlias($use) { foreach ($this->uses as $key => $value) { $parts = explode(' as ', $value); if ($parts[0] === $use && count($parts) == 2) { unset($this->uses[$value]); } } return $this; } /** * @inheritDoc */ public function addTrait($trait) { if (is_array($trait)) { if (! array_key_exists('traitName', $trait)) { throw new Exception\InvalidArgumentException('Missing required value for traitName'); } $traitName = $trait['traitName']; if (array_key_exists('aliases', $trait)) { foreach ($trait['aliases'] as $alias) { $this->addAlias($alias); } } if (array_key_exists('insteadof', $trait)) { foreach ($trait['insteadof'] as $insteadof) { $this->addTraitOverride($insteadof); } } } else { $traitName = $trait; } if (! $this->hasTrait($traitName)) { $this->traits[] = $traitName; } return $this; } /** * @inheritDoc */ public function addTraits(array $traits) { foreach ($traits as $trait) { $this->addTrait($trait); } return $this; } /** * @inheritDoc */ public function hasTrait($traitName) { return in_array($traitName, $this->traits); } /** * @inheritDoc */ public function getTraits() { return $this->traits; } /** * @inheritDoc */ public function removeTrait($traitName) { $key = array_search($traitName, $this->traits); if (false !== $key) { unset($this->traits[$key]); } return $this; } /** * @inheritDoc */ public function addTraitAlias($method, $alias, $visibility = null) { if (is_array($method)) { if (! array_key_exists('traitName', $method)) { throw new Exception\InvalidArgumentException('Missing required argument "traitName" for $method'); } if (! array_key_exists('method', $method)) { throw new Exception\InvalidArgumentException('Missing required argument "method" for $method'); } $traitAndMethod = $method['traitName'] . '::' . $method['method']; } else { $traitAndMethod = $method; } // Validations if (! str_contains($traitAndMethod, '::')) { throw new Exception\InvalidArgumentException( 'Invalid Format: $method must be in the format of trait::method' ); } if (! is_string($alias)) { throw new Exception\InvalidArgumentException('Invalid Alias: $alias must be a string or array.'); } if ($this->classGenerator->hasMethod($alias)) { throw new Exception\InvalidArgumentException('Invalid Alias: Method name already exists on this class.'); } if ( null !== $visibility && $visibility !== ReflectionMethod::IS_PUBLIC && $visibility !== ReflectionMethod::IS_PRIVATE && $visibility !== ReflectionMethod::IS_PROTECTED ) { throw new Exception\InvalidArgumentException( 'Invalid Type: $visibility must of ReflectionMethod::IS_PUBLIC,' . ' ReflectionMethod::IS_PRIVATE or ReflectionMethod::IS_PROTECTED' ); } [$trait, $method] = explode('::', $traitAndMethod); if (! $this->hasTrait($trait)) { throw new Exception\InvalidArgumentException('Invalid trait: Trait does not exists on this class'); } $this->traitAliases[$traitAndMethod] = [ 'alias' => $alias, 'visibility' => $visibility, ]; return $this; } /** * @inheritDoc */ public function getTraitAliases() { return $this->traitAliases; } /** * @inheritDoc */ public function addTraitOverride($method, $traitsToReplace) { if (false === is_array($traitsToReplace)) { $traitsToReplace = [$traitsToReplace]; } $traitAndMethod = $method; if (is_array($method)) { if (! array_key_exists('traitName', $method)) { throw new Exception\InvalidArgumentException('Missing required argument "traitName" for $method'); } if (! array_key_exists('method', $method)) { throw new Exception\InvalidArgumentException('Missing required argument "method" for $method'); } $traitAndMethod = (string) $method['traitName'] . '::' . (string) $method['method']; } // Validations if (! str_contains($traitAndMethod, '::')) { throw new Exception\InvalidArgumentException( 'Invalid Format: $method must be in the format of trait::method' ); } [$trait, $method] = explode('::', $traitAndMethod); if (! $this->hasTrait($trait)) { throw new Exception\InvalidArgumentException('Invalid trait: Trait does not exists on this class'); } if (! array_key_exists($traitAndMethod, $this->traitOverrides)) { $this->traitOverrides[$traitAndMethod] = []; } foreach ($traitsToReplace as $traitToReplace) { if (! is_string($traitToReplace)) { throw new Exception\InvalidArgumentException( 'Invalid Argument: $traitToReplace must be a string or array of strings' ); } if (! in_array($traitToReplace, $this->traitOverrides[$traitAndMethod])) { $this->traitOverrides[$traitAndMethod][] = $traitToReplace; } } return $this; } /** * @inheritDoc */ public function removeTraitOverride($method, $overridesToRemove = null) { if (! array_key_exists($method, $this->traitOverrides)) { return $this; } if (null === $overridesToRemove) { unset($this->traitOverrides[$method]); return $this; } $overridesToRemove = ! is_array($overridesToRemove) ? [$overridesToRemove] : $overridesToRemove; foreach ($overridesToRemove as $traitToRemove) { $key = array_search($traitToRemove, $this->traitOverrides[$method]); if (false !== $key) { unset($this->traitOverrides[$method][$key]); } } return $this; } /** * @inheritDoc */ public function getTraitOverrides() { return $this->traitOverrides; } /** * @inheritDoc */ public function generate() { $output = ''; $indent = $this->getIndentation(); $traits = $this->getTraits(); if (empty($traits)) { return $output; } $output .= $indent . 'use ' . implode(', ', $traits); $aliases = $this->getTraitAliases(); $overrides = $this->getTraitOverrides(); if (empty($aliases) && empty($overrides)) { return $output . ';' . self::LINE_FEED . self::LINE_FEED; } $output .= ' {' . self::LINE_FEED; foreach ($aliases as $method => $alias) { $visibility = null !== $alias['visibility'] ? current(Reflection::getModifierNames($alias['visibility'])) . ' ' : ''; // validation check if ($this->classGenerator->hasMethod($alias['alias'])) { throw new Exception\RuntimeException(sprintf( 'Generation Error: Aliased method %s already exists on this class', $alias['alias'] )); } $output .= $indent . $indent . $method . ' as ' . $visibility . $alias['alias'] . ';' . self::LINE_FEED; } foreach ($overrides as $method => $insteadofTraits) { foreach ($insteadofTraits as $insteadofTrait) { $output .= $indent . $indent . $method . ' insteadof ' . $insteadofTrait . ';' . self::LINE_FEED; } } return $output . $indent . '}' . self::LINE_FEED . self::LINE_FEED; } } src/Generator/TraitUsageInterface.php000064400000011303147133776660013705 0ustar00 */ public function getUses(); /** * Add trait takes an array of trait options or string as arguments. * * Array Format: * key: traitName value: String * * key: aliases value: array of arrays * key: method value: @see addTraitAlias * key: alias value: @see addTraitAlias * key: visibility value: @see addTraitAlias * * key: insteadof value: array of arrays * key: method value: @see self::addTraitOverride * key: traitToReplace value: @see self::addTraitOverride * * @param string|array $trait * @psalm-param string|array{traitName: string, aliases?: array, insteadof?: array} $trait * @return self */ public function addTrait($trait); /** * Add multiple traits. Trait can be an array of trait names or array of trait * configurations * * @param array $traits Array of string names or configurations (@see addTrait) * @psalm-param list $traits * @return self */ public function addTraits(array $traits); /** * Check to see if the class has a trait defined * * @param string $traitName * @return bool */ public function hasTrait($traitName); /** * Get a list of trait names * * @return array */ public function getTraits(); /** * Remove a trait by its name * * @param string $traitName * @return self */ public function removeTrait($traitName); /** * Add a trait alias. This will be used to generate the AS portion of the use statement. * * $method: * This method provides 2 ways for defining the trait method. * Option 1: String * Option 2: Array * key: traitName value: name of trait * key: method value: trait method * * $alias: * Alias is a string representing the new method name. * * @param array{traitName: non-empty-string, method: non-empty-string}|non-empty-string $method * @param non-empty-string $alias * @param ReflectionMethod::IS_PUBLIC|ReflectionMethod::IS_PRIVATE|ReflectionMethod::IS_PROTECTED|null $visibility * @return $this */ public function addTraitAlias($method, $alias, $visibility = null); /** * @return array< * non-empty-string, * array{ * alias: string, * visibility: ReflectionMethod::IS_PRIVATE|ReflectionMethod::IS_PROTECTED|ReflectionMethod::IS_PUBLIC|null * } * > */ public function getTraitAliases(); /** * Add a trait method override. This will be used to generate the INSTEADOF portion of the use * statement. * * $method: * This method provides 2 ways for defining the trait method. * Option 1: String Format: :: * Option 2: Array * key: traitName value: trait name * key: method value: method name * * $traitToReplace: * The name of the trait that you wish to supersede. * * This method provides 2 ways for defining the trait method. * Option 1: String of trait to replace * Option 2: Array of strings of traits to replace * @param mixed $method * @param mixed $traitsToReplace * @return $this */ public function addTraitOverride($method, $traitsToReplace); /** * Remove an override for a given trait::method * * $method: * This method provides 2 ways for defining the trait method. * Option 1: String Format: :: * Option 2: Array * key: traitName value: trait name * key: method value: method name * * $overridesToRemove: * The name of the trait that you wish to remove. * * This method provides 2 ways for defining the trait method. * Option 1: String of trait to replace * Option 2: Array of strings of traits to replace * * @param mixed $method * @param mixed $overridesToRemove * @return self */ public function removeTraitOverride($method, $overridesToRemove = null); /** * Return trait overrides * * @return array */ public function getTraitOverrides(); } src/Generator/TypeGenerator.php000064400000011603147133776660012607 0ustar00assertCanBeStandaloneNullable(); } } /** * @internal * * @psalm-pure */ public static function fromReflectionType( ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $type, ?ReflectionClass $currentClass ): ?self { if (null === $type) { return null; } if ($type instanceof ReflectionUnionType) { return new self( new UnionType(array_map( static fn( ReflectionIntersectionType|ReflectionNamedType $type ): IntersectionType|AtomicType => $type instanceof ReflectionNamedType ? AtomicType::fromReflectionNamedTypeAndClass($type, $currentClass) : self::fromIntersectionType($type, $currentClass), $type->getTypes() )), false ); } if ($type instanceof ReflectionIntersectionType) { return new self(self::fromIntersectionType($type, $currentClass), false); } $atomicType = AtomicType::fromReflectionNamedTypeAndClass($type, $currentClass); return new self( $atomicType, $atomicType->type !== 'mixed' && $atomicType !== 'null' && $type->allowsNull() ); } /** @psalm-pure */ private static function fromIntersectionType( ReflectionIntersectionType $intersectionType, ?ReflectionClass $currentClass ): IntersectionType { return new IntersectionType(array_map( static fn( ReflectionNamedType $type ): AtomicType => AtomicType::fromReflectionNamedTypeAndClass($type, $currentClass), $intersectionType->getTypes() )); } /** * @throws InvalidArgumentException * @psalm-pure */ public static function fromTypeString(string $type): self { [$nullable, $trimmedNullable] = self::trimNullable($type); if ( ! str_contains($trimmedNullable, CompositeType::INTERSECTION_SEPARATOR) && ! str_contains($trimmedNullable, CompositeType::UNION_SEPARATOR) ) { return new self(CompositeType::fromString($trimmedNullable), $nullable); } if ($nullable) { throw new InvalidArgumentException(sprintf( 'Type "%s" is a union type, and therefore cannot be also marked nullable with the "?" prefix', $type )); } return new self(CompositeType::fromString($trimmedNullable)); } /** * {@inheritDoc} * * Generates the type string, including FQCN "\\" prefix, so that * it can directly be used within any code snippet, regardless of * imports. * * @psalm-return non-empty-string */ public function generate(): string { return ($this->nullable ? self::NULL_MARKER : '') . $this->type->fullyQualifiedName(); } public function equals(TypeGenerator $otherType): bool { return $this->generate() === $otherType->generate(); } /** * @return non-empty-string the cleaned type string. Note that this value is not suitable for code generation, * since the returned value does not include any root namespace prefixes, when applicable, * and therefore the values cannot be used as FQCN in generated code. */ public function __toString(): string { return $this->type->toString(); } /** * @return bool[]|string[] ordered tuple, first key represents whether the type is nullable, second is the * trimmed string * @psalm-return array{bool, string} * @psalm-pure */ private static function trimNullable(string $type): array { if (str_starts_with($type, self::NULL_MARKER)) { return [true, substr($type, 1)]; } return [false, $type]; } } src/Generator/TypeGenerator/AtomicType.php000064400000015067147133776660014675 0ustar00 */ private const BUILT_IN_TYPES_PRECEDENCE = [ 'bool' => 1, 'int' => 2, 'float' => 3, 'string' => 4, 'array' => 5, 'callable' => 6, 'iterable' => 7, 'object' => 8, 'static' => 9, 'mixed' => 10, 'void' => 11, 'false' => 12, 'true' => 13, 'null' => 14, 'never' => 15, ]; /** @psalm-var array */ private const NOT_NULLABLE_TYPES = [ 'null' => null, 'false' => null, 'true' => null, 'void' => null, 'mixed' => null, 'never' => null, ]; /** A regex pattern to match valid class/interface/trait names */ private const VALID_IDENTIFIER_MATCHER = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*' . '(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/'; /** @psalm-var value-of|0 */ public int $sortIndex; /** @psalm-var non-empty-string */ public string $type; /** * @psalm-param non-empty-string $type * @psalm-param value-of|0 $sortIndex */ private function __construct(string $type, int $sortIndex) { $this->type = $type; $this->sortIndex = $sortIndex; } /** * @psalm-pure * @throws InvalidArgumentException */ public static function fromString(string $type): self { $trimmedType = '\\' === ($type[0] ?? '') ? substr($type, 1) : $type; $lowerCaseType = strtolower($trimmedType); if (array_key_exists($lowerCaseType, self::BUILT_IN_TYPES_PRECEDENCE)) { if ($lowerCaseType !== strtolower($type)) { throw new InvalidArgumentException(sprintf( 'Provided type "%s" is a built-in type, and should not be prefixed with "\\"', $type )); } return new self($lowerCaseType, self::BUILT_IN_TYPES_PRECEDENCE[$lowerCaseType]); } if (1 !== preg_match(self::VALID_IDENTIFIER_MATCHER, $trimmedType)) { throw new InvalidArgumentException(sprintf( 'Provided type "%s" is not recognized as a valid expression: ' . 'it must match "%s" or be one of the built-in types (%s)', $type, self::VALID_IDENTIFIER_MATCHER, implode(', ', self::BUILT_IN_TYPES_PRECEDENCE) )); } assert('' !== $trimmedType); return new self($trimmedType, 0); } /** * @psalm-pure * @throws InvalidArgumentException */ public static function fromReflectionNamedTypeAndClass( ReflectionNamedType $type, ?ReflectionClass $currentClass ): self { $name = $type->getName(); $lowerCaseName = strtolower($name); if ('self' === $lowerCaseName && $currentClass) { return new self($currentClass->getName(), 0); } if ('parent' === $lowerCaseName && $currentClass && $parentClass = $currentClass->getParentClass()) { return new self($parentClass->getName(), 0); } return self::fromString($name); } /** @psalm-return non-empty-string */ public function fullyQualifiedName(): string { return array_key_exists($this->type, self::BUILT_IN_TYPES_PRECEDENCE) ? $this->type : '\\' . $this->type; } /** @return non-empty-string */ public function toString(): string { return $this->type; } /** @throws InvalidArgumentException */ public function assertCanUnionWith(self|IntersectionType $other): void { if ($other instanceof IntersectionType) { $other->assertCanUnionWith($this); return; } if ( 'mixed' === $this->type || 'void' === $this->type || 'never' === $this->type ) { throw new InvalidArgumentException(sprintf( 'Type "%s" cannot be composed in a union with any other types', $this->type )); } if ($other->type === $this->type) { throw new InvalidArgumentException(sprintf( 'Type "%s" cannot be composed in a union with the same type "%s"', $this->type, $other->type )); } if ( ('true' === $other->type && 'false' === $this->type) || ('false' === $other->type && 'true' === $this->type) ) { throw new InvalidArgumentException(sprintf( 'Type "%s" cannot be composed in a union with type "%s"', $this->type, $other->type )); } } /** @throws InvalidArgumentException */ public function assertCanIntersectWith(AtomicType $other): void { if (array_key_exists($this->type, self::BUILT_IN_TYPES_PRECEDENCE)) { throw new InvalidArgumentException(sprintf( 'Type "%s" cannot be composed in an intersection with any other types', $this->type )); } if ($other->type === $this->type) { throw new InvalidArgumentException(sprintf( 'Type "%s" cannot be composed in an intersection with the same type "%s"', $this->type, $other->type )); } } /** @throws InvalidArgumentException */ public function assertCanBeStandaloneNullable(): void { if (array_key_exists($this->type, self::NOT_NULLABLE_TYPES)) { throw new InvalidArgumentException(sprintf( 'Type "%s" cannot be nullable', $this->type )); } } } src/Generator/TypeGenerator/CompositeType.php000064400000004353147133776660015417 0ustar00 $typesInUnion */ $typesInUnion = array_map( self::fromString(...), array_map( static fn (string $type): string => trim($type, '()'), explode(self::UNION_SEPARATOR, $type) ) ); return new UnionType($typesInUnion); } if (str_contains($type, self::INTERSECTION_SEPARATOR)) { /** @var non-empty-list $typesInIntersection */ $typesInIntersection = array_map(self::fromString(...), explode(self::INTERSECTION_SEPARATOR, $type)); return new IntersectionType($typesInIntersection); } return AtomicType::fromString($type); } } src/Generator/TypeGenerator/IntersectionType.php000064400000004617147133776660016126 0ustar00 sorted, at least 2 values always present */ private readonly array $types; /** * @param non-empty-list $types at least 2 values needed * @throws InvalidArgumentException If the given types cannot intersect. */ public function __construct(array $types) { usort( $types, static fn(AtomicType $a, AtomicType $b): int => $a->type <=> $b->type ); foreach ($types as $index => $atomicType) { foreach (array_diff_key($types, array_flip([$index])) as $otherType) { $atomicType->assertCanIntersectWith($otherType); } } $this->types = $types; } /** @return non-empty-string */ public function toString(): string { return implode( '&', array_map(static fn(AtomicType $type): string => $type->toString(), $this->types) ); } /** @return non-empty-string */ public function fullyQualifiedName(): string { return implode( '&', array_map(static fn(AtomicType $type): string => $type->fullyQualifiedName(), $this->types) ); } /** @throws InvalidArgumentException When the union is not possible. */ public function assertCanUnionWith(AtomicType|self $other): void { if ($other instanceof AtomicType) { foreach ($this->types as $type) { $type->assertCanUnionWith($other); } return; } $thisString = $this->toString(); $otherString = $other->toString(); if (str_contains($thisString, $otherString) || str_contains($otherString, $thisString)) { throw new InvalidArgumentException(sprintf( 'Types "%s" and "%s" cannot be intersected, as they include each other', $thisString, $otherString )); } } } src/Generator/TypeGenerator/UnionType.php000064400000004032147133776660014537 0ustar00 $types sorted, at least 2 values always present */ private readonly array $types; /** @param non-empty-list $types at least 2 values needed */ public function __construct(array $types) { usort( $types, static fn(AtomicType|IntersectionType $a, AtomicType|IntersectionType $b): int => [ $a instanceof IntersectionType ? -1 : $a->sortIndex, $a->toString(), ] <=> [ $b instanceof IntersectionType ? -1 : $b->sortIndex, $b->toString(), ] ); foreach ($types as $index => $type) { foreach (array_diff_key($types, array_flip([$index])) as $otherType) { $type->assertCanUnionWith($otherType); } } $this->types = $types; } /** @return non-empty-string */ public function toString(): string { return implode( '|', array_map( static fn(AtomicType|IntersectionType $type): string => $type instanceof IntersectionType ? '(' . $type->toString() . ')' : $type->toString(), $this->types ) ); } /** @return non-empty-string */ public function fullyQualifiedName(): string { return implode( '|', array_map( static fn(AtomicType|IntersectionType $type): string => $type instanceof IntersectionType ? '(' . $type->fullyQualifiedName() . ')' : $type->fullyQualifiedName(), $this->types ) ); } } src/Generator/ValueGenerator.php000064400000035077147133776660012755 0ustar00setValue($value); } if ($type !== self::TYPE_AUTO) { $this->setType($type); } if ($outputMode !== self::OUTPUT_MULTIPLE_LINE) { $this->setOutputMode($outputMode); } if ($constants === null) { $constants = new SplArrayObject(); } elseif (! ($constants instanceof SplArrayObject || $constants instanceof StdlibArrayObject)) { throw new InvalidArgumentException( '$constants must be an instance of ArrayObject or Laminas\Stdlib\ArrayObject' ); } $this->constants = $constants; } /** * Init constant list by defined and magic constants * * @deprecated this method attempts to make some magic constants work with the value generator, * but the value generator is not aware of its surrounding, and cannot really * generate constant expressions. For such a functionality, consider using an AST-based * code builder instead. */ public function initEnvironmentConstants() { $constants = [ '__DIR__', '__FILE__', '__LINE__', '__CLASS__', '__TRAIT__', '__METHOD__', '__FUNCTION__', '__NAMESPACE__', '::', ]; $constants = array_merge($constants, array_keys(get_defined_constants()), $this->constants->getArrayCopy()); $this->constants->exchangeArray($constants); } /** * Add constant to list * * @deprecated this method attempts to make some magic constants work with the value generator, * but the value generator is not aware of its surrounding, and cannot really * generate constant expressions. For such a functionality, consider using an AST-based * code builder instead. * * @param string $constant * @return $this */ public function addConstant($constant) { $this->constants->append($constant); return $this; } /** * Delete constant from constant list * * @deprecated this method attempts to make some magic constants work with the value generator, * but the value generator is not aware of its surrounding, and cannot really * generate constant expressions. For such a functionality, consider using an AST-based * code builder instead. * * @param string $constant * @return bool */ public function deleteConstant($constant) { if (($index = array_search($constant, $this->constants->getArrayCopy())) !== false) { $this->constants->offsetUnset($index); } return $index !== false; } /** * Return constant list * * @deprecated this method attempts to make some magic constants work with the value generator, * but the value generator is not aware of its surrounding, and cannot really * generate constant expressions. For such a functionality, consider using an AST-based * code builder instead. * * @return SplArrayObject|StdlibArrayObject */ public function getConstants() { return $this->constants; } /** * @return bool */ public function isValidConstantType() { if ($this->type === self::TYPE_AUTO) { $type = $this->getAutoDeterminedType($this->value); } else { $type = $this->type; } $validConstantTypes = [ self::TYPE_ARRAY, self::TYPE_ARRAY_LONG, self::TYPE_ARRAY_SHORT, self::TYPE_BOOLEAN, self::TYPE_BOOL, self::TYPE_NUMBER, self::TYPE_INTEGER, self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_DOUBLE, self::TYPE_STRING, self::TYPE_CONSTANT, self::TYPE_NULL, ]; return in_array($type, $validConstantTypes); } /** * @param mixed $value * @return ValueGenerator */ public function setValue($value) { $this->value = $value; return $this; } /** * @return mixed */ public function getValue() { return $this->value; } /** * @param string $type * @return ValueGenerator */ public function setType($type) { $this->type = (string) $type; return $this; } /** * @return string */ public function getType() { return $this->type; } /** * @param int $arrayDepth * @return ValueGenerator */ public function setArrayDepth($arrayDepth) { $this->arrayDepth = (int) $arrayDepth; return $this; } /** * @return int */ public function getArrayDepth() { return $this->arrayDepth; } /** * @param string $type * @return string */ protected function getValidatedType($type) { $types = [ self::TYPE_AUTO, self::TYPE_BOOLEAN, self::TYPE_BOOL, self::TYPE_NUMBER, self::TYPE_INTEGER, self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_DOUBLE, self::TYPE_STRING, self::TYPE_ARRAY, self::TYPE_ARRAY_SHORT, self::TYPE_ARRAY_LONG, self::TYPE_CONSTANT, self::TYPE_NULL, self::TYPE_ENUM, self::TYPE_OBJECT, self::TYPE_OTHER, ]; if (in_array($type, $types)) { return $type; } return self::TYPE_AUTO; } /** * @param mixed $value * @return string */ public function getAutoDeterminedType($value) { switch (gettype($value)) { case 'boolean': return self::TYPE_BOOLEAN; case 'string': foreach ($this->constants as $constant) { if ($value === $constant) { return self::TYPE_CONSTANT; } if (str_contains($value, $constant)) { return self::TYPE_CONSTANT; } } return self::TYPE_STRING; case 'double': case 'float': case 'integer': return self::TYPE_NUMBER; case 'array': return self::TYPE_ARRAY; case 'NULL': return self::TYPE_NULL; case 'object': if ($value instanceof UnitEnum) { return self::TYPE_ENUM; } // enums are typed as objects, so this fall through is intentional case 'resource': case 'unknown type': default: return self::TYPE_OTHER; } } /** * @throws Exception\RuntimeException * @return string */ public function generate() { $type = $this->type; if ($type !== self::TYPE_AUTO) { $type = $this->getValidatedType($type); } $value = $this->value; if ($type === self::TYPE_AUTO) { $type = $this->getAutoDeterminedType($value); } $isArrayType = in_array($type, [self::TYPE_ARRAY, self::TYPE_ARRAY_LONG, self::TYPE_ARRAY_SHORT]); if ($isArrayType) { foreach ($value as &$curValue) { if ($curValue instanceof self) { continue; } if (is_array($curValue)) { $newType = $type; } else { $newType = self::TYPE_AUTO; } $curValue = new self($curValue, $newType, $this->outputMode, $this->getConstants()); $curValue->setIndentation($this->indentation); } } $output = ''; switch ($type) { case self::TYPE_BOOLEAN: case self::TYPE_BOOL: $output .= $value ? 'true' : 'false'; break; case self::TYPE_STRING: $output .= self::escape($value); break; case self::TYPE_NULL: $output .= 'null'; break; case self::TYPE_NUMBER: case self::TYPE_INTEGER: case self::TYPE_INT: case self::TYPE_FLOAT: case self::TYPE_DOUBLE: case self::TYPE_CONSTANT: $output .= $value; break; case self::TYPE_ARRAY: case self::TYPE_ARRAY_LONG: case self::TYPE_ARRAY_SHORT: if ($type === self::TYPE_ARRAY_LONG) { $startArray = 'array('; $endArray = ')'; } else { $startArray = '['; $endArray = ']'; } $output .= $startArray; if ($this->outputMode == self::OUTPUT_MULTIPLE_LINE) { $output .= self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth + 1); } $outputParts = []; $noKeyIndex = 0; foreach ($value as $n => $v) { /** @var ValueGenerator $v */ $v->setArrayDepth($this->arrayDepth + 1); $partV = $v->generate(); $short = false; if (is_int($n)) { if ($n === $noKeyIndex) { $short = true; $noKeyIndex++; } else { $noKeyIndex = max($n + 1, $noKeyIndex); } } if ($short) { $outputParts[] = $partV; } else { $outputParts[] = (is_int($n) ? $n : self::escape($n)) . ' => ' . $partV; } } $padding = $this->outputMode == self::OUTPUT_MULTIPLE_LINE ? self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth + 1) : ' '; $output .= implode(',' . $padding, $outputParts); if ($this->outputMode == self::OUTPUT_MULTIPLE_LINE) { if (count($outputParts) > 0) { $output .= ','; } $output .= self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth); } $output .= $endArray; break; case self::TYPE_ENUM: if (! is_object($value)) { throw new Exception\RuntimeException('Value is not an object.'); } $output = sprintf('\%s::%s', $value::class, (string) $value->name); break; case self::TYPE_OTHER: default: throw new Exception\RuntimeException(sprintf( 'Type "%s" is unknown or cannot be used as property default value.', get_debug_type($value) )); } return $output; } /** * Quotes value for PHP code. * * @param string $input Raw string. * @param bool $quote Whether add surrounding quotes or not. * @return string PHP-ready code. */ public static function escape($input, $quote = true) { $output = addcslashes($input, "\\'"); // adds quoting strings if ($quote) { $output = "'" . $output . "'"; } return $output; } /** * @param self::OUTPUT_* $outputMode * @return $this */ public function setOutputMode($outputMode) { $this->outputMode = (string) $outputMode; return $this; } /** * @return self::OUTPUT_* */ public function getOutputMode() { return $this->outputMode; } /** @return string */ public function __toString() { return $this->generate(); } } src/Generic/Prototype/PrototypeClassFactory.php000064400000006076147133776660016005 0ustar00 */ protected $prototypes = []; /** @var PrototypeGenericInterface|null */ protected $genericPrototype; /** * @param PrototypeInterface[] $prototypes */ public function __construct(array $prototypes = [], ?PrototypeGenericInterface $genericPrototype = null) { foreach ($prototypes as $prototype) { $this->addPrototype($prototype); } if ($genericPrototype) { $this->setGenericPrototype($genericPrototype); } } /** * @throws Exception\InvalidArgumentException */ public function addPrototype(PrototypeInterface $prototype): void { $prototypeName = $this->normalizeName($prototype->getName()); if (isset($this->prototypes[$prototypeName])) { throw new Exception\InvalidArgumentException('A prototype with this name already exists in this manager'); } $this->prototypes[$prototypeName] = $prototype; } /** * @throws Exception\InvalidArgumentException */ public function setGenericPrototype(PrototypeGenericInterface $prototype): void { if (isset($this->genericPrototype)) { throw new Exception\InvalidArgumentException('A default prototype is already set'); } $this->genericPrototype = $prototype; } /** * @param string $name * @return string */ protected function normalizeName($name) { return str_replace(['-', '_'], '', $name); } /** * @param string $name * @return bool */ public function hasPrototype($name) { $name = $this->normalizeName($name); return isset($this->prototypes[$name]); } /** * @param string $prototypeName * @return PrototypeInterface * @throws Exception\RuntimeException */ public function getClonedPrototype($prototypeName) { $prototypeName = $this->normalizeName($prototypeName); if (! $this->hasPrototype($prototypeName) && ! isset($this->genericPrototype)) { throw new Exception\RuntimeException('This tag name is not supported by this tag manager'); } if (! $this->hasPrototype($prototypeName)) { $newPrototype = clone $this->genericPrototype; $newPrototype->setName($prototypeName); return $newPrototype; } return clone $this->prototypes[$prototypeName]; } } src/Generic/Prototype/PrototypeGenericInterface.php000064400000000442147133776660016574 0ustar00 */ class ClassReflection extends ReflectionClass implements ReflectionInterface { /** @var DocBlockReflection|null */ protected $docBlock; /** * Return the classes DocBlock reflection object * * @return DocBlockReflection|false * @throws Exception\ExceptionInterface When 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; } /** * {@inheritDoc} * * @param bool $includeDocComment * @return int|false */ #[ReturnTypeWillChange] 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() - $this->getStartLine(); // 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 implemented interfaces * * @return array */ #[ReturnTypeWillChange] public function getInterfaces() { return array_map( static fn (ReflectionClass $interface): ClassReflection => new ClassReflection($interface->getName()), parent::getInterfaces() ); } /** * Return method reflection by name * * @param string $name * @return MethodReflection */ #[ReturnTypeWillChange] public function getMethod($name) { return new MethodReflection($this->getName(), parent::getMethod($name)->getName()); } /** * {@inheritDoc} * * @param int $filter * @return list */ #[ReturnTypeWillChange] public function getMethods($filter = -1) { $name = $this->getName(); return array_map( static fn (ReflectionMethod $method): MethodReflection => new MethodReflection($name, $method->getName()), parent::getMethods($filter) ); } /** * {@inheritDoc} * * @return array */ #[ReturnTypeWillChange] public function getTraits() { return array_map( static fn (ReflectionClass $trait): ClassReflection => new ClassReflection($trait->getName()), parent::getTraits() ); } /** * {@inheritDoc} * * @return ClassReflection|false */ #[ReturnTypeWillChange] public function getParentClass() { $reflection = parent::getParentClass(); if (! $reflection) { return false; } return new ClassReflection($reflection->getName()); } /** * {@inheritDoc} * * @param string $name * @return PropertyReflection */ #[ReturnTypeWillChange] public function getProperty($name) { $phpReflection = parent::getProperty($name); $laminasReflection = new PropertyReflection($this->getName(), $phpReflection->getName()); unset($phpReflection); return $laminasReflection; } /** * {@inheritDoc} * * @param int $filter * @return list */ #[ReturnTypeWillChange] public function getProperties($filter = -1) { $name = $this->getName(); return array_map( static fn (ReflectionProperty $property): PropertyReflection => new PropertyReflection($name, $property->getName()), parent::getProperties($filter) ); } /** * @return string */ public function toString() { return parent::__toString(); } /** * @return string */ public function __toString() { return parent::__toString(); } } src/Reflection/DocBlock/Tag/AuthorTag.php000064400000002173147133776660014256 0ustar00]*)\>)?(.*)$/u', $content, $match)) { return; } if ($match[1] !== '') { $this->authorName = rtrim($match[1]); } if (isset($match[3]) && $match[3] !== '') { $this->authorEmail = $match[3]; } } /** @return null|string */ public function getAuthorName() { return $this->authorName; } /** @return null|string */ public function getAuthorEmail() { return $this->authorEmail; } /** @return non-empty-string */ public function __toString() { return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n"; } } src/Reflection/DocBlock/Tag/GenericTag.php000064400000003215147133776660014366 0ustar00 */ protected $values = []; /** * @param string $contentSplitCharacter */ public function __construct($contentSplitCharacter = ' ') { $this->contentSplitCharacter = $contentSplitCharacter; } /** @inheritDoc */ public function initialize($content) { $this->parse($content); } /** @return string|null */ public function getName() { return $this->name; } /** * @param string $name * @return void */ public function setName($name) { $this->name = $name; } /** @return string|null */ public function getContent() { return $this->content; } /** * @param int $position * @return string */ public function returnValue($position) { return $this->values[$position]; } /** @return non-empty-string */ public function __toString() { return 'DocBlock Tag [ * @' . $this->name . ' ]' . "\n"; } /** * @param string $docBlockLine * @return void */ protected function parse($docBlockLine) { $this->content = trim($docBlockLine); $this->values = explode($this->contentSplitCharacter, $docBlockLine); } } src/Reflection/DocBlock/Tag/LicenseTag.php000064400000002130147133776660014367 0ustar00url = trim($match[1]); } if (isset($match[2]) && $match[2] !== '') { $this->licenseName = $match[2]; } } /** @return null|string */ public function getUrl() { return $this->url; } /** @return null|string */ public function getLicenseName() { return $this->licenseName; } /** @return non-empty-string */ public function __toString() { return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n"; } } src/Reflection/DocBlock/Tag/MethodTag.php000064400000003564147133776660014241 0ustar00 */ protected $types = []; /** @var string|null */ protected $methodName; /** @var string|null */ protected $description; /** @var bool */ protected $isStatic = false; /** @return 'method' */ public function getName() { return 'method'; } /** @inheritDoc */ public function initialize($content) { $match = []; if (! preg_match('#^(static[\s]+)?(.+[\s]+)?(.+\(\))[\s]*(.*)$#m', $content, $match)) { return; } if ($match[1] !== '') { $this->isStatic = true; } if ($match[2] !== '') { $this->types = explode('|', rtrim($match[2])); } $this->methodName = $match[3]; if ($match[4] !== '') { $this->description = $match[4]; } } /** * Get return value type * * @deprecated 2.0.4 use getTypes instead * * @return null|string */ public function getReturnType() { if (empty($this->types)) { return null; } return $this->types[0]; } /** @inheritDoc */ public function getTypes() { return $this->types; } /** @return string|null */ public function getMethodName() { return $this->methodName; } /** @return string|null */ public function getDescription() { return $this->description; } /** @return bool */ public function isStatic() { return $this->isStatic; } /** @return non-empty-string */ public function __toString() { return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n"; } } src/Reflection/DocBlock/Tag/ParamTag.php000064400000003077147133776660014060 0ustar00 */ protected $types = []; /** @var string|null */ protected $variableName; /** @var string|null */ protected $description; /** @return 'param' */ public function getName() { return 'param'; } /** @inheritDoc */ public function initialize($content) { $matches = []; if (! preg_match('#((?:[\w|\\\]+(?:\[\])*\|?)+)(?:\s+(\$\S+))?(?:\s+(.*))?#s', $content, $matches)) { return; } $this->types = explode('|', $matches[1]); if (isset($matches[2])) { $this->variableName = $matches[2]; } if (isset($matches[3])) { $this->description = trim(preg_replace('#\s+#', ' ', $matches[3])); } } /** * Get parameter variable type * * @deprecated 2.0.4 use getTypes instead * * @return string */ public function getType() { if (empty($this->types)) { return ''; } return $this->types[0]; } /** @inheritDoc */ public function getTypes() { return $this->types; } /** @return string|null */ public function getVariableName() { return $this->variableName; } /** @return string|null */ public function getDescription() { return $this->description; } } src/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php000064400000000351147133776660017014 0ustar00 */ public function getTypes(); } src/Reflection/DocBlock/Tag/PropertyTag.php000064400000003321147133776660014634 0ustar00 */ protected $types = []; /** @var string|null */ protected $propertyName; /** @var string|null */ protected $description; /** * @return string */ public function getName() { return 'property'; } /** @inheritDoc */ public function initialize($content) { $match = []; if (! preg_match('#^(.+)?(\$[\S]+)[\s]*(.*)$#m', $content, $match)) { return; } if ($match[1] !== '') { $this->types = explode('|', rtrim($match[1])); } if ($match[2] !== '') { $this->propertyName = $match[2]; } if ($match[3] !== '') { $this->description = $match[3]; } } /** * @deprecated 2.0.4 use getTypes instead * * @return null|string */ public function getType() { if (empty($this->types)) { return null; } return $this->types[0]; } /** @inheritDoc */ public function getTypes() { return $this->types; } /** * @return null|string */ public function getPropertyName() { return $this->propertyName; } /** * @return null|string */ public function getDescription() { return $this->description; } /** * @return string * @psalm-return non-empty-string */ public function __toString() { return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n"; } } src/Reflection/DocBlock/Tag/ReturnTag.php000064400000002422147133776660014270 0ustar00 */ protected $types = []; /** @var string|null */ protected $description; /** * @return string */ public function getName() { return 'return'; } /** @inheritDoc */ public function initialize($content) { $matches = []; if (! preg_match('#((?:[\w|\\\]+(?:\[\])*\|?)+)(?:\s+(.*))?#s', $content, $matches)) { return; } $this->types = explode('|', $matches[1]); if (isset($matches[2])) { $this->description = trim(preg_replace('#\s+#', ' ', $matches[2])); } } /** * @deprecated 2.0.4 use getTypes instead * * @return string */ public function getType() { if (empty($this->types)) { return ''; } return $this->types[0]; } /** @inheritDoc */ public function getTypes() { return $this->types; } /** * @return string|null */ public function getDescription() { return $this->description; } } src/Reflection/DocBlock/Tag/TagInterface.php000064400000000423147133776660014710 0ustar00 */ protected $types = []; /** @var string|null */ protected $description; /** * @return string */ public function getName() { return 'throws'; } /** @inheritDoc */ public function initialize($content) { $matches = []; preg_match('#([\w|\\\]+)(?:\s+(.*))?#', $content, $matches); $this->types = explode('|', $matches[1]); if (isset($matches[2])) { $this->description = $matches[2]; } } /** * Get return variable type * * @deprecated 2.0.4 use getTypes instead * * @return string */ public function getType() { return implode('|', $this->getTypes()); } /** @inheritDoc */ public function getTypes() { return $this->types; } /** * @return string|null */ public function getDescription() { return $this->description; } } src/Reflection/DocBlock/Tag/VarTag.php000064400000003036147133776660013543 0ustar00 */ private $types = []; /** @var string|null */ private $variableName; /** @var string|null */ private $description; /** @inheritDoc */ public function getName(): string { return 'var'; } /** @inheritDoc */ public function initialize($content): void { $match = []; if ( ! preg_match( '#^([^\$]\S+)?\s*(\$[\S]+)?\s*(.*)$#m', $content, $match ) ) { return; } if ($match[1] !== '') { $this->types = explode('|', rtrim($match[1])); } if ($match[2] !== '') { $this->variableName = $match[2]; } if ($match[3] !== '') { $this->description = $match[3]; } } /** @inheritDoc */ public function getTypes(): array { return $this->types; } public function getVariableName(): ?string { return $this->variableName; } public function getDescription(): ?string { return $this->description; } /** * @psalm-return non-empty-string */ public function __toString(): string { return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL; } } src/Reflection/DocBlock/TagManager.php000064400000002163147133776660013652 0ustar00addPrototype(new Tag\ParamTag()); $this->addPrototype(new Tag\ReturnTag()); $this->addPrototype(new Tag\MethodTag()); $this->addPrototype(new Tag\PropertyTag()); $this->addPrototype(new Tag\AuthorTag()); $this->addPrototype(new Tag\LicenseTag()); $this->addPrototype(new Tag\ThrowsTag()); $this->addPrototype(new Tag\VarTag()); $this->setGenericPrototype(new Tag\GenericTag()); } /** * @param string $tagName * @param string $content * @return TagInterface */ public function createTag($tagName, $content = null) { /** @var TagInterface $newTag */ $newTag = $this->getClonedPrototype($tagName); if ($content) { $newTag->initialize($content); } return $newTag; } } src/Reflection/DocBlockReflection.php000064400000014600147133776660013656 0ustar00initializeDefaultTags(); } $this->tagManager = $tagManager; if ($commentOrReflector instanceof Reflector) { $this->reflector = $commentOrReflector; if (! method_exists($commentOrReflector, 'getDocComment')) { throw new Exception\InvalidArgumentException('Reflector must contain method "getDocComment"'); } $this->docComment = $commentOrReflector->getDocComment(); // determine line numbers $lineCount = substr_count($this->docComment, "\n"); $this->startLine = $this->reflector->getStartLine() - $lineCount - 1; $this->endLine = $this->reflector->getStartLine() - 1; } elseif (is_string($commentOrReflector)) { $this->docComment = $commentOrReflector; } else { throw new Exception\InvalidArgumentException(sprintf( '%s must have a (string) DocComment or a Reflector in the constructor', static::class )); } if ($this->docComment == '') { throw new Exception\InvalidArgumentException('DocComment cannot be empty'); } $this->reflect(); } /** * Retrieve contents of DocBlock * * @return string */ public function getContents() { $this->reflect(); return $this->cleanDocComment; } /** * Get start line (position) of DocBlock * * @return int */ public function getStartLine() { $this->reflect(); return $this->startLine; } /** * Get last line (position) of DocBlock * * @return int */ public function getEndLine() { $this->reflect(); return $this->endLine; } /** * Get DocBlock short description * * @return string */ public function getShortDescription() { $this->reflect(); return $this->shortDescription; } /** * Get DocBlock long description * * @return string */ public function getLongDescription() { $this->reflect(); return $this->longDescription; } /** * Does the DocBlock contain the given annotation tag? * * @param string $name * @return bool */ public function hasTag($name) { $this->reflect(); foreach ($this->tags as $tag) { if ($tag->getName() == $name) { return true; } } return false; } /** * Retrieve the given DocBlock tag * * @param string $name * @return DocBlockTagInterface|false */ public function getTag($name) { $this->reflect(); foreach ($this->tags as $tag) { if ($tag->getName() == $name) { return $tag; } } return false; } /** * Get all DocBlock annotation tags * * @param string $filter * @return DocBlockTagInterface[] */ public function getTags($filter = null) { $this->reflect(); if ($filter === null || ! is_string($filter)) { return $this->tags; } $returnTags = []; foreach ($this->tags as $tag) { if ($tag->getName() == $filter) { $returnTags[] = $tag; } } return $returnTags; } /** * Parse the DocBlock * * @return void */ protected function reflect() { if ($this->isReflected) { return; } $docComment = preg_replace('#[ ]{0,1}\*/$#', '', $this->docComment); // create a clean docComment $this->cleanDocComment = preg_replace("#[ \t]*(?:/\*\*|\*/|\*)[ ]{0,1}(.*)?#", '$1', $docComment); // @todo should be changed to remove first and last empty line $this->cleanDocComment = ltrim($this->cleanDocComment, "\r\n"); $scanner = new DocBlockScanner($docComment); $this->shortDescription = ltrim($scanner->getShortDescription()); $this->longDescription = ltrim($scanner->getLongDescription()); foreach ($scanner->getTags() as $tag) { $this->tags[] = $this->tagManager->createTag(ltrim($tag['name'], '@'), ltrim($tag['value'])); } $this->isReflected = true; } /** * @return string */ public function toString() { $str = 'DocBlock [ /* DocBlock */ ] {' . "\n\n"; $str .= ' - Tags [' . count($this->tags) . '] {' . "\n"; foreach ($this->tags as $tag) { $str .= ' ' . $tag; } $str .= ' }' . "\n"; $str .= '}' . "\n"; return $str; } /** * Serialize to string * * Required by the Reflector interface * * @return string */ public function __toString() { return $this->toString(); } } src/Reflection/Exception/BadMethodCallException.php000064400000000276147133776660016427 0ustar00getDocComment())) { throw new Exception\InvalidArgumentException(sprintf( '%s does not have a DocBlock', $this->getName() )); } return new DocBlockReflection($comment); } /** * Get start line (position) of function * * @param bool $includeDocComment * @return int */ #[ReturnTypeWillChange] public function getStartLine($includeDocComment = false) { if ($includeDocComment) { if ($this->getDocComment() != '') { return $this->getDocBlock()->getStartLine(); } } return parent::getStartLine(); } /** * Get contents of function * * @param bool $includeDocBlock * @return string */ public function getContents($includeDocBlock = true) { $fileName = $this->getFileName(); if (false === $fileName) { return ''; } $startLine = $this->getStartLine(); $endLine = $this->getEndLine(); // eval'd protect if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) { $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName); $startLine = $endLine = $matches[1]; } $lines = array_slice( file($fileName, FILE_IGNORE_NEW_LINES), $startLine - 1, $endLine - ($startLine - 1), true ); $functionLine = implode("\n", $lines); $content = ''; if ($this->isClosure()) { preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)?\s*\}#s', $functionLine, $matches); if (isset($matches[0])) { $content = $matches[0]; } } else { $name = substr($this->getName(), strrpos($this->getName(), '\\') + 1); preg_match( '#function\s+' . preg_quote($name) . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)?}#', $functionLine, $matches ); if (isset($matches[0])) { $content = $matches[0]; } } $docComment = $this->getDocComment(); return $includeDocBlock && $docComment ? $docComment . "\n" . $content : $content; } /** * Get method prototype * * @deprecated this method is unreliable, and should not be used: it will be removed in the next major release. * It may crash on parameters with union types, and will return relative types, instead of * FQN references * * @param string $format * @return array|string */ public function getPrototype($format = self::PROTOTYPE_AS_ARRAY) { $docBlock = $this->getDocBlock(); $return = $docBlock->getTag('return'); $returnTypes = $return->getTypes(); $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0]; $prototype = [ 'namespace' => $this->getNamespaceName(), 'name' => substr($this->getName(), strlen($this->getNamespaceName()) + 1), 'return' => $returnType, 'arguments' => [], ]; $parameters = $this->getParameters(); foreach ($parameters as $parameter) { $prototype['arguments'][$parameter->getName()] = [ 'type' => $parameter->detectType(), 'required' => ! $parameter->isOptional(), 'by_ref' => $parameter->isPassedByReference(), 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null, ]; } if ($format == self::PROTOTYPE_AS_STRING) { $line = $prototype['return'] . ' ' . $prototype['name'] . '('; $args = []; 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 function parameters * * @return list */ #[ReturnTypeWillChange] public function getParameters() { $name = $this->getName(); return array_map( static fn (ReflectionParameter $parameter): ParameterReflection => new ParameterReflection($name, $parameter->getName()), parent::getParameters() ); } /** * Get return type tag * * @deprecated this method is unreliable, and will be dropped in the next major release. * If you are attempting to inspect the return type of an expression, please * use more reliable tools, such as `vimeo/psalm` or `phpstan/phpstan` instead. * * @throws Exception\InvalidArgumentException * @return DocBlockReflection */ public function getReturn() { $docBlock = $this->getDocBlock(); if (! $docBlock->hasTag('return')) { throw new Exception\InvalidArgumentException( 'Function does not specify an @return annotation tag; cannot determine return type' ); } $tag = $docBlock->getTag('return'); return new DocBlockReflection('@return ' . $tag->getDescription()); } /** * Get method body * * @return string|false */ public function getBody() { $fileName = $this->getFileName(); if (false === $fileName) { throw new Exception\InvalidArgumentException( 'Cannot determine internals functions body' ); } $startLine = $this->getStartLine(); $endLine = $this->getEndLine(); // eval'd protect if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) { $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName); $startLine = $endLine = $matches[1]; } $lines = array_slice( file($fileName, FILE_IGNORE_NEW_LINES), $startLine - 1, $endLine - ($startLine - 1), true ); $functionLine = implode("\n", $lines); $body = false; if ($this->isClosure()) { preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)\s*\}#s', $functionLine, $matches); if (isset($matches[2])) { $body = $matches[2]; } } else { $name = substr($this->getName(), strrpos($this->getName(), '\\') + 1); preg_match('#function\s+' . $name . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)}#', $functionLine, $matches); if (isset($matches[1])) { $body = $matches[1]; } } return $body; } /** * @return string */ public function toString() { return $this->__toString(); } /** * Required due to bug in php * * @return string */ public function __toString() { return parent::__toString(); } } src/Reflection/MethodReflection.php000064400000035051147133776660013421 0ustar00getDocComment()) { return false; } return new DocBlockReflection($this); } /** * Get start line (position) of method * * @param bool $includeDocComment * @return int */ #[ReturnTypeWillChange] public function getStartLine($includeDocComment = false) { if ($includeDocComment) { if ($this->getDocComment() != '') { return $this->getDocBlock()->getStartLine(); } } return parent::getStartLine(); } /** * Get reflection of declaring class * * @return ClassReflection */ #[ReturnTypeWillChange] public function getDeclaringClass() { $phpReflection = parent::getDeclaringClass(); $laminasReflection = new ClassReflection($phpReflection->getName()); unset($phpReflection); return $laminasReflection; } /** * Get method prototype * * @deprecated this method is unreliable, and should not be used: it will be removed in the next major release. * It may crash on parameters with union types, and will return relative types, instead of * FQN references * * @param string $format * @return array|string */ #[ReturnTypeWillChange] 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->getDeclaringClass(); $prototype = [ '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' => [], ]; $parameters = $this->getParameters(); foreach ($parameters as $parameter) { $prototype['arguments'][$parameter->getName()] = [ 'type' => $parameter->detectType(), 'required' => ! $parameter->isOptional(), 'by_ref' => $parameter->isPassedByReference(), 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null, ]; if ($parameter->isPromoted()) { $prototype['arguments'][$parameter->getName()]['promoted'] = true; if ($parameter->isPublicPromoted()) { $prototype['arguments'][$parameter->getName()]['visibility'] = 'public'; } elseif ($parameter->isProtectedPromoted()) { $prototype['arguments'][$parameter->getName()]['visibility'] = 'protected'; } elseif ($parameter->isPrivatePromoted()) { $prototype['arguments'][$parameter->getName()]['visibility'] = 'private'; } } } if ($format == self::PROTOTYPE_AS_STRING) { $line = $prototype['visibility'] . ' ' . $prototype['return'] . ' ' . $prototype['name'] . '('; $args = []; foreach ($prototype['arguments'] as $name => $argument) { $argsLine = ( array_key_exists('visibility', $argument) ? $argument['visibility'] . ' ' : '' ) . ( $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 list */ #[ReturnTypeWillChange] public function getParameters() { $method = [$this->getDeclaringClass()->getName(), $this->getName()]; return array_map( static fn (PhpReflectionParameter $parameter): ParameterReflection => new ParameterReflection($method, $parameter->getName()), parent::getParameters() ); } /** * 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 $this->extractMethodContents(true); } /** * Tokenize method string and return concatenated body * * @param bool $bodyOnly * @return string */ protected function extractMethodContents($bodyOnly = false) { $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 ($bodyOnly === false) { //if first instance of tokenType grab prefixed whitespace //and append to body if ($capture === false) { $body .= $this->extractPrefixedWhitespace($tokens, $key); } $body .= $tokenValue; } $capture = true; } else { //closure test if ($firstBrace && $tokenType == 'T_FUNCTION') { $body .= $tokenValue; break; } $capture = false; break; } break; case '{': if ($capture === false) { break; } if ($firstBrace === false) { $firstBrace = true; if ($bodyOnly === true) { break; } } $body .= $tokenValue; break; case '}': if ($capture === false) { break; } //check to see if this is the last brace if ($this->isEndingBrace($tokens, $key)) { //capture the end brace if not bodyOnly if ($bodyOnly === false) { $body .= $tokenValue; } break 2; } $body .= $tokenValue; break; default: if ($capture === false) { break; } // if returning body only wait for first brace before capturing if ($bodyOnly === true && $firstBrace !== true) { break; } $body .= $tokenValue; 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|null */ protected function isEndingBrace($haystack, $position) { $count = count($haystack); //advance one 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; } } return null; } /** * 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 occurrence of ( or if ($tokenType == 'T_STRING') { //check to see if function name is passed, if so validate 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(); } } src/Reflection/ParameterReflection.php000064400000010766147133776660014127 0ustar00getName()); } /** * Get class reflection object * * @return null|ClassReflection */ #[ReturnTypeWillChange] public function getClass() { $type = parent::getType(); if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) { return null; } return new ClassReflection($type->getName()); } /** * Get declaring function reflection object * * @return FunctionReflection|MethodReflection */ #[ReturnTypeWillChange] public function getDeclaringFunction() { $function = parent::getDeclaringFunction(); if ($function instanceof ReflectionMethod) { return new MethodReflection($function->getDeclaringClass()->getName(), $function->getName()); } return new FunctionReflection($function->getName()); } /** * Get parameter type * * @deprecated this method is unreliable, and should not be used: it will be removed in the next major release. * It may crash on parameters with union types, and will return relative types, instead of * FQN references * * @return string|null */ public function detectType() { if ( null !== ($type = $this->getType()) && $type->isBuiltin() ) { return $type->getName(); } if (null !== $type && $type->getName() === 'self') { $declaringClass = $this->getDeclaringClass(); assert($declaringClass !== null, 'A parameter called `self` can only exist on a class'); return $declaringClass->getName(); } if (($class = $this->getClass()) instanceof ReflectionClass) { return $class->getName(); } $docBlock = $this->getDeclaringFunction()->getDocBlock(); if (! $docBlock instanceof DocBlockReflection) { return null; } /** @var ParamTag[] $params */ $params = $docBlock->getTags('param'); $paramTag = $params[$this->getPosition()] ?? null; $variableName = '$' . $this->getName(); if ($paramTag && ('' === $paramTag->getVariableName() || $variableName === $paramTag->getVariableName())) { return $paramTag->getTypes()[0] ?? ''; } foreach ($params as $param) { if ($param->getVariableName() === $variableName) { return $param->getTypes()[0] ?? ''; } } return null; } /** * @return string */ public function toString() { return parent::__toString(); } public function isPublicPromoted(): bool { $property = $this->promotedProperty(); if ($property === null) { return false; } return (bool) ($property->getModifiers() & ReflectionProperty::IS_PUBLIC); } public function isProtectedPromoted(): bool { $property = $this->promotedProperty(); if ($property === null) { return false; } return (bool) ($property->getModifiers() & ReflectionProperty::IS_PROTECTED); } public function isPrivatePromoted(): bool { $property = $this->promotedProperty(); if ($property === null) { return false; } return (bool) ($property->getModifiers() & ReflectionProperty::IS_PRIVATE); } private function promotedProperty(): ?ReflectionProperty { if (! $this->isPromoted()) { return null; } $declaringClass = $this->getDeclaringClass(); assert($declaringClass !== null, 'Promoted properties are always part of a class'); return $declaringClass->getProperty($this->getName()); } } src/Reflection/PropertyReflection.php000064400000002313147133776660014020 0ustar00getName()); unset($phpReflection); return $laminasReflection; } /** * Get DocBlock comment * * @return string|false False if no DocBlock defined */ #[ReturnTypeWillChange] public function getDocComment() { return parent::getDocComment(); } /** * @return false|DocBlockReflection */ public function getDocBlock() { if (! ($docComment = $this->getDocComment())) { return false; } return new DocBlockReflection($docComment); } /** * @return string */ public function toString() { return $this->__toString(); } } src/Reflection/ReflectionInterface.php000064400000000377147133776660014104 0ustar00docComment = $docComment; } /** * @return string */ public function getShortDescription() { $this->scan(); return $this->shortDescription; } /** * @return string */ public function getLongDescription() { $this->scan(); return $this->longDescription; } /** * @return array */ public function getTags() { $this->scan(); return $this->tags; } /** * @return void */ protected function scan() { if ($this->isScanned) { return; } $mode = 1; $tokens = $this->tokenize(); $tagIndex = null; reset($tokens); SCANNER_TOP: $token = current($tokens); switch ($token[0]) { case 'DOCBLOCK_NEWLINE': if ($this->shortDescription != '' && $tagIndex === null) { $mode = 2; } else { $this->longDescription .= $token[1]; } goto SCANNER_CONTINUE; //goto no break needed case 'DOCBLOCK_WHITESPACE': case 'DOCBLOCK_TEXT': if ($tagIndex !== null) { $this->tags[$tagIndex]['value'] .= $this->tags[$tagIndex]['value'] == '' ? $token[1] : ' ' . $token[1]; goto SCANNER_CONTINUE; } elseif ($mode <= 2) { if ($mode == 1) { $this->shortDescription .= $token[1]; } else { $this->longDescription .= $token[1]; } goto SCANNER_CONTINUE; } //gotos no break needed case 'DOCBLOCK_TAG': array_push($this->tags, [ 'name' => $token[1], 'value' => '', ]); end($this->tags); $tagIndex = key($this->tags); $mode = 3; goto SCANNER_CONTINUE; //goto no break needed case 'DOCBLOCK_COMMENTEND': goto SCANNER_END; } SCANNER_CONTINUE: if (next($tokens) === false) { goto SCANNER_END; } goto SCANNER_TOP; SCANNER_END: $this->shortDescription = trim($this->shortDescription); $this->longDescription = trim($this->longDescription); $this->isScanned = true; } /** * @phpcs:disable Generic.Formatting.MultipleStatementAlignment.NotSame * @return array */ protected function tokenize() { static $CONTEXT_INSIDE_DOCBLOCK = 0x01; static $CONTEXT_INSIDE_ASTERISK = 0x02; $context = 0x00; $stream = $this->docComment; $streamIndex = null; $tokens = []; $tokenIndex = null; $currentChar = null; $currentWord = null; $currentLine = null; $MACRO_STREAM_ADVANCE_CHAR = function ($positionsForward = 1) use ( &$stream, &$streamIndex, &$currentChar, &$currentWord, &$currentLine ) { $positionsForward = $positionsForward > 0 ? $positionsForward : 1; $streamIndex = $streamIndex === null ? 0 : $streamIndex + $positionsForward; if (! isset($stream[$streamIndex])) { $currentChar = false; return false; } $currentChar = $stream[$streamIndex]; $matches = []; $currentLine = preg_match('#(.*?)\r?\n#', $stream, $matches, 0, $streamIndex) === 1 ? $matches[1] : substr($stream, $streamIndex); if ($currentChar === ' ') { $currentWord = preg_match('#( +)#', $currentLine, $matches) === 1 ? $matches[1] : $currentLine; } else { $currentWord = ($matches = strpos($currentLine, ' ')) !== false ? substr($currentLine, 0, $matches) : $currentLine; } return $currentChar; }; $MACRO_STREAM_ADVANCE_WORD = function () use (&$currentWord, &$MACRO_STREAM_ADVANCE_CHAR) { return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentWord)); }; $MACRO_STREAM_ADVANCE_LINE = function () use (&$currentLine, &$MACRO_STREAM_ADVANCE_CHAR) { return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentLine)); }; $MACRO_TOKEN_ADVANCE = function () use (&$tokenIndex, &$tokens) { $tokenIndex = $tokenIndex === null ? 0 : $tokenIndex + 1; $tokens[$tokenIndex] = ['DOCBLOCK_UNKNOWN', '']; }; $MACRO_TOKEN_SET_TYPE = function ($type) use (&$tokenIndex, &$tokens) { $tokens[$tokenIndex][0] = $type; }; $MACRO_TOKEN_APPEND_CHAR = function () use (&$currentChar, &$tokens, &$tokenIndex) { $tokens[$tokenIndex][1] .= $currentChar; }; $MACRO_TOKEN_APPEND_WORD = function () use (&$currentWord, &$tokens, &$tokenIndex) { $tokens[$tokenIndex][1] .= $currentWord; }; $MACRO_TOKEN_APPEND_WORD_PARTIAL = function ($length) use (&$currentWord, &$tokens, &$tokenIndex) { $tokens[$tokenIndex][1] .= substr($currentWord, 0, $length); }; $MACRO_TOKEN_APPEND_LINE = function () use (&$currentLine, &$tokens, &$tokenIndex) { $tokens[$tokenIndex][1] .= $currentLine; }; $MACRO_STREAM_ADVANCE_CHAR(); $MACRO_TOKEN_ADVANCE(); TOKENIZER_TOP: if ($context === 0x00 && $currentChar === '/' && $currentWord === '/**') { $MACRO_TOKEN_SET_TYPE('DOCBLOCK_COMMENTSTART'); $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); $context |= $CONTEXT_INSIDE_DOCBLOCK; $context |= $CONTEXT_INSIDE_ASTERISK; if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($context & $CONTEXT_INSIDE_DOCBLOCK && $currentWord === '*/') { $MACRO_TOKEN_SET_TYPE('DOCBLOCK_COMMENTEND'); $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); $context &= ~$CONTEXT_INSIDE_DOCBLOCK; if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === ' ' || $currentChar === "\t") { $MACRO_TOKEN_SET_TYPE( $context & $CONTEXT_INSIDE_ASTERISK ? 'DOCBLOCK_WHITESPACE' : 'DOCBLOCK_WHITESPACE_INDENT' ); $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === '*') { if (($context & $CONTEXT_INSIDE_DOCBLOCK) && ($context & $CONTEXT_INSIDE_ASTERISK)) { $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TEXT'); } else { $MACRO_TOKEN_SET_TYPE('DOCBLOCK_ASTERISK'); $context |= $CONTEXT_INSIDE_ASTERISK; } $MACRO_TOKEN_APPEND_CHAR(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === '@') { $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TAG'); $MACRO_TOKEN_APPEND_WORD(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_WORD() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } if ($currentChar === "\n") { $MACRO_TOKEN_SET_TYPE('DOCBLOCK_NEWLINE'); $MACRO_TOKEN_APPEND_CHAR(); $MACRO_TOKEN_ADVANCE(); $context &= ~$CONTEXT_INSIDE_ASTERISK; if ($MACRO_STREAM_ADVANCE_CHAR() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; } $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TEXT'); $MACRO_TOKEN_APPEND_LINE(); $MACRO_TOKEN_ADVANCE(); if ($MACRO_STREAM_ADVANCE_LINE() === false) { goto TOKENIZER_END; } goto TOKENIZER_TOP; TOKENIZER_END: array_pop($tokens); return $tokens; } }