Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
{
"ignore_php_platform_requirements": {
"8.4": true
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Laminas\Code;
use Laminas\Code\Exception\InvalidArgumentException;
use function array_keys;
use function gettype;
use function implode;
use function is_string;
use function key;
use function lcfirst;
use function sprintf;
use function str_replace;
use function ucwords;
class DeclareStatement
{
public const TICKS = 'ticks';
public const STRICT_TYPES = 'strict_types';
public const ENCODING = 'encoding';
private const ALLOWED = [
self::TICKS => 'integer',
self::STRICT_TYPES => 'integer',
self::ENCODING => 'string',
];
/** @param int|string $value */
private function __construct(protected string $directive, protected $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);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Laminas\Code\Exception;
class BadMethodCallException extends \BadMethodCallException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Laminas\Code\Exception;
interface ExceptionInterface
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Laminas\Code\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Laminas\Code\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Laminas\Code\Generator;
use Traversable;
use function get_debug_type;
use function is_array;
use function method_exists;
use function sprintf;
abstract class AbstractGenerator implements GeneratorInterface
{
/**
* Line feed to use in place of EOL
*/
public const LINE_FEED = "\n";
protected bool $isSourceDirty = true;
/** @var string 4 spaces by default */
protected string $indentation = ' ';
/**
* TODO: Type should be changed to "string" in the next major version. Nullable for BC
*/
protected ?string $sourceContent = null;
/**
* @param array $options
*/
public function __construct($options = [])
{
if ($options) {
$this->setOptions($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;
}
}

View File

@@ -0,0 +1,226 @@
<?php
namespace Laminas\Code\Generator;
use function is_array;
use function is_string;
use function sprintf;
abstract class AbstractMemberGenerator extends AbstractGenerator
{
public const FLAG_ABSTRACT = 0x01;
public const FLAG_FINAL = 0x02;
public const FLAG_STATIC = 0x04;
public const FLAG_INTERFACE = 0x08;
public const FLAG_PUBLIC = 0x10;
public const FLAG_PROTECTED = 0x20;
public const FLAG_PRIVATE = 0x40;
public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_PROTECTED = 'protected';
public const VISIBILITY_PRIVATE = 'private';
protected ?DocBlockGenerator $docBlock = null;
protected string $name = '';
protected int $flags = self::FLAG_PUBLIC;
/**
* @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 $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;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Laminas\Code\Generator;
class BodyGenerator extends AbstractGenerator
{
protected string $content = '';
/**
* @param string $content
* @return BodyGenerator
*/
public function setContent($content)
{
$this->content = (string) $content;
return $this;
}
/**
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* @return string
*/
public function generate()
{
return $this->getContent();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
<?php
namespace Laminas\Code\Generator\DocBlock;
use Laminas\Code\Generator\DocBlock\Tag\GenericTag;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface as ReflectionTagInterface;
/**
* @deprecated Deprecated in 2.3. Use GenericTag instead
*/
class Tag extends GenericTag
{
/**
* @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead
*
* @return Tag
*/
public static function fromReflection(ReflectionTagInterface $reflectionTag)
{
$tagManager = new TagManager();
$tagManager->initializeDefaultTags();
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();
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generator\AbstractGenerator;
use function explode;
use function implode;
use function is_string;
/**
* This abstract class can be used as parent for all tags
* that use a type part in their content.
*
* @see http://www.phpdoc.org/docs/latest/for-users/phpdoc/types.html
*/
abstract class AbstractTypeableTag extends AbstractGenerator
{
/** @var string|null */
protected $description;
/** @var string[] */
protected $types = [];
/**
* @param string|string[] $types
* @param string|null $description
*/
public function __construct($types = [], $description = null)
{
if (! empty($types)) {
$this->setTypes($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);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generator\AbstractGenerator;
use Laminas\Code\Generator\DocBlock\TagManager;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface as ReflectionTagInterface;
class AuthorTag extends AbstractGenerator implements TagInterface
{
/** @var string|null */
protected $authorName;
/** @var string|null */
protected $authorEmail;
/**
* @param string|null $authorName
* @param string|null $authorEmail
*/
public function __construct($authorName = null, $authorEmail = null)
{
if (! empty($authorName)) {
$this->setAuthorName($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 . '>' : '');
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generator\AbstractGenerator;
use Laminas\Code\Generic\Prototype\PrototypeGenericInterface;
use function ltrim;
class GenericTag extends AbstractGenerator implements TagInterface, PrototypeGenericInterface
{
/** @var string|null */
protected $name;
/** @var string|null */
protected $content;
/**
* @param string|null $name
* @param string|null $content
*/
public function __construct($name = null, $content = null)
{
if (! empty($name)) {
$this->setName($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 : '');
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generator\AbstractGenerator;
use Laminas\Code\Generator\DocBlock\TagManager;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface as ReflectionTagInterface;
class LicenseTag extends AbstractGenerator implements TagInterface
{
/** @var string|null */
protected $url;
/** @var string|null */
protected $licenseName;
/**
* @param string|null $url
* @param string|null $licenseName
*/
public function __construct($url = null, $licenseName = null)
{
if (! empty($url)) {
$this->setUrl($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 : '');
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use function rtrim;
class MethodTag extends AbstractTypeableTag implements TagInterface
{
/** @var string|null */
protected $methodName;
/** @var bool */
protected $isStatic = false;
/**
* @param string|null $methodName
* @param string[] $types
* @param string $description
* @param bool $isStatic
*/
public function __construct($methodName = null, $types = [], $description = null, $isStatic = false)
{
if (! empty($methodName)) {
$this->setMethodName($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 : '');
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generator\DocBlock\TagManager;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface as ReflectionTagInterface;
use function ltrim;
class ParamTag extends AbstractTypeableTag implements TagInterface
{
/** @var string */
protected $variableName;
/**
* @param string $variableName
* @param array $types
* @param string $description
*/
public function __construct($variableName = null, $types = [], $description = null)
{
if (! empty($variableName)) {
$this->setVariableName($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 : '');
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use function ltrim;
class PropertyTag extends AbstractTypeableTag implements TagInterface
{
/** @var string|null */
protected $propertyName;
/**
* @param string $propertyName
* @param string[] $types
* @param string $description
*/
public function __construct($propertyName = null, $types = [], $description = null)
{
if (! empty($propertyName)) {
$this->setPropertyName($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 : '');
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generator\DocBlock\TagManager;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface as ReflectionTagInterface;
class ReturnTag extends AbstractTypeableTag implements TagInterface
{
/**
* @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 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 : '');
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generic\Prototype\PrototypeInterface;
interface TagInterface extends PrototypeInterface
{
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
class ThrowsTag extends AbstractTypeableTag implements TagInterface
{
/**
* @return string
*/
public function getName()
{
return 'throws';
}
/**
* @return string
*/
public function generate()
{
return '@throws'
. (! empty($this->types) ? ' ' . $this->getTypesAsString() : '')
. (! empty($this->description) ? ' ' . $this->description : '');
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Laminas\Code\Generator\DocBlock\Tag;
use function ltrim;
class VarTag extends AbstractTypeableTag implements TagInterface
{
private ?string $variableName = null;
/**
* @param string|string[] $types
*/
public function __construct(?string $variableName = null, $types = [], ?string $description = null)
{
if (null !== $variableName) {
$this->variableName = 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 : '');
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Laminas\Code\Generator\DocBlock;
use Laminas\Code\Generator\DocBlock\Tag\TagInterface;
use Laminas\Code\Generic\Prototype\PrototypeClassFactory;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface as ReflectionTagInterface;
use ReflectionClass;
use ReflectionMethod;
use function method_exists;
use function str_starts_with;
use function strpos;
use function substr;
use function ucfirst;
/**
* This class is used in DocBlockGenerator and creates the needed
* Tag classes depending on the tag. So for example an @author tag
* will trigger the creation of an AuthorTag class.
*
* If none of the classes is applicable, the GenericTag class will be
* created
*/
class TagManager extends PrototypeClassFactory
{
/**
* @return void
*/
public function initializeDefaultTags()
{
$this->addPrototype(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;
}
}

View File

@@ -0,0 +1,263 @@
<?php
namespace Laminas\Code\Generator;
use Laminas\Code\Generator\DocBlock\Tag;
use Laminas\Code\Generator\DocBlock\Tag\TagInterface;
use Laminas\Code\Generator\DocBlock\TagManager;
use Laminas\Code\Reflection\DocBlockReflection;
use function explode;
use function is_array;
use function sprintf;
use function str_replace;
use function strtolower;
use function trim;
use function wordwrap;
class DocBlockGenerator extends AbstractGenerator
{
protected string $shortDescription = '';
protected string $longDescription = '';
protected array $tags = [];
protected string $indentation = '';
protected bool $wordwrap = true;
protected static ?TagManager $tagManager = null;
/**
* Build a DocBlock generator object from a reflection object
*
* @return DocBlockGenerator
*/
public static function fromReflection(DocBlockReflection $reflectionDocBlock)
{
$docBlock = new static();
$docBlock->setSourceContent($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;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Laminas\Code\Generator\EnumGenerator\Cases;
use InvalidArgumentException;
use function in_array;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class BackedCases
{
/**
* @param 'int'|'string' $type
* @param list<non-empty-string> $cases
*/
private function __construct(public readonly string $type, public readonly array $cases)
{
}
/**
* @param array<non-empty-string, int>|array<non-empty-string, string> $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);
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Laminas\Code\Generator\EnumGenerator\Cases;
use ReflectionEnum;
use ReflectionEnumBackedCase;
use ReflectionEnumUnitCase;
use ReflectionNamedType;
use function array_combine;
use function array_key_exists;
use function array_map;
use function assert;
/** @internal */
final class CaseFactory
{
/**
* @psalm-param array{
* name: non-empty-string,
* pureCases: list<non-empty-string>,
* }|array{
* name: non-empty-string,
* backedCases: array{
* type: 'int',
* cases: array<non-empty-string, int>,
* }|array{
* type: 'string',
* cases: array<non-empty-string, string>,
* },
* } $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()
);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Laminas\Code\Generator\EnumGenerator\Cases;
/**
* @internal
*
* @psalm-immutable
*/
final class PureCases
{
/** @param list<non-empty-string> $cases */
private function __construct(public readonly array $cases)
{
}
/**
* @param list<non-empty-string> $pureCases
*/
public static function fromCases(array $pureCases): self
{
return new self($pureCases);
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Laminas\Code\Generator\EnumGenerator;
use Laminas\Code\Generator\EnumGenerator\Cases\BackedCases;
use Laminas\Code\Generator\EnumGenerator\Cases\CaseFactory;
use Laminas\Code\Generator\EnumGenerator\Cases\PureCases;
use ReflectionEnum;
use function array_map;
use function implode;
/** @psalm-immutable */
final class EnumGenerator
{
/**
* Line feed to use in place of EOL
*/
private const LINE_FEED = "\n";
/**
* spaces of indentation by default
*/
private const INDENTATION = ' ';
/**
* @param BackedCases|PureCases $cases
*/
private function __construct(private readonly Name $name, private $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<non-empty-string>,
* }|array{
* name: non-empty-string,
* backedCases: array{
* type: 'int'|'string',
* cases: array<non-empty-string, int|string>,
* },
* } $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),
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Laminas\Code\Generator\EnumGenerator;
use function strrpos;
use function substr;
/**
* @internal
*
* @psalm-immutable
*/
final class Name
{
private function __construct(private readonly string $name, private readonly ?string $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);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Laminas\Code\Generator\Exception;
class ClassNotFoundException extends RuntimeException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Laminas\Code\Generator\Exception;
use Laminas\Code\Exception\ExceptionInterface as Exception;
interface ExceptionInterface extends Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Laminas\Code\Generator\Exception;
use Laminas\Code\Exception;
class InvalidArgumentException extends Exception\InvalidArgumentException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Laminas\Code\Generator\Exception;
use Laminas\Code\Exception;
class RuntimeException extends Exception\RuntimeException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,595 @@
<?php
namespace Laminas\Code\Generator;
use Laminas\Code\DeclareStatement;
use Laminas\Code\Exception\InvalidArgumentException;
use Laminas\Code\Generator\Exception\ClassNotFoundException;
use Traversable;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function count;
use function current;
use function dirname;
use function file_put_contents;
use function in_array;
use function is_array;
use function is_string;
use function is_writable;
use function method_exists;
use function preg_match;
use function preg_replace;
use function property_exists;
use function reset;
use function sprintf;
use function str_repeat;
use function str_replace;
use function strrpos;
use function strtolower;
use function substr;
use function token_get_all;
use const T_COMMENT;
use const T_DOC_COMMENT;
use const T_OPEN_TAG;
use const T_WHITESPACE;
/**
* @psalm-type InputUses = array<
* string|int,
* array{
* 'use': non-empty-string,
* 'as': non-empty-string|null
* }|array{
* non-empty-string,
* non-empty-string|null
* }|non-empty-string
* >
*/
class FileGenerator extends AbstractGenerator
{
protected string $filename = '';
protected ?DocBlockGenerator $docBlock = null;
/** @var string[] */
protected array $requiredFiles = [];
protected string $namespace = '';
/** @psalm-var list<array{non-empty-string, non-empty-string|null}> */
protected array $uses = [];
/**
* @var ClassGenerator[]
* @psalm-var array<string, ClassGenerator>
*/
protected array $classes = [];
protected string $body = '';
/**
* @var DeclareStatement[]
* @psalm-var array<string, DeclareStatement>
*/
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<int, array{string, null|string, false|null|string}>
*/
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 = '<?php' . self::LINE_FEED;
}
// if there are markers, put the body into the output
if (preg_match('#/\* Laminas_Code_Generator_Php_File-(.*?)Marker:#m', $body)) {
$tokens = token_get_all($body);
foreach ($tokens as $token) {
if (is_array($token) && in_array($token[0], [T_OPEN_TAG, T_COMMENT, T_DOC_COMMENT, T_WHITESPACE])) {
$output .= $token[1];
}
}
$body = '';
}
// Add file DocBlock, if any
if (null !== ($docBlock = $this->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;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Laminas\Code\Generator;
interface GeneratorInterface
{
/** @return string */
public function generate();
}

View File

@@ -0,0 +1,143 @@
<?php
namespace Laminas\Code\Generator;
use Laminas\Code\Reflection\ClassReflection;
use function sprintf;
use function str_replace;
use function strtolower;
class InterfaceGenerator extends ClassGenerator
{
public const OBJECT_TYPE = 'interface';
public const IMPLEMENTS_KEYWORD = 'extends';
/**
* Build a Code Generation Php Object from a Class Reflection
*
* @return static
*/
public static function fromReflection(ClassReflection $classReflection)
{
if (! $classReflection->isInterface()) {
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;
}
}

View File

@@ -0,0 +1,406 @@
<?php
namespace Laminas\Code\Generator;
use Laminas\Code\Reflection\MethodReflection;
use Stringable;
use function array_map;
use function explode;
use function implode;
use function is_array;
use function is_string;
use function preg_replace;
use function sprintf;
use function str_replace;
use function str_starts_with;
use function strlen;
use function strtolower;
use function substr;
use function trim;
use function uasort;
class MethodGenerator extends AbstractMemberGenerator implements Stringable
{
protected ?DocBlockGenerator $docBlock = null;
/** @var ParameterGenerator[] */
protected array $parameters = [];
protected string $body = '';
private ?TypeGenerator $returnType = null;
private bool $returnsReference = false;
/**
* @return MethodGenerator
*/
public static function fromReflection(MethodReflection $reflectionMethod)
{
$method = static::copyMethodSignature($reflectionMethod);
$method->setSourceContent($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 (str_starts_with($line, $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;
}
public function __toString(): string
{
return $this->generate();
}
}

View File

@@ -0,0 +1,326 @@
<?php
namespace Laminas\Code\Generator;
use Laminas\Code\Reflection\ParameterReflection;
use ReflectionException;
use function str_replace;
use function strtolower;
class ParameterGenerator extends AbstractGenerator
{
protected string $name = '';
protected ?TypeGenerator $type = null;
protected ?ValueGenerator $defaultValue = null;
protected int $position = 0;
protected bool $passedByReference = false;
private bool $variadic = false;
private bool $omitDefaultValue = false;
/**
* @return ParameterGenerator
*/
public static function fromReflection(ParameterReflection $reflectionParameter)
{
$param = new ParameterGenerator();
$param->setName($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) {
$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;
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Laminas\Code\Generator;
use Laminas\Code\Reflection\Exception\RuntimeException;
use Laminas\Code\Reflection\ParameterReflection;
use function sprintf;
final class PromotedParameterGenerator extends ParameterGenerator
{
public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_PROTECTED = 'protected';
public const VISIBILITY_PRIVATE = 'private';
/**
* @psalm-param non-empty-string $name
* @psalm-param ?non-empty-string $type
* @psalm-param PromotedParameterGenerator::VISIBILITY_* $visibility
*/
public function __construct(
string $name,
?string $type = null,
private readonly string $visibility = self::VISIBILITY_PUBLIC,
?int $position = null,
bool $passByReference = false
) {
parent::__construct(
$name,
$type,
null,
$position,
$passByReference,
);
}
/** @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()
);
}
}

View File

@@ -0,0 +1,341 @@
<?php
namespace Laminas\Code\Generator;
use Laminas\Code\Reflection\PropertyReflection;
use function array_reduce;
use function get_debug_type;
use function is_bool;
use function sprintf;
use function str_replace;
use function strtolower;
class PropertyGenerator extends AbstractMemberGenerator
{
public const FLAG_CONSTANT = 0x08;
public const FLAG_READONLY = 0x80;
protected bool $isConst = false;
protected ?PropertyValueGenerator $defaultValue = null;
private bool $omitDefaultValue = false;
/**
* @param PropertyValueGenerator|string|array|null $defaultValue
* @param int|int[] $flags
*/
public function __construct(
?string $name = null,
$defaultValue = null,
$flags = self::FLAG_PUBLIC,
protected ?TypeGenerator $type = null
) {
parent::__construct();
if (null !== $name) {
$this->setName($name);
}
if (null !== $defaultValue) {
$this->setDefaultValue($defaultValue);
}
if ($flags !== self::FLAG_PUBLIC) {
$this->setFlags($flags);
}
}
/** @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 fn(int $a, int $b): int => $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;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Laminas\Code\Generator;
class PropertyValueGenerator extends ValueGenerator
{
protected int $arrayDepth = 1;
/**
* @return string
*/
public function generate()
{
return parent::generate() . ';';
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace Laminas\Code\Generator;
use Laminas\Code\Reflection\ClassReflection;
use function str_replace;
use function strtolower;
class TraitGenerator extends ClassGenerator
{
public const OBJECT_TYPE = 'trait';
/**
* Build a Code Generation Php Object from a Class Reflection
*
* @return static
*/
public static function fromReflection(ClassReflection $classReflection)
{
// class generator
$cg = new static($classReflection->getName());
$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;
}
}

View File

@@ -0,0 +1,447 @@
<?php
namespace Laminas\Code\Generator;
use Reflection;
use ReflectionMethod;
use function array_key_exists;
use function array_search;
use function array_values;
use function count;
use function current;
use function explode;
use function implode;
use function in_array;
use function is_array;
use function is_string;
use function sprintf;
use function str_contains;
use function strpos;
/** @psalm-type Visibility = ReflectionMethod::IS_PRIVATE|ReflectionMethod::IS_PROTECTED|ReflectionMethod::IS_PUBLIC */
class TraitUsageGenerator extends AbstractGenerator implements TraitUsageInterface
{
/** @psalm-var array<int, string> 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<non-empty-string, non-empty-string> Array of string names */
protected array $uses = [];
public function __construct(protected 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;
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace Laminas\Code\Generator;
use ReflectionMethod;
interface TraitUsageInterface
{
/**
* Add a class to "use" classes
*
* @param non-empty-string $use
* @param non-empty-string|null $useAlias
* @return self
*/
public function addUse($use, $useAlias = null);
/**
* Returns the "use" classes
*
* @return list<non-empty-string>
*/
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<string|array{traitName: string, aliases?: array, insteadof?: array}> $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: <trait name>::<method name>
* 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: <trait name>::<method name>
* 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();
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace Laminas\Code\Generator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\TypeGenerator\AtomicType;
use Laminas\Code\Generator\TypeGenerator\CompositeType;
use Laminas\Code\Generator\TypeGenerator\IntersectionType;
use Laminas\Code\Generator\TypeGenerator\UnionType;
use ReflectionClass;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionUnionType;
use Stringable;
use function array_map;
use function sprintf;
use function str_contains;
use function str_starts_with;
use function substr;
/** @psalm-immutable */
final class TypeGenerator implements GeneratorInterface, Stringable
{
private const NULL_MARKER = '?';
private function __construct(
private readonly UnionType|IntersectionType|AtomicType $type,
private readonly bool $nullable = false
) {
if ($nullable && $type instanceof AtomicType) {
$type->assertCanBeStandaloneNullable();
}
}
/**
* @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->type !== '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];
}
}

View File

@@ -0,0 +1,217 @@
<?php
namespace Laminas\Code\Generator\TypeGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use ReflectionClass;
use ReflectionNamedType;
use function array_key_exists;
use function assert;
use function implode;
use function preg_match;
use function sprintf;
use function strtolower;
use function substr;
/**
* Represents a single/indivisible (atomic) type, as supported by PHP.
* This means that this object can be composed into more complex union, intersection
* and nullable types.
*
* @internal the {@see AtomicType} is an implementation detail of the type generator,
*
* @psalm-immutable
*/
final class AtomicType
{
/**
* Built-in type sorting, ascending.
*
* @psalm-var array<non-empty-string, positive-int>
*/
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<non-empty-string, null> */
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-param non-empty-string $type
* @psalm-param value-of<AtomicType::BUILT_IN_TYPES_PRECEDENCE>|0 $sortIndex
*/
private function __construct(
public string $type,
public int $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
));
}
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Laminas\Code\Generator\TypeGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use function array_map;
use function explode;
use function preg_match;
use function sprintf;
use function str_contains;
use function trim;
/**
* @internal the {@see CompositeType} is an implementation detail of the type generator,
*
* @psalm-immutable
* @final
*/
abstract class CompositeType
{
public const UNION_SEPARATOR = '|';
public const INTERSECTION_SEPARATOR = '&';
/** @psalm-pure */
public static function fromString(string $type): UnionType|IntersectionType|AtomicType
{
if (str_contains($type, self::UNION_SEPARATOR)) {
// This horrible regular expression verifies that union delimiters `|` are never contained
// in parentheses, and that all intersection `&` are contained in parentheses. It's simplistic,
// and it will crash with very large broken types, but that's sufficient for our **current**
// use-case.
// If this becomes more problematic, an actual parser is a better (although slower) alternative.
if (1 !== preg_match('/^(([|]|[^()&]+)+|(\(([&]|[^|()]+)\))+)+$/', $type)) {
throw new InvalidArgumentException(sprintf(
'Invalid type syntax "%s": intersections in a union must be surrounded by "(" and ")"',
$type
));
}
/** @var non-empty-list<IntersectionType|AtomicType> $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<AtomicType> $typesInIntersection */
$typesInIntersection = array_map(self::fromString(...), explode(self::INTERSECTION_SEPARATOR, $type));
return new IntersectionType($typesInIntersection);
}
return AtomicType::fromString($type);
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Laminas\Code\Generator\TypeGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use function array_diff_key;
use function array_flip;
use function array_map;
use function implode;
use function sprintf;
use function str_contains;
use function usort;
/**
* @internal the {@see IntersectionType} is an implementation detail of the type generator,
*
* @psalm-immutable
*/
final class IntersectionType
{
/** @var non-empty-list<AtomicType> sorted, at least 2 values always present */
private readonly array $types;
/**
* @param non-empty-list<AtomicType> $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
));
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Laminas\Code\Generator\TypeGenerator;
use function array_diff_key;
use function array_flip;
use function array_map;
use function implode;
use function usort;
/**
* @internal the {@see UnionType} is an implementation detail of the type generator,
*
* @psalm-immutable
*/
final class UnionType
{
/** @var non-empty-list<AtomicType|IntersectionType> $types sorted, at least 2 values always present */
private readonly array $types;
/** @param non-empty-list<AtomicType|IntersectionType> $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
)
);
}
}

View File

@@ -0,0 +1,508 @@
<?php
namespace Laminas\Code\Generator;
use ArrayObject as SplArrayObject;
use Laminas\Code\Exception\InvalidArgumentException;
use Laminas\Stdlib\ArrayObject as StdlibArrayObject;
use Stringable;
use UnitEnum;
use function addcslashes;
use function array_keys;
use function array_merge;
use function array_search;
use function count;
use function get_debug_type;
use function get_defined_constants;
use function gettype;
use function implode;
use function in_array;
use function is_array;
use function is_int;
use function is_object;
use function max;
use function sprintf;
use function str_contains;
use function str_repeat;
class ValueGenerator extends AbstractGenerator implements Stringable
{
/**#@+
* Constant values
*/
public const TYPE_AUTO = 'auto';
public const TYPE_BOOLEAN = 'boolean';
public const TYPE_BOOL = 'bool';
public const TYPE_NUMBER = 'number';
public const TYPE_INTEGER = 'integer';
public const TYPE_INT = 'int';
public const TYPE_FLOAT = 'float';
public const TYPE_DOUBLE = 'double';
public const TYPE_STRING = 'string';
public const TYPE_ARRAY = 'array';
public const TYPE_ARRAY_SHORT = 'array_short';
public const TYPE_ARRAY_LONG = 'array_long';
public const TYPE_CONSTANT = 'constant';
public const TYPE_NULL = 'null';
public const TYPE_ENUM = 'enum';
public const TYPE_OBJECT = 'object';
public const TYPE_OTHER = 'other';
/**#@-*/
public const OUTPUT_MULTIPLE_LINE = 'multipleLine';
public const OUTPUT_SINGLE_LINE = 'singleLine';
/** @var mixed */
protected $value;
protected string $type = self::TYPE_AUTO;
protected int $arrayDepth = 0;
/** @var self::OUTPUT_* */
protected string $outputMode = self::OUTPUT_MULTIPLE_LINE;
protected array $allowedTypes = [];
/**
* Autodetectable constants
*
* @var SplArrayObject|StdlibArrayObject
*/
protected $constants;
/**
* @param mixed $value
* @param string $type
* @param self::OUTPUT_* $outputMode
* @param null|SplArrayObject|StdlibArrayObject $constants
*/
public function __construct(
$value = null,
$type = self::TYPE_AUTO,
$outputMode = self::OUTPUT_MULTIPLE_LINE,
$constants = null
) {
// strict check is important here if $type = AUTO
if ($value !== null) {
$this->setValue($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;
}
public function __toString(): string
{
return $this->generate();
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Laminas\Code\Generic\Prototype;
use Laminas\Code\Reflection\Exception;
use function str_replace;
/**
* This is a factory for classes which are identified by name.
*
* All classes that this factory can supply need to
* be registered before (prototypes). This prototypes need to implement
* an interface which ensures every prototype has a name.
*
* If the factory can not supply the class someone is asking for
* it tries to fallback on a generic default prototype, which would
* have need to be set before.
*
* @internal this class is not part of the public API of this package
*/
class PrototypeClassFactory
{
/** @var array<string, PrototypeInterface> */
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];
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Laminas\Code\Generic\Prototype;
/** @internal this class is not part of the public API of this package */
interface PrototypeGenericInterface extends PrototypeInterface
{
/**
* @param string $name
* @return void
*/
public function setName($name);
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Laminas\Code\Generic\Prototype;
/** @internal this class is not part of the public API of this package */
interface PrototypeInterface
{
/**
* @return string
*/
public function getName();
}

View File

@@ -0,0 +1,209 @@
<?php
namespace Laminas\Code\Reflection;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use ReturnTypeWillChange;
use function array_map;
use function array_slice;
use function array_unshift;
use function file;
use function file_exists;
use function implode;
use function strstr;
/**
* @template TReflected of object
* @template-extends ReflectionClass<TReflected>
*/
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<class-string, ClassReflection>
*/
#[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<MethodReflection>
*/
#[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<trait-string, ClassReflection>
*/
#[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<PropertyReflection>
*/
#[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();
}
public function __toString(): string
{
return parent::__toString();
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use Stringable;
use function preg_match;
use function rtrim;
class AuthorTag implements TagInterface, Stringable
{
/** @var string|null */
protected $authorName;
/** @var string|null */
protected $authorEmail;
/** @return 'author' */
public function getName()
{
return 'author';
}
/** @inheritDoc */
public function initialize($content)
{
$match = [];
if (! preg_match('/^([^\<]*)(\<([^\>]*)\>)?(.*)$/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(): string
{
return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n";
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use Laminas\Code\Generic\Prototype\PrototypeGenericInterface;
use Stringable;
use function explode;
use function trim;
class GenericTag implements TagInterface, PrototypeGenericInterface, Stringable
{
/** @var string|null */
protected $name;
/** @var string|null */
protected $content;
/** @var list<string> */
protected $values = [];
/**
* @param string $contentSplitCharacter
*/
public function __construct(protected $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(): string
{
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);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use Stringable;
use function preg_match;
use function trim;
class LicenseTag implements TagInterface, Stringable
{
/** @var string|null */
protected $url;
/** @var string|null */
protected $licenseName;
/** @return 'license' */
public function getName()
{
return 'license';
}
/** @inheritDoc */
public function initialize($content)
{
$match = [];
if (! preg_match('#^([\S]*)(?:\s+(.*))?$#m', $content, $match)) {
return;
}
if ($match[1] !== '') {
$this->url = 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(): string
{
return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n";
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use Stringable;
use function explode;
use function preg_match;
use function rtrim;
class MethodTag implements TagInterface, PhpDocTypedTagInterface, Stringable
{
/** @var list<string> */
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(): string
{
return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n";
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use function explode;
use function preg_match;
use function preg_replace;
use function trim;
class ParamTag implements TagInterface, PhpDocTypedTagInterface
{
/** @var list<string> */
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;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
interface PhpDocTypedTagInterface
{
/**
* Return all types supported by the tag definition
*
* @return list<string>
*/
public function getTypes();
}

View File

@@ -0,0 +1,94 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use Stringable;
use function explode;
use function preg_match;
use function rtrim;
class PropertyTag implements TagInterface, PhpDocTypedTagInterface, Stringable
{
/** @var list<string> */
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;
}
/**
* @psalm-return non-empty-string
*/
public function __toString(): string
{
return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . "\n";
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use function explode;
use function preg_match;
use function preg_replace;
use function trim;
class ReturnTag implements TagInterface, PhpDocTypedTagInterface
{
/** @var list<string> */
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;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use Laminas\Code\Generic\Prototype\PrototypeInterface;
interface TagInterface extends PrototypeInterface
{
/**
* @param string $content
* @return void
*/
public function initialize($content);
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use function explode;
use function implode;
use function preg_match;
class ThrowsTag implements TagInterface, PhpDocTypedTagInterface
{
/**
* @var string[]
* @psalm-var list<string>
*/
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;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Laminas\Code\Reflection\DocBlock\Tag;
use Stringable;
use function explode;
use function preg_match;
use function rtrim;
use const PHP_EOL;
class VarTag implements TagInterface, PhpDocTypedTagInterface, Stringable
{
/**
* @var string[]
* @psalm-var list<string>
*/
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;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Laminas\Code\Reflection\DocBlock;
use Laminas\Code\Generic\Prototype\PrototypeClassFactory;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface;
class TagManager extends PrototypeClassFactory
{
/**
* @return void
*/
public function initializeDefaultTags()
{
$this->addPrototype(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;
}
}

View File

@@ -0,0 +1,278 @@
<?php
namespace Laminas\Code\Reflection;
use Laminas\Code\Reflection\DocBlock\Tag\TagInterface as DocBlockTagInterface;
use Laminas\Code\Reflection\DocBlock\TagManager as DocBlockTagManager;
use Laminas\Code\Scanner\DocBlockScanner;
use Reflector;
use function count;
use function is_string;
use function ltrim;
use function method_exists;
use function preg_replace;
use function sprintf;
use function substr_count;
class DocBlockReflection implements ReflectionInterface
{
/** @var Reflector */
protected $reflector;
/** @var string */
protected $docComment;
/** @var DocBlockTagManager */
protected $tagManager;
/** @var int */
protected $startLine;
/** @var int */
protected $endLine;
/** @var string */
protected $cleanDocComment;
/** @var string */
protected $longDescription;
/** @var string */
protected $shortDescription;
/** @var array */
protected $tags = [];
/** @var bool */
protected $isReflected = false;
/**
* Export reflection
*
* Required by the Reflector interface.
*
* @todo What should this do?
* @return void
*/
public static function export()
{
}
/**
* @param Reflector|string $commentOrReflector
* @throws Exception\InvalidArgumentException
*/
public function __construct($commentOrReflector, ?DocBlockTagManager $tagManager = null)
{
if (! $tagManager) {
$tagManager = new DocBlockTagManager();
$tagManager->initializeDefaultTags();
}
$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
*/
public function __toString(): string
{
return $this->toString();
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Laminas\Code\Reflection\Exception;
use Laminas\Code\Exception;
class BadMethodCallException extends Exception\BadMethodCallException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Laminas\Code\Reflection\Exception;
use Laminas\Code\Exception\ExceptionInterface as Exception;
interface ExceptionInterface extends Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Laminas\Code\Reflection\Exception;
use Laminas\Code\Exception;
class InvalidArgumentException extends Exception\InvalidArgumentException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Laminas\Code\Reflection\Exception;
use Laminas\Code\Exception;
class RuntimeException extends Exception\RuntimeException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,289 @@
<?php
namespace Laminas\Code\Reflection;
use ReflectionFunction;
use ReflectionParameter;
use ReturnTypeWillChange;
use function array_map;
use function array_slice;
use function count;
use function file;
use function implode;
use function preg_match;
use function preg_quote;
use function preg_replace;
use function sprintf;
use function strlen;
use function strrpos;
use function substr;
use function var_export;
use const FILE_IGNORE_NEW_LINES;
class FunctionReflection extends ReflectionFunction implements ReflectionInterface
{
/**
* Constant use in @MethodReflection to display prototype as an array
*/
public const PROTOTYPE_AS_ARRAY = 'prototype_as_array';
/**
* Constant use in @MethodReflection to display prototype as a string
*/
public const PROTOTYPE_AS_STRING = 'prototype_as_string';
/**
* Get function DocBlock
*
* @throws Exception\InvalidArgumentException
* @return DocBlockReflection
*/
public function getDocBlock()
{
if ('' == ($comment = $this->getDocComment())) {
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<ParameterReflection>
*/
#[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
*
* @psalm-external-mutation-free
*/
public function __toString(): string
{
return parent::__toString();
}
}

View File

@@ -0,0 +1,482 @@
<?php
namespace Laminas\Code\Reflection;
use ReflectionMethod as PhpReflectionMethod;
use ReflectionParameter as PhpReflectionParameter;
use ReturnTypeWillChange;
use function array_key_exists;
use function array_map;
use function array_shift;
use function array_slice;
use function class_exists;
use function count;
use function file;
use function file_exists;
use function implode;
use function is_array;
use function rtrim;
use function strlen;
use function substr;
use function token_get_all;
use function token_name;
use function var_export;
use const FILE_IGNORE_NEW_LINES;
class MethodReflection extends PhpReflectionMethod implements ReflectionInterface
{
/**
* Constant use in @MethodReflection to display prototype as an array
*/
public const PROTOTYPE_AS_ARRAY = 'prototype_as_array';
/**
* Constant use in @MethodReflection to display prototype as a string
*/
public const PROTOTYPE_AS_STRING = 'prototype_as_string';
/**
* Retrieve method DocBlock reflection
*
* @return DocBlockReflection|false
*/
public function getDocBlock()
{
if ('' == $this->getDocComment()) {
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<ParameterReflection>
*/
#[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('<?php ' . $functionLine);
//remove first entry which is php open tag
array_shift($tokens);
if (! count($tokens)) {
return '';
}
$capture = false;
$firstBrace = false;
$body = '';
foreach ($tokens as $key => $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();
}
public function __toString(): string
{
return parent::__toString();
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Laminas\Code\Reflection;
use Laminas\Code\Reflection\DocBlock\Tag\ParamTag;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionProperty;
use ReturnTypeWillChange;
use function assert;
/** @psalm-immutable */
class ParameterReflection extends ReflectionParameter implements ReflectionInterface
{
/** @var bool */
protected $isFromMethod = false;
/**
* Get declaring class reflection object
*
* @return ClassReflection|null
*/
#[ReturnTypeWillChange]
public function getDeclaringClass()
{
$reflection = parent::getDeclaringClass();
if (! $reflection) {
return null;
}
return new ClassReflection($reflection->getName());
}
/**
* 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());
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Laminas\Code\Reflection;
use ReflectionProperty as PhpReflectionProperty;
use ReturnTypeWillChange;
/**
* @todo implement line numbers
*/
class PropertyReflection extends PhpReflectionProperty implements ReflectionInterface
{
/**
* Get declaring class reflection object
*
* @return ClassReflection
*/
#[ReturnTypeWillChange]
public function getDeclaringClass()
{
$phpReflection = parent::getDeclaringClass();
$laminasReflection = new ClassReflection($phpReflection->getName());
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();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Laminas\Code\Reflection;
use Reflector;
/** @internal this class is not part of the public API of this package */
interface ReflectionInterface extends Reflector
{
/**
* @return string
*/
public function toString();
}

View File

@@ -0,0 +1,307 @@
<?php
namespace Laminas\Code\Scanner;
use function array_key_last;
use function array_pop;
use function array_push;
use function current;
use function end;
use function key;
use function next;
use function preg_match;
use function reset;
use function strlen;
use function strpos;
use function substr;
use function trim;
/** @internal this class is not part of the public API of this package */
class DocBlockScanner
{
/** @var bool */
protected $isScanned = false;
/** @var string */
protected $shortDescription = '';
/** @var string */
protected $longDescription = '';
/** @var array */
protected $tags = [];
/**
* @param string $docComment
*/
public function __construct(protected $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' => '',
]);
$tagIndex = array_key_last($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;
}
}