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

PECL extension support and introduce newly refined main class "Aspect" #214

Merged
merged 28 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
61613c1
PECL: Add Aspect, PeclDispatcher classes and related test
koriym Jul 6, 2024
7e7a26f
Add MethodInterceptorInterface
koriym Jul 6, 2024
d0cc366
Add specification document for the Aspect class
koriym Jul 6, 2024
ee4fb17
Update Weaver and Aspect classes, add tests
koriym Jul 6, 2024
010dd03
Update PeclDispatcher interface
koriym Jul 6, 2024
213f32e
Refactor Aspect class and update tests
koriym Jul 6, 2024
754d13c
Update README with new Aspect usage and interceptor details
koriym Jul 6, 2024
32d6b03
Update README.md with a clearer explanation and add link
koriym Jul 6, 2024
60abc0a
Refactor README.ja.md for clarity and conciseness
koriym Jul 6, 2024
79d785e
Update code to improve psalm compatibility
koriym Jul 6, 2024
52f6420
Soothe phpstan: Remove argument validation from PeclDispatcher
koriym Jul 6, 2024
d4a5849
Soothe phpstan: Remove argument validation from PeclDispatcher
koriym Jul 6, 2024
931072b
Add method_intercept function for mock testing
koriym Jul 6, 2024
9f183d5
Move MethodInterceptorInterface to src-mock directory
koriym Jul 6, 2024
e586673
Update annotation format in AspectTest
koriym Jul 6, 2024
1d04a5f
Add ray-di logo to README
koriym Jul 6, 2024
cec1481
Move PeclDispatcher to src-php8 directory
koriym Jul 7, 2024
01d8b8e
Refactor AspectTest and remove PHP 8.1 requirement
koriym Jul 7, 2024
1d64791
Add composer-require-checker.json file
koriym Jul 7, 2024
c9702d8
Add LogicException class
koriym Jul 7, 2024
9d7dd23
Refactor method_intercept function in Mock
koriym Jul 7, 2024
b0bb9ad
fixup! Add composer-require-checker.json file
koriym Jul 7, 2024
0bcea46
Add continuous integration workflow for PECL extension
koriym Jul 7, 2024
bbb90c7
Update PECL extension use in README files
koriym Jul 7, 2024
4fbd354
Soothe SA: Add MethodInterceptorInterface usage in Aspect
koriym Jul 7, 2024
e17771f
Add namespace to MethodInterceptorInterface
koriym Jul 7, 2024
b485b95
Add namespace to method_intercept.php and remove unused use statement
koriym Jul 7, 2024
d844ceb
Mark 'method_intercept' function for PHPStan and Psalm to ignore
koriym Jul 7, 2024
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
114 changes: 114 additions & 0 deletions .github/workflows/continuous-integration-pecl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Test with Ray.Aop PECL Extension

on:
push:
pull_request:
workflow_dispatch:

jobs:
build-and-test:
runs-on: ubuntu-latest

strategy:
matrix:
php-version: ['8.1', '8.2', '8.3']

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl
tools: phpize, composer:v2

- name: Install build tools and Valgrind
run: |
sudo apt-get update
sudo apt-get install -y autoconf automake libtool bison re2c valgrind

- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress

- name: Build extension
id: build_extension
run: |
git clone https://github.com/ray-di/ext-rayaop.git
cd ext-rayaop
phpize
./configure
make

- name: PHP info
id: php_info
run: |
php -dextension=./ext-rayaop/modules/rayaop.so -i | grep rayaop

- name: Run PECL demo with debug logging
id: run_pecl_demo
run: |
timeout 60s php -n -dextension=./ext-rayaop/modules/rayaop.so -dmemory_limit=128M -dreport_memleaks=1 -dzend.assertions=1 -dassert.exception=1 ./ext-rayaop/smoke.php
continue-on-error: true

- name: Check loaded extension
id: check_loaded_extension
run: |
php -dextension=./ext-rayaop/modules/rayaop.so -r 'var_dump(extension_loaded("rayaop"));'

- name: Check function exists
id: check_function_exists
run: |
php -dextension=./ext-rayaop/modules/rayaop.so -r 'var_dump(function_exists("method_intercept"));'

- name: Run PHPUnit tests
id: run_phpunit_tests
run: |
php -dextension=./ext-rayaop/modules/rayaop.so vendor/bin/phpunit --coverage-clover=coverage.xml

- name: Run Valgrind memory check
if: steps.build_extension.outcome == 'failure' || steps.run_pecl_demo.outcome == 'failure'
run: |
cat << EOF > valgrind.supp
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: reachable
...
fun:php_module_startup
...
}
EOF
valgrind --suppressions=valgrind.supp --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt php -n -dextension=./ext-rayaop/modules/rayaop.so vendor/bin/phpunit

- name: Check Valgrind results
if: steps.build_extension.outcome == 'failure' || steps.run_pecl_demo.outcome == 'failure'
run: |
if [ -f valgrind-out.txt ]; then
echo "Valgrind log found:"
cat valgrind-out.txt
if ! grep -q "ERROR SUMMARY: 0 errors from 0 contexts" valgrind-out.txt; then
echo "Valgrind found errors"
exit 1
fi
else
echo "Valgrind log not found. This is unexpected."
exit 1
fi

- name: Upload Valgrind log file
if: steps.build_extension.outcome == 'failure' || steps.run_pecl_demo.outcome == 'failure'
uses: actions/upload-artifact@v2
with:
name: valgrind-log
path: valgrind-out.txt
if-no-files-found: warn

- name: Final status check
if: always()
run: |
if [[ "${{ steps.build_extension.outcome }}" == 'failure' || "${{ steps.run_pecl_demo.outcome }}" == 'failure' ]]; then
echo "Build extension and run failed. Please check the logs for more information."
exit 1
fi
2 changes: 2 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ jobs:
uses: ray-di/.github/.github/workflows/static-analysis.yml@v1
with:
php_version: 8.2
has_crc_config: true

187 changes: 65 additions & 122 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,18 @@ class WeekendBlocker implements MethodInterceptor
use Ray\Aop\Sample\Annotation\NotOnWeekends;
use Ray\Aop\Sample\Annotation\RealBillingService;

$pointcut = new Pointcut(
$aspect = new Aspect();
$aspect->bind(
(new Matcher())->any(),
(new Matcher())->annotatedWith(NotOnWeekends::class),
[new WeekendBlocker()]
);
$bind = (new Bind())->bind(RealBillingService::class, [$pointcut]);
$billing = (new Weaver($bind, $tmpDir))->newInstance(RealBillingService::class, [], $bind);

$billing = $aspect->newInstance(RealBillingService::class);
try {
echo $billing->chargeOrder();
} catch (\RuntimeException $e) {
echo $e->getMessage() . "\n";
exit(1);
}
```

Expand All @@ -97,117 +96,41 @@ try {
chargeOrder not allowed on weekends!
```

## メソッド名を指定したマッチ
## PECL拡張

```php
<?php
$bind = (new Bind())->bindInterceptors('chargeOrder', [new WeekendBlocker()]);
$compiler = new Weaver($bind, $tmpDir);
$billing = $compiler->newInstance('RealBillingService', [], $bind);
try {
echo $billing->chargeOrder();
} catch (\RuntimeException $e) {
echo $e->getMessage() . "\n";
exit(1);
}
```

## 独自のマッチャー

独自のマッチャーを作成することもでます。
`contains` マッチャーを作成するためには、2つのメソッドを持つクラスを提供する必要があります。
1つはクラスのマッチを行う`matchesClass`メソッド、もう1つはメソッドのマッチを行う`matchesMethod`です。いずれもマッチしたかどうかをブールで返します。

```php
use Ray\Aop\AbstractMatcher;

class IsContainsMatcher extends AbstractMatcher
{
/**
* {@inheritdoc}
*/
public function matchesClass(\ReflectionClass $class, array $arguments) : bool
{
[$contains] = $arguments;

return (strpos($class->name, $contains) !== false);
}

/**
* {@inheritdoc}
*/
public function matchesMethod(\ReflectionMethod $method, array $arguments) : bool
{
[$contains] = $arguments;

return (strpos($method->name, $contains) !== false);
}
}
```
Ray.Aopは[PECL拡張](https://github.com/ray-di/ext-rayaop)もサポートしています。拡張機能がインストールされている場合、weaveメソッドを使用してディレクトリ内のすべてのクラスにアスペクトを適用できます。

```php
$pointcut = new Pointcut(
$aspect = new Aspect();
$aspect->bind(
(new Matcher())->any(),
new IsContainsMatcher('charge'),
(new Matcher())->annotatedWith(NotOnWeekends::class),
[new WeekendBlocker()]
);
$bind = (new Bind)->bind(RealBillingService::class, [$pointcut]);
$billing = (new Weaver($bind, $tmpDir))->newInstance(RealBillingService::class, [$arg1, $arg2]);
$aspect->weave(__DIR__ . '/src'); // ディレクトリ内でマッチャーに一致するすべてのクラスにアスペクトを織り込みます。
$billing = new RealBillingService();
echo $billing->chargeOrder(); // インターセプターが適用されます。
```

マッチャーはdoctrineアノテーション、またはPHP8アトリビュートの読み込みをサポートします。
PECL拡張機能を使用すると:

```php
public function matchesClass(\ReflectionClass $class, array $arguments) : bool
{
assert($class instanceof \Ray\Aop\ReflectionClass);
$classAnnotation = $class->getAnnotation(Foo::class); // @Foo or #[Foo]
// ...
}
通常のnewキーワードを使用してコードのどこでも新しいインスタンスを作成できます。
インターセプションはfinalクラスやメソッドでも動作します。
これらの機能を使用するには、PECL拡張機能をインストールするだけで、Ray.Aopが自動的に利用します。

public function matchesMethod(\ReflectionMethod $method, array $arguments) : bool
{
assert($method instanceof \Ray\Aop\ReflectionMethod);
$methodAnnotation = $method->getAnnotation(Bar::class);
}
```
### PECL拡張のインストール

## パフォーマンス
PECL拡張機能の利用にはPHP 8.1以上が必要です。詳細は[ext-rayaop](https://github.com/ray-di/ext-rayaop?tab=readme-ov-file#installation)を参照してください。

`Weaver`オブジェクトはキャッシュ可能です。コンパイル、束縛、アノテーション読み込みコストを削減します。
## 設定オプション
Aspectインスタンスを作成する際に、オプションで一時ディレクトリを指定できます。

```php
$weaver = unserialize(file_get_contentes('./serializedWeaver'));
$billing = (new Weaver($bind, $tmpDir))->newInstance(RealBillingService::class, [$arg1, $arg2]);
$aspect = new Aspect('/path/to/tmp/dir');
```
指定しない場合、システムのデフォルトの一時ディレクトリが使用されます。

## 優先順位

インターセプターの実行順は以下のルールで決定されます。

* 基本的にはバインドした順に実行されます。
* `PriorityPointcut`で定義したものが最も優先されます。
* アノテーションでメソッドマッチするものは`PriorityPointcut`の次に優先されます。その時アノテートされた順で優先されます。

```php
/**
* @Auth // 1st
* @Cache // 2nd
* @Log // 3rd
*/
```

## 制限

この機能の背後ではメソッドのインターセプションを事前にコードを生成する事で可能にしています。Ray.Aopはダイナミックにサブクラスを生成してメソッドをオーバーライドするインターセプターを適用します。

クラスとメソッドは以下のものである必要があります。

* クラスは *final* ではない
* メソッドは *public*


## インターセプター
## インターセプターの詳細

呼び出されたメソッドをそのまま実行するだけのインターセプターは以下のようになります。

Expand All @@ -216,21 +139,25 @@ class MyInterceptor implements MethodInterceptor
{
public function invoke(MethodInvocation $invocation)
{
// メソッド実行前
//

// メソッド実行
$result = invocation->proceed();

// メソッド実行後
//

// メソッド呼び出し前
$result = $invocation->proceed();
// メソッド呼び出し後
return $result;
}
}
```

インターセプターに渡されるメソッド実行(`MethodInvocation`)オブジェクトは以下のメソッドを持ちます。
`$invocation->proceed()`はチェーン内の次のインターセプターを呼び出します。インターセプターが存在しない場合、ターゲットメソッドを呼び出します。このチェーンにより、単一のメソッドに対して複数のインターセプターを適用し、バインドされた順序で実行することができます。

インターセプターA、B、Cがメソッドにバインドされている場合の実行フローの例:

1. インターセプターA(前)
1. インターセプターB(前)
1. インターセプターC(前)
1. ターゲットメソッド
1. インターセプターC(後)
1. インターセプターB(後)
1. インターセプターA(後)


* [`$invocation->proceed()`](https://github.com/ray-di/Ray.Aop/blob/2.x/src/Joinpoint.php#L41) - メソッド実行
Expand All @@ -240,7 +167,7 @@ class MyInterceptor implements MethodInterceptor
* [`$invocation->getNamedArguments()`](https://github.com/ray-di/Ray.Aop/blob/2.x/src/Invocation.php#L32) - 名前付き引数の取得


拡張されたリフレクションはアノテーション取得のメソッドを持ちます
拡張されたClassReflectionとMethodReflectionはPHP 8の属性とドクトリンアノテーションを取得するメソッドを提供します

```php
/** @var $method \Ray\Aop\ReflectionMethod */
Expand All @@ -250,11 +177,37 @@ $class = $invocation->getMethod()->getDeclaringClass();
```


* [`$method->getAnnotations()`]() - メソッドアノテーションの取得
* [`$method->getAnnotations()`]() - メソッドアトリビュート/アノテーションの取得
* [`$method->getAnnotation($name)`]()
* [`$class->->getAnnotations()`]() - クラスアノテーションの取得
* [`$class->->getAnnotations()`]() - クラスアトリビュート/アノテーションの取得
* [`$class->->getAnnotation($name)`]()

## 独自のマッチャー

独自のマッチャーを作成できます。例えば、`ContainsMatcher`を作成するには:

```php
use Ray\Aop\AbstractMatcher;

class ContainsMatcher extends AbstractMatcher
{
public function matchesClass(\ReflectionClass $class, array $arguments) : bool
{
[$contains] = $arguments;
return (strpos($class->name, $contains) !== false);
}

public function matchesMethod(\ReflectionMethod $method, array $arguments) : bool
{
[$contains] = $arguments;
return (strpos($method->name, $contains) !== false);
}
}
```

アノテーション/属性
Ray.Aopは[doctrine/annotation](https://github.com/doctrine/annotations)またはPHP 8の[Attributes](https://www.php.net/manual/en/language.attributes.overview.php)のどちらかも使用できます。

## AOPアライアンス

このメソッドインターセプターのAPIは[AOPアライアンス](http://aopalliance.sourceforge.net/doc/org/aopalliance/intercept/MethodInterceptor.html)の部分実装です。
Expand All @@ -268,16 +221,6 @@ Ray.Aopの推奨インストール方法は、[Composer](https://github.com/comp
$ composer require ray/aop ~2.0
```

## パフォーマンス

AOPクラスのコンパイルにより、Ray.Aopは高速に動作します。アノテーションの読み込みは初回コンパイル時のみなので、ランタイムのパフォーマンスに影響を与えません。開発段階や最初の実行時にも、ファイルのタイムスタンプを利用してPHPファイルがキャッシュされ、通常はアノテーション生成のコストを気にする必要はありませんが、アプリケーションのブートストラップでアノテーションリーダーの設定を行うことで、初回コンパイル時のパフォーマンスが向上します。特に大規模なアプリケーションでこの設定は役立ちます。

### APCu

```php
SevericeLocator::setReader(new PsrCachedReader(new Reader(), $apcuCache));
```

### アトリビュートのみ使用(推奨)

```php
Expand Down
Loading
Loading