Skip to content

Commit

Permalink
Added support for promoted parameters
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Petri <[email protected]>
  • Loading branch information
michaelpetri committed Nov 24, 2021
1 parent 4746c4d commit 54c1b6b
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 16 deletions.
44 changes: 32 additions & 12 deletions src/Generator/ClassGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laminas\Code\Generator;

use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Reflection\ClassReflection;

use function array_diff;
Expand Down Expand Up @@ -33,6 +34,7 @@ class ClassGenerator extends AbstractGenerator implements TraitUsageInterface
public const IMPLEMENTS_KEYWORD = 'implements';
public const FLAG_ABSTRACT = 0x01;
public const FLAG_FINAL = 0x02;
private const CONSTRUCTOR_NAME = '__construct';

protected ?FileGenerator $containingFileGenerator = null;

Expand Down Expand Up @@ -140,7 +142,17 @@ public static function fromReflection(ClassReflection $classReflection)
}

if ($reflectionMethod->getDeclaringClass()->getName() == $className) {
$methods[] = MethodGenerator::fromReflection($reflectionMethod);
$method = MethodGenerator::fromReflection($reflectionMethod);

if (self::CONSTRUCTOR_NAME === $method->getName()) {
foreach ($method->getParameters() as $parameter) {
if ($parameter instanceof PromotedParameterGenerator) {
$cg->removeProperty($parameter->getName());
}
}
}

$methods[] = $method;
}
}

Expand All @@ -161,14 +173,14 @@ public static function fromReflection(ClassReflection $classReflection)
* @configkey implementedinterfaces
* @configkey properties
* @configkey methods
* @throws Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @param array $array
* @return static
*/
public static function fromArray(array $array)
{
if (! isset($array['name'])) {
throw new Exception\InvalidArgumentException(
throw new InvalidArgumentException(
'Class generator requires that a name is provided for this object'
);
}
Expand Down Expand Up @@ -547,14 +559,14 @@ public function addConstantFromGenerator(PropertyGenerator $constant)
$constantName = $constant->getName();

if (isset($this->constants[$constantName])) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'A constant by name %s already exists in this class.',
$constantName
));
}

if (! $constant->isConst()) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'The value %s is not defined as a constant.',
$constantName
));
Expand All @@ -576,7 +588,7 @@ public function addConstantFromGenerator(PropertyGenerator $constant)
public function addConstant($name, $value, bool $isFinal = false)
{
if (empty($name) || ! is_string($name)) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'%s expects string for name',
__METHOD__
));
Expand Down Expand Up @@ -645,7 +657,7 @@ public function addProperties(array $properties)
public function addProperty($name, $defaultValue = null, $flags = PropertyGenerator::FLAG_PUBLIC)
{
if (! is_string($name)) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'%s::%s expects string for name',
static::class,
__FUNCTION__
Expand All @@ -672,7 +684,7 @@ public function addPropertyFromGenerator(PropertyGenerator $property)
$propertyName = $property->getName();

if (isset($this->properties[$propertyName])) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'A property by name %s already exists in this class.',
$propertyName
));
Expand Down Expand Up @@ -830,7 +842,7 @@ public function addMethod(
$docBlock = null
) {
if (! is_string($name)) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'%s::%s expects string for name',
static::class,
__FUNCTION__
Expand All @@ -851,12 +863,20 @@ public function addMethodFromGenerator(MethodGenerator $method)
$methodName = $method->getName();

if ($this->hasMethod($methodName)) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'A method by name %s already exists in this class.',
$methodName
));
}

if (self::CONSTRUCTOR_NAME !== $methodName) {
foreach ($method->getParameters() as $parameter) {
if ($parameter instanceof PromotedParameterGenerator) {
throw new InvalidArgumentException('Promoted parameter can only be added to constructor.');
}
}
}

$this->methods[strtolower($methodName)] = $method;
return $this;
}
Expand Down Expand Up @@ -1102,7 +1122,7 @@ public function generate()
/**
* @param mixed $value
* @return void
* @throws Exception\InvalidArgumentException
* @throws InvalidArgumentException
*/
private function validateConstantValue($value)
{
Expand All @@ -1116,7 +1136,7 @@ private function validateConstantValue($value)
return;
}

throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'Expected value for constant, value must be a "scalar" or "null", "%s" found',
gettype($value)
));
Expand Down
7 changes: 6 additions & 1 deletion src/Generator/MethodGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use function substr;
use function trim;
use function uasort;
use function var_dump;

class MethodGenerator extends AbstractMemberGenerator
{
Expand Down Expand Up @@ -77,7 +78,11 @@ public static function copyMethodSignature(MethodReflection $reflectionMethod):
$method->setName($reflectionMethod->getName());

foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
$method->setParameter(ParameterGenerator::fromReflection($reflectionParameter));
$method->setParameter(
$reflectionParameter->isPromoted()
? PromotedParameterGenerator::fromReflection($reflectionParameter)
: ParameterGenerator::fromReflection($reflectionParameter)
);
}

return $method;
Expand Down
98 changes: 98 additions & 0 deletions src/Generator/PromotedParameterGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?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-var PromotedParameterGenerator::VISIBILITY_* */
private string $visibility;

/**
* @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,
string $visibility = self::VISIBILITY_PUBLIC,
?int $position = null,
bool $passByReference = false
) {
parent::__construct(
$name,
$type,
null,
$position,
$passByReference,
);

$this->visibility = $visibility;
}

/** @psalm-return non-empty-string */
public function generate(): string
{
return $this->visibility . ' ' . parent::generate();
}

public static function fromReflection(ParameterReflection $reflectionParameter): self
{
if (! $reflectionParameter->isPromoted()) {
throw new RuntimeException(
sprintf('Can not create "%s" from unprompted reflection.', self::class)
);
}

$visibility = self::VISIBILITY_PUBLIC;

if ($reflectionParameter->isProtectedPromoted()) {
$visibility = self::VISIBILITY_PROTECTED;
} elseif ($reflectionParameter->isPrivatePromoted()) {
$visibility = self::VISIBILITY_PRIVATE;
}

return self::fromParameterGeneratorWithVisibility(
parent::fromReflection($reflectionParameter),
$visibility
);
}

/** @psalm-param PromotedParameterGenerator::VISIBILITY_* $visibility */
public static function fromParameterGeneratorWithVisibility(ParameterGenerator $generator, string $visibility): self
{
$name = $generator->getName();
$type = $generator->getType();

if ('' === $name) {
throw new \Laminas\Code\Generator\Exception\RuntimeException(
'Name of promoted parameter must be non-empty-string.'
);
}

if ('' === $type) {
throw new \Laminas\Code\Generator\Exception\RuntimeException(
'Type of promoted parameter must be non-empty-string.'
);
}

return new self(
$name,
$type,
$visibility,
$generator->getPosition(),
$generator->getPassedByReference()
);
}
}
29 changes: 26 additions & 3 deletions src/Reflection/MethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ReflectionMethod as PhpReflectionMethod;
use ReturnTypeWillChange;

use function array_key_exists;
use function array_shift;
use function array_slice;
use function class_exists;
Expand Down Expand Up @@ -116,15 +117,37 @@ public function getPrototype($format = self::PROTOTYPE_AS_ARRAY)
'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 = ($argument['type'] ?
$argument['type'] . ' '
: '') . ($argument['by_ref'] ? '&' : '') . '$' . $name;
$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);
}
Expand Down
38 changes: 38 additions & 0 deletions src/Reflection/ParameterReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use ReturnTypeWillChange;

use function method_exists;
Expand Down Expand Up @@ -130,4 +131,41 @@ public function __toString()
{
return parent::__toString();
}

/** @psalm-pure */
public function isPromoted(): bool
{
if (! method_exists(parent::class, 'isPromoted')) {
return false;
}

return (bool) parent::isPromoted();
}

public function isPublicPromoted(): bool
{
return $this->isPromoted()
&& $this->getDeclaringClass()
->getProperty($this->getName())
->getModifiers()
& ReflectionProperty::IS_PUBLIC;
}

public function isProtectedPromoted(): bool
{
return $this->isPromoted()
&& $this->getDeclaringClass()
->getProperty($this->getName())
->getModifiers()
& ReflectionProperty::IS_PROTECTED;
}

public function isPrivatePromoted(): bool
{
return $this->isPromoted()
&& $this->getDeclaringClass()
->getProperty($this->getName())
->getModifiers()
& ReflectionProperty::IS_PRIVATE;
}
}
Loading

0 comments on commit 54c1b6b

Please sign in to comment.