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

Support enum in attribute arguments #212

Merged
merged 19 commits into from
May 20, 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
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() {}
}
Loading