Skip to content

Commit

Permalink
Merge pull request #205 from ray-di/codegen
Browse files Browse the repository at this point in the history
Create own codegen
  • Loading branch information
koriym committed Apr 21, 2024
2 parents 71d0698 + 56bf55f commit da2abe9
Show file tree
Hide file tree
Showing 53 changed files with 1,269 additions and 907 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coding-standards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ jobs:
cs:
uses: ray-di/.github/.github/workflows/coding-standards.yml@v1
with:
php_version: 8.1
php_version: 8.2
2 changes: 1 addition & 1 deletion .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ jobs:
sa:
uses: ray-di/.github/.github/workflows/static-analysis.yml@v1
with:
php_version: 8.1
php_version: 8.2
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
/composer.lock
/coverage.xml
/vendor-bin/**/vendor
/.phpunit-cache
/.phpunit.cache/
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
],
"require": {
"php": "^7.2 || ^8.0",
"ext-hash": "*",
"ext-tokenizer": "*",
"doctrine/annotations": "^1.12 || ^2.0",
"koriym/attributes": "^1.0.3",
"nikic/php-parser": "^4.16"
"koriym/attributes": "^1.0.3"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"phpunit/phpunit": "^8.5.23 || ^9.5.10"
"phpunit/phpunit": "^8.5.23 || ^9.5.10",
"nikic/php-parser": "^4.16"
},
"config": {
"sort-packages": true,
Expand Down
1 change: 0 additions & 1 deletion phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ com
<exclude name="SlevomatCodingStandard.Classes.SuperfluousTraitNaming.SuperfluousSuffix"/>
<!-- /Base -->
<!-- Option -->
<exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed"/>
<exclude name="SlevomatCodingStandard.Functions.RequireTrailingCommaInCall.MissingTrailingComma"/>
<!-- /Option -->
<!-- Exclude Fake files form Doctrine CS -->
Expand Down
8 changes: 6 additions & 2 deletions phpmd.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<!--codesize-->
<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity">
<properties>
<property name="reportLevel" value="11" />
</properties>
</rule>
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
<rule ref="rulesets/codesize.xml/ExcessiveClassComplexity"/>
<rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>
Expand All @@ -27,7 +31,7 @@
<!--unusedcode-->
<rule ref="rulesets/unusedcode.xml/UnusedFormalParameter"/>
<rule ref="rulesets/unusedcode.xml/UnusedLocalVariable"/>
<rule ref="rulesets/unusedcode.xml/UnusedPrivateField"/>
<!-- <rule ref="rulesets/unusedcode.xml/UnusedPrivateField"/>-->
<rule ref="rulesets/unusedcode.xml/UnusedPrivateMethod"/>
<!--controversial-->
<rule ref="rulesets/controversial.xml/CamelCaseClassName"/>
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
parameters:
phpVersion: 80200
level: max
paths:
- sl-src
Expand Down
3 changes: 2 additions & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config https://psalm.dev/schema/config"
findUnusedBaselineEntry="true"
findUnusedCode="false"
phpVersion="8.2"
findUnusedCode = "false"
>
<projectFiles>
<directory name="sl-src" />
Expand Down
5 changes: 3 additions & 2 deletions sl-src/CacheReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use function array_merge;
use function assert;
use function filemtime;
use function is_string;
use function max;
use function rawurlencode;

Expand Down Expand Up @@ -161,7 +162,7 @@ private function getLastModification(ReflectionClass $class): int // @phpstan-i
$parent = $class->getParentClass();

$lastModification = max(array_merge(
[$filename ? filemtime($filename) : 0],
[is_string($filename) ? filemtime($filename) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $class->getTraits()),
Expand All @@ -185,7 +186,7 @@ private function getTraitLastModificationTime(ReflectionClass $reflectionTrait):
}

$lastModificationTime = max(array_merge(
[$fileName ? filemtime($fileName) : 0],
[is_string($fileName) ? filemtime($fileName) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $reflectionTrait->getTraits())
Expand Down
66 changes: 0 additions & 66 deletions src/AopClass.php

This file was deleted.

28 changes: 0 additions & 28 deletions src/AopClassName.php

This file was deleted.

193 changes: 193 additions & 0 deletions src/AopCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
<?php

declare(strict_types=1);

namespace Ray\Aop;

use Ray\Aop\Exception\InvalidSourceClassException;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionUnionType;

use function array_keys;
use function file_exists;
use function file_get_contents;
use function implode;
use function in_array;
use function preg_replace;
use function preg_replace_callback;
use function sprintf;
use function token_get_all;

use const T_CLASS;
use const T_EXTENDS;
use const T_STRING;

final class AopCode
{
public const INTERCEPT_STATEMENT = '\$this->_intercept(__FUNCTION__, func_get_args());';

/** @var string */
private $code = '';

/** @var int */
private $curlyBraceCount = 0;

/** @var MethodSignatureString */
private $methodSignature;

public function __construct(MethodSignatureString $methodSignature)
{
$this->methodSignature = $methodSignature;
}

/** @param ReflectionClass<object> $sourceClass */
public function generate(ReflectionClass $sourceClass, BindInterface $bind, string $postfix): string
{
$this->parseClass($sourceClass, $postfix);
$this->implementsInterface(WeavedInterface::class);
$this->addMethods($sourceClass, $bind);

return $this->getCodeText();
}

/** @return void */
private function add(string $text)
{
if ($text === '{') {
$this->curlyBraceCount++;
}

if ($text === '}') {
// @codeCoverageIgnoreStart
$this->curlyBraceCount--;
// @codeCoverageIgnoreEnd
}

$this->code .= $text;
}

/** @param non-empty-string $code */
private function insert(string $code): void
{
$replacement = $code . '}';
$this->code = (string) preg_replace('/}\s*$/', $replacement, $this->code);
}

private function addClassName(string $className, string $postfix): void
{
$newClassName = $className . $postfix;
$this->add($newClassName . ' extends ' . $className . ' ');
}

/** @param ReflectionClass<object> $sourceClass */
private function parseClass(ReflectionClass $sourceClass, string $postfix): void
{
$fileName = (string) $sourceClass->getFileName();
if (! file_exists($fileName)) {
throw new InvalidSourceClassException($sourceClass->getName());
}

$code = (string) file_get_contents($fileName);
/** @var array<int, array{int, string, int}|string> $tokens */
$tokens = token_get_all($code);
$iterator = new TokenIterator($tokens);
$inClass = false;
$className = '';

for ($iterator->rewind(); $iterator->valid(); $iterator->next()) {
[$id, $text] = $iterator->getToken();
$isClassKeyword = $id === T_CLASS;
if ($isClassKeyword) {
$inClass = true;
$this->add($text);
continue;
}

$isClassName = $inClass && $id === T_STRING && empty($className);
if ($isClassName) {
$className = $text;
$this->addClassName($className, $postfix);
continue;
}

$isExtendsKeyword = $id === T_EXTENDS;
if ($isExtendsKeyword) {
$iterator->skipExtends();
continue;
}

$isClassSignatureEnds = $inClass && $text === '{';
if ($isClassSignatureEnds) {
$this->addIntercepterTrait();

return;
}

$this->add($text);
}
}

private function implementsInterface(string $interfaceName): void
{
$pattern = '/(class\s+[\w\s]+extends\s+\w+)(?:\s+implements\s+(.+))?/';
$this->code = (string) preg_replace_callback($pattern, static function ($matches) use ($interfaceName) {
if (isset($matches[2])) {
// 既に implements が存在する場合
// $match[0] class FakePhp8Types_test extends FakePhp8Types implements FakeNullInterface, \Ray\Aop\FakeNullInterface1
// $match[1] class FakePhp8Types_test extends FakePhp8Types
// $match[2] FakeNullInterface, \Ray\Aop\FakeNullInterface1
return sprintf('%s implements %s, \%s', $matches[1], $matches[2], $interfaceName);
}

// implements が存在しない場合
return sprintf('%s implements \%s', $matches[0], $interfaceName);
}, $this->code);
}

/** @param ReflectionClass<object> $class */
private function addMethods(ReflectionClass $class, BindInterface $bind): void
{
$bindings = array_keys($bind->getBindings());

$parentMethods = $class->getMethods();
$interceptedMethods = [];
foreach ($parentMethods as $method) {
if (! in_array($method->getName(), $bindings)) {
continue;
}

$signature = $this->methodSignature->get($method);
$isVoid = false;
if ($method->hasReturnType() && (! $method->getReturnType() instanceof ReflectionUnionType)) {
$nt = $method->getReturnType();
$isVoid = $nt instanceof ReflectionNamedType && $nt->getName() === 'void';
}

$return = $isVoid ? '' : 'return ';
$interceptedMethods[] = sprintf(" %s\n {\n %s%s\n }\n", $signature, $return, self::INTERCEPT_STATEMENT);
}

if (! $interceptedMethods) {
return;
}

$this->insert(implode("\n", $interceptedMethods));
}

private function addIntercepterTrait(): void
{
$this->add(sprintf("{\n use \%s;\n}\n", InterceptTrait::class));
}

private function getCodeText(): string
{
// close opened curly brace
while ($this->curlyBraceCount !== 0) {
$this->code .= '}';
$this->curlyBraceCount--;
}

return $this->code;
}
}
Loading

0 comments on commit da2abe9

Please sign in to comment.