Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for constant expression typehints #65

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Constraint/AccessorPairConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ protected function getTestValues(ReflectionMethod $method, ReflectionParameter $
}
}

return $this->valueProviderFactory->getProvider($typehint)->getValues();
return $this->valueProviderFactory->getProvider($typehint, $method)->getValues();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Constraint/Typehint/PhpDocParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public function getReturnTypehint(string $originalDocComment): ?string
return $this->normalizeDocblock($matches[1]);
}

preg_match('/\*\s*@return\s+(.*?)(?:\s+|\*)/', $docComment, $matches);
preg_match('/\*\s*@return\s+(.*?)(?:\s+|\*\/)/', $docComment, $matches);
if (isset($matches[1])) {
return $this->normalizeDocblock($matches[1]);
}
Expand Down
11 changes: 6 additions & 5 deletions src/Constraint/ValueProvider/NativeValueProviderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use phpDocumentor\Reflection\Types\String_;
use phpDocumentor\Reflection\PseudoTypes\False_;
use phpDocumentor\Reflection\PseudoTypes\True_;
use ReflectionMethod;

/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
Expand All @@ -48,9 +49,9 @@ public function __construct(ValueProviderFactory $valueProviderFactory)
/**
* @throws LogicException
*/
public function getProvider(Type $typehint): ?ValueProvider
public function getProvider(Type $typehint, ?ReflectionMethod $method = null): ?ValueProvider
{
$provider = $this->getCompoundProvider($typehint);
$provider = $this->getCompoundProvider($typehint, $method);
if ($provider !== null) {
return $provider;
}
Expand All @@ -73,13 +74,13 @@ public function getProvider(Type $typehint): ?ValueProvider
return null;
}

protected function getCompoundProvider(Type $typehint): ?ValueProvider
protected function getCompoundProvider(Type $typehint, ?ReflectionMethod $method): ?ValueProvider
{
switch (get_class($typehint)) {
case Array_::class:
return new ArrayProvider(
$this->valueProviderFactory->getProvider($typehint->getValueType()),
$this->valueProviderFactory->getProvider($typehint->getKeyType())
$this->valueProviderFactory->getProvider($typehint->getValueType(), $method),
$this->valueProviderFactory->getProvider($typehint->getKeyType(), $method)
);
case ArrayShape::class:
$items = $typehint->getItems();
Expand Down
54 changes: 54 additions & 0 deletions src/Constraint/ValueProvider/Pseudo/ConstExpressionProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);

namespace DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo;

use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\ValueProvider;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\Self_;
use ReflectionClass;
use ReflectionMethod;
use RuntimeException;

class ConstExpressionProvider implements ValueProvider
{
protected Type $owner;
protected string $expression;
protected ?ReflectionMethod $method;

public function __construct(Type $owner, string $expression, ?ReflectionMethod $method)
{
$this->owner = $owner;
$this->expression = $expression;
$this->method = $method;
}

/**
* @inheritDoc
*/
public function getValues(): array
{
if ($this->owner instanceof Object_ && $this->owner->getFqsen() !== null) {
/** @var class-string $fqsen */
$fqsen = (string)$this->owner->getFqsen();

$constClass = new ReflectionClass($fqsen);
} elseif ($this->owner instanceof Self_ && $this->method !== null) {
$constClass = $this->method->getDeclaringClass();
} else {
throw new RuntimeException('ConstExpressionProvider can only be used with object or self typehints');
}

$constants = $constClass->getConstants();
$expression = str_replace('*', '.*', $this->expression);
$values = [];
foreach ($constants as $constant => $value) {
if (preg_match('/^' . $expression . '$/', $constant) === 1) {
$values[] = $value;
}
}

return $values;
}
}
11 changes: 8 additions & 3 deletions src/Constraint/ValueProvider/PseudoValueProviderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\CallableStringProvider;
use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\ClassStringProvider;
use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\ConstExpressionProvider;
use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\DirectValueProvider;
use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\HtmlEscapedStringProvider;
use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\ListProvider;
Expand All @@ -18,6 +19,7 @@
use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Scalar\StringProvider;
use LogicException;
use phpDocumentor\Reflection\PseudoTypes\CallableString;
use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
use phpDocumentor\Reflection\PseudoTypes\FloatValue;
use phpDocumentor\Reflection\PseudoTypes\HtmlEscapedString;
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
Expand All @@ -37,6 +39,7 @@
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\ArrayKey;
use phpDocumentor\Reflection\Types\ClassString;
use ReflectionMethod;

/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
Expand All @@ -53,23 +56,25 @@ public function __construct(ValueProviderFactory $valueProviderFactory)
/**
* @throws LogicException
*/
public function getProvider(Type $typehint): ?ValueProvider
public function getProvider(Type $typehint, ?ReflectionMethod $method = null): ?ValueProvider
{
switch (get_class($typehint)) {
case ArrayKey::class:
return new ValueProviderList(new StringProvider(), new IntProvider());
case IntegerRange::class:
return new IntProvider((int)$typehint->getMinValue(), (int)$typehint->getMaxValue());
case List_::class:
return new ListProvider($this->valueProviderFactory->getProvider($typehint->getValueType()));
return new ListProvider($this->valueProviderFactory->getProvider($typehint->getValueType(), $method));
case NonEmptyList::class:
return new NonEmptyValueProvider(new ListProvider($this->valueProviderFactory->getProvider($typehint->getValueType())));
return new NonEmptyValueProvider(new ListProvider($this->valueProviderFactory->getProvider($typehint->getValueType(), $method)));
case NegativeInteger::class:
return new IntProvider(PHP_INT_MIN, -1);
case Numeric_::class:
return new ValueProviderList(new NumericStringProvider(), new IntProvider(), new FloatProvider(new IntProvider()));
case PositiveInteger::class:
return new IntProvider(1, PHP_INT_MAX);
case ConstExpression::class:
return new ConstExpressionProvider($typehint->getOwner(), $typehint->getExpression(), $method);
}

return $this->getPseudoStringProvider($typehint);
Expand Down
15 changes: 8 additions & 7 deletions src/Constraint/ValueProvider/ValueProviderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use phpDocumentor\Reflection\Types\Intersection;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;
use ReflectionMethod;

/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
Expand All @@ -33,7 +34,7 @@ public function __construct()
*
* @throws LogicException
*/
public function getProvider(Type $typehint): ValueProvider
public function getProvider(Type $typehint, ?ReflectionMethod $method = null): ValueProvider
{
// Support intersection typehints, such as "Iterator&Countable"
if ($typehint instanceof Intersection) {
Expand All @@ -42,12 +43,12 @@ public function getProvider(Type $typehint): ValueProvider

// Support union typehints, such as "string|null"
if ($typehint instanceof Compound) {
return new ValueProviderList(...$this->getProviders(iterator_to_array($typehint)));
return new ValueProviderList(...$this->getProviders(iterator_to_array($typehint), $method));
}

// Support nullable typehints, such as "?string". Adds a NullProvider to the regular typehint's ValueProvider.
if ($typehint instanceof Nullable) {
return new ValueProviderList(new NullProvider(), $this->getProvider($typehint->getActualType()));
return new ValueProviderList(new NullProvider(), $this->getProvider($typehint->getActualType(), $method));
}

// Support for fully namespaced class name
Expand All @@ -56,13 +57,13 @@ public function getProvider(Type $typehint): ValueProvider
}

// Check if the provider typehint is a PHP scalar type
$nativeProvider = $this->nativeProviderFactory->getProvider($typehint);
$nativeProvider = $this->nativeProviderFactory->getProvider($typehint, $method);
if ($nativeProvider !== null) {
return $nativeProvider;
}

// Check if the provider typehint is a PHP pseudoType
$pseudoProvider = $this->pseudoProviderFactory->getProvider($typehint);
$pseudoProvider = $this->pseudoProviderFactory->getProvider($typehint, $method);
if ($pseudoProvider !== null) {
return $pseudoProvider;
}
Expand All @@ -76,11 +77,11 @@ public function getProvider(Type $typehint): ValueProvider
* @return ValueProvider[]
* @throws LogicException
*/
protected function getProviders(array $typehints): array
protected function getProviders(array $typehints, ?ReflectionMethod $method): array
{
$providers = [];
foreach ($typehints as $typehint) {
$providers[] = $this->getProvider($typehint);
$providers[] = $this->getProvider($typehint, $method);
}

return $providers;
Expand Down
28 changes: 28 additions & 0 deletions tests/Integration/data/success/Regular/ConstOtherProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);

namespace DigitalRevolution\AccessorPairConstraint\Tests\Integration\data\success\Regular;

class ConstOtherProperty
{
/** @var ConstSelfProperty::CONSTANT_* */
private string $property;

/**
* @return ConstSelfProperty::CONSTANT_*
*/
public function getProperty(): string
{
return $this->property;
}

/**
* @param ConstSelfProperty::CONSTANT_* $property
*/
public function setProperty(string $property): self
{
$this->property = $property;

return $this;
}
}
31 changes: 31 additions & 0 deletions tests/Integration/data/success/Regular/ConstSelfProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);

namespace DigitalRevolution\AccessorPairConstraint\Tests\Integration\data\success\Regular;

class ConstSelfProperty
{
public const CONSTANT_1 = 'value1';
public const CONSTANT_2 = 'value2';

/** @var self::CONSTANT_* */
private string $property;

/**
* @return self::CONSTANT_*
*/
public function getProperty(): string
{
return $this->property;
}

/**
* @param self::CONSTANT_* $property
*/
public function setProperty(string $property): self
{
$this->property = $property;

return $this;
}
}
4 changes: 4 additions & 0 deletions tests/Unit/Constraint/Typehint/PhpDocParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ public static function returnTypehintProvider(): Generator
yield ['/** @return array */', 'array'];
yield ['/**@return array*/', 'array'];
yield ["/**\n *@return array\n */", 'array'];

// Constant expressions
yield ['/** @return self::CONSTANT_* */', 'self::CONSTANT_*'];
yield ['/** @return SomeClass::CONSTANT_* */', 'SomeClass::CONSTANT_*'];
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace DigitalRevolution\AccessorPairConstraint\Tests\Unit\Constraint\ValueProvider\Pseudo;

use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\ConstExpressionProvider;
use DigitalRevolution\AccessorPairConstraint\Tests\Unit\Constraint\ValueProvider\AbstractValueProviderTestCase;
use DigitalRevolution\AccessorPairConstraint\Tests\Unit\Constraint\ValueProvider\Pseudo\data\ClassWithConsts;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Types\Callable_;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\Self_;
use ReflectionMethod;

/**
* @coversDefaultClass \DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Pseudo\ConstExpressionProvider
* @covers ::__construct
*/
class ConstExpressionProviderTest extends AbstractValueProviderTestCase
{
/**
* @covers ::getValues
*/
public function testGetValues(): void
{
$valueProvider = new ConstExpressionProvider(new Object_(new Fqsen('\\' . ClassWithConsts::class)), 'CONST_*', null);
$values = $valueProvider->getValues();

static::assertValueTypes($values, ['string']);
static::assertContains('CONST_A', $values);
static::assertContains('CONST_B', $values);
static::assertNotContains('CONSTANT_A', $values);
}

/**
* @covers ::getValues
*/
public function testGetValuesSelf(): void
{
$valueProvider = new ConstExpressionProvider(new Self_(), 'CONST_*', new ReflectionMethod(ClassWithConsts::class, 'setConst'));
$values = $valueProvider->getValues();

static::assertValueTypes($values, ['string']);
static::assertContains('CONST_A', $values);
static::assertContains('CONST_B', $values);
static::assertNotContains('CONSTANT_A', $values);
}

/**
* @covers ::getValues
*/
public function testGetValuesInvalidType(): void
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('ConstExpressionProvider can only be used with object or self typehints');

$valueProvider = new ConstExpressionProvider(new Callable_([]), 'CONST_*', null);
$valueProvider->getValues();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace DigitalRevolution\AccessorPairConstraint\Tests\Unit\Constraint\ValueProvider\Pseudo\data;

class ClassWithConsts
{
public const CONST_A = 'CONST_A';
public const CONST_B = 'CONST_B';
public const CONSTANT_A = 'CONSTANT_A';

/** @var string */
private string $const;

/**
* @param self::CONST_* $const
*/
public function setConst(string $const): self
{
$this->const = $const;

return $this;
}

/**
* @return self::CONST_*
*/
public function getConst(): string
{
return $this->const;
}
}
Loading