Skip to content

Commit

Permalink
Merge pull request #212 from apple-x-co/enum-in-attribute-args
Browse files Browse the repository at this point in the history
Support enum in attribute arguments
  • Loading branch information
koriym authored May 20, 2024
2 parents 284cec0 + 1d15b6d commit 41e5af6
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 42 deletions.
102 changes: 62 additions & 40 deletions src/MethodSignatureString.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Reflection;
use ReflectionMethod;
use ReflectionParameter;
use UnitEnum;

use function implode;
use function is_numeric;
Expand All @@ -19,87 +20,108 @@
use const PHP_EOL;
use const PHP_MAJOR_VERSION;

/** @SuppressWarnings(PHPMD.CyclomaticComplexity) */
final class MethodSignatureString
{
private const PHP_VERSION_8 = 80000;
private const NULLABLE_PHP8 = 'null|';
private const NULLABLE_PHP7 = '?';
private const INDENT = ' ';

/** @var TypeString */
private $typeString;

public function __construct(int $phpVersion)
{
$nullableStr = $phpVersion >= 80000 ? 'null|' : '?';
$nullableStr = $phpVersion >= self::PHP_VERSION_8 ? self::NULLABLE_PHP8 : self::NULLABLE_PHP7;
$this->typeString = new TypeString($nullableStr);
}

/**
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function get(ReflectionMethod $method): string
{
$signatureParts = [];
$signatureParts = $this->getDocComment($method);
$this->addAttributes($method, $signatureParts);
$this->addAccessModifiers($method, $signatureParts);
$this->addMethodSignature($method, $signatureParts);

// PHPDocを取得
return implode(' ', $signatureParts);
}

/** @return array<string> */
private function getDocComment(ReflectionMethod $method): array
{
$docComment = $method->getDocComment();
if (is_string($docComment)) {
$signatureParts[] = $docComment . PHP_EOL;
}

// アトリビュートを取得 (PHP 8.0+ の場合のみ)
if (PHP_MAJOR_VERSION >= 8) {
/** @psalm-suppress MixedAssignment */
foreach ($method->getAttributes() as $attribute) {
$argsList = $attribute->getArguments();
$formattedArgs = [];
return is_string($docComment) ? [$docComment . PHP_EOL] : [];
}

foreach ($argsList as $name => $value) {
$formattedValue = preg_replace('/\s+/', ' ', var_export($value, true));
$argRepresentation = is_numeric($name) ? $formattedValue : "{$name}: {$formattedValue}";
$formattedArgs[] = $argRepresentation;
}
/** @param array<string> $signatureParts */
private function addAttributes(ReflectionMethod $method, array &$signatureParts): void
{
if (PHP_MAJOR_VERSION < 8) {
return;
}

$signatureParts[] = sprintf(' #[\\%s(%s)]', $attribute->getName(), implode(', ', $formattedArgs)) . PHP_EOL;
$attributes = $method->getAttributes();
foreach ($attributes as $attribute) {
$argsList = $attribute->getArguments();
$formattedArgs = [];
/** @var mixed $value */
foreach ($argsList as $name => $value) {
$formattedArgs[] = $this->formatArg($name, $value);
}

$signatureParts[] = sprintf(' #[\\%s(%s)]', $attribute->getName(), implode(', ', $formattedArgs)) . PHP_EOL;
}

if ($signatureParts) {
$signatureParts[] = ' '; // インデント追加
if (empty($signatureParts)) {
return;
}

// アクセス修飾子を取得
$signatureParts[] = self::INDENT;
}

/** @param array<string> $signatureParts */
private function addAccessModifiers(ReflectionMethod $method, array &$signatureParts): void
{
$modifier = implode(' ', Reflection::getModifierNames($method->getModifiers()));

$signatureParts[] = $modifier;
}

// メソッド名とパラメータを取得
/** @param array<string> $signatureParts */
private function addMethodSignature(ReflectionMethod $method, array &$signatureParts): void
{
$params = [];
foreach ($method->getParameters() as $param) {
$params[] = $this->generateParameterCode($param);
}

$returnType = '';
$parmsList = implode(', ', $params);
$rType = $method->getReturnType();
if ($rType) {
$returnType = ': ' . ($this->typeString)($rType);
}
$return = $rType ? ': ' . ($this->typeString)($rType) : '';

$parmsList = implode(', ', $params);
$signatureParts[] = sprintf('function %s(%s)%s', $method->getName(), $parmsList, $return);
}

$signatureParts[] = sprintf('function %s(%s)%s', $method->getName(), $parmsList, $returnType);
/**
* @param string|int $name
* @param mixed $value
*/
private function formatArg($name, $value): string
{
$formattedValue = $value instanceof UnitEnum ?
'\\' . var_export($value, true)
: preg_replace('/\s+/', '', var_export($value, true));

return implode(' ', $signatureParts);
return is_numeric($name) ? (string) $formattedValue : "{$name}: {$formattedValue}";
}

public function generateParameterCode(ReflectionParameter $param): string
private function generateParameterCode(ReflectionParameter $param): string
{
$typeStr = ($this->typeString)($param->getType());
$typeStrWithSpace = $typeStr ? $typeStr . ' ' : $typeStr;
// Variadicのチェック
$variadicStr = $param->isVariadic() ? '...' : '';

// 参照渡しのチェック
$referenceStr = $param->isPassedByReference() ? '&' : '';

// デフォルト値のチェック
$defaultStr = '';
if ($param->isDefaultValueAvailable()) {
$default = var_export($param->getDefaultValue(), true);
Expand Down
8 changes: 6 additions & 2 deletions tests/AopCodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function testReturnType(): void
public function testVariousMethodSignature(): void
{
$bind = new Bind();
for ($i = 1; $i <= 22; $i++) {
for ($i = 1; $i <= 24; $i++) {
$bind->bindInterceptors('method' . (string) $i, []);
}

Expand Down Expand Up @@ -83,10 +83,14 @@ public function testVariousMethodSignature(): void
$this->assertStringContainsString(' /**
* PHPDoc
*/
#[\\Ray\\Aop\\Annotation\\FakeMarker4(array ( 0 => 1, 1 => 2, ), 3)]
#[\\Ray\\Aop\\Annotation\\FakeMarker4(array(0=>1,1=>2,), 3)]
public function method21()', $code);
$this->assertStringContainsString('#[\\Ray\\Aop\\Annotation\\FakeMarkerName(a: 1, b: \'string\', c: true)]
public function method22()', $code);
$this->assertStringContainsString('#[\\Ray\\Aop\\Annotation\\FakeMarker5(\\Ray\\Aop\\FakePhp81Enum::Apple)]
public function method23()', $code);
$this->assertStringContainsString('#[\\Ray\\Aop\\Annotation\\FakeMarker6(fruit1: \\Ray\\Aop\\FakePhp81Enum::Apple, fruit2: \\Ray\\Aop\\FakePhp81Enum::Orange)]
public function method24()', $code);
}

/** @requires PHP 8.2 */
Expand Down
20 changes: 20 additions & 0 deletions tests/Fake/Annotation/FakeMarker5.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Ray\Aop\Annotation;

use Attribute;
use Ray\Aop\FakePhp81Enum;

/**
* @Annotation
* @Target("METHOD")
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class FakeMarker5
{
public function __construct(public readonly FakePhp81Enum $fruit)
{
}
}
22 changes: 22 additions & 0 deletions tests/Fake/Annotation/FakeMarker6.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Ray\Aop\Annotation;

use Attribute;
use Ray\Aop\FakePhp81Enum;

/**
* @Annotation
* @Target("METHOD")
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class FakeMarker6
{
public function __construct(
public readonly FakePhp81Enum $fruit1,
public readonly FakePhp81Enum $fruit2,
) {
}
}
12 changes: 12 additions & 0 deletions tests/Fake/FakePhp81Enum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Ray\Aop;

enum FakePhp81Enum
{
case Apple;
case Orange;
case Grape;
}
7 changes: 7 additions & 0 deletions tests/Fake/FakePhp8Types.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Ray\Aop;

use Ray\Aop\Annotation\FakeMarker4;
use Ray\Aop\Annotation\FakeMarker5;
use Ray\Aop\Annotation\FakeMarker6;
use Ray\Aop\Annotation\FakeMarkerName;

class FakePhp8Types implements FakeNullInterface, \Ray\Aop\FakeNullInterface1
Expand Down Expand Up @@ -62,4 +64,9 @@ public function method21() {}
#[FakeMarkerName(a: 1, b: 'string', c:true)]
public function method22() {}

#[FakeMarker5(FakePhp81Enum::Apple)]
public function method23() {}

#[FakeMarker6(fruit1: FakePhp81Enum::Apple, fruit2: FakePhp81Enum::Orange)]
public function method24() {}
}

0 comments on commit 41e5af6

Please sign in to comment.