diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0dc79f4..f2f31a0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
- php-version: ['8.1', '8.2', '8.3']
+ php-version: ['8.1', '8.2', '8.3', '8.4']
steps:
- name: Checkout code
@@ -50,8 +50,17 @@ jobs:
timeout 60s php -n -dextension=./modules/rayaop.so -dmemory_limit=128M -dreport_memleaks=1 -dzend.assertions=1 -dassert.exception=1 smoke.php
continue-on-error: true
+ # Add demo directory test
+ - name: Test demo directory
+ id: test_demo_dir
+ run: |
+ cd demo
+ composer install
+ php -dextension=../modules/rayaop.so aop.php
+ continue-on-error: true
+
- name: Run Valgrind memory check
- if: steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure'
+ if: steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure' || steps.test_demo_dir.outcome == 'failure'
run: |
cat << EOF > valgrind.supp
{
@@ -65,27 +74,32 @@ jobs:
EOF
valgrind --suppressions=valgrind.supp --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt php -n -dextension=./modules/rayaop.so smoke.php
+ # Also run Valgrind on demo directory
+ cd demo
+ valgrind --suppressions=../valgrind.supp --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-demo-out.txt php -n -dextension=../modules/rayaop.so aop.php
+
- name: Check Valgrind results
- if: steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure'
+ if: steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure' || steps.test_demo_dir.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
+ for log in valgrind-out.txt demo/valgrind-demo-out.txt; do
+ if [ -f "$log" ]; then
+ echo "Checking Valgrind log: $log"
+ cat "$log"
+ if ! grep -q "ERROR SUMMARY: 0 errors from 0 contexts" "$log"; then
+ echo "Valgrind found errors in $log"
+ exit 1
+ fi
fi
- else
- echo "Valgrind log not found. This is unexpected."
- exit 1
- fi
+ done
- - name: Upload Valgrind log file
- if: (steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure') && always()
+ - name: Upload Valgrind logs
+ if: (steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure' || steps.test_demo_dir.outcome == 'failure') && always()
uses: actions/upload-artifact@v4
with:
- name: valgrind-log
- path: valgrind-out.txt
+ name: valgrind-logs
+ path: |
+ valgrind-out.txt
+ demo/valgrind-demo-out.txt
if-no-files-found: warn
- name: Upload test logs
@@ -96,12 +110,13 @@ jobs:
path: |
tests/*.log
tests/*.sh
+ demo/*.log
if-no-files-found: warn
- name: Final status check
if: always()
run: |
- if [ "${{ steps.run_tests.outcome }}" == "failure" ] || [ "${{ steps.run_demo.outcome }}" == "failure" ]; then
- echo "Tests or demo run failed. Please check the logs for more information."
+ if [ "${{ steps.run_tests.outcome }}" == "failure" ] || [ "${{ steps.run_demo.outcome }}" == "failure" ] || [ "${{ steps.test_demo_dir.outcome }}" == "failure" ]; then
+ echo "Tests, demo run, or demo directory test failed. Please check the logs for more information."
exit 1
fi
diff --git a/.gitignore b/.gitignore
index 4a3eb36..bc96b4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,6 @@
/Makefile.objects
/cmake-build-debug
/modules/rayaop.so
+
+/demo/vendor/
+/demo/tmp/
diff --git a/README.ja.md b/README.ja.md
index cd82ef1..d12f56e 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -1,34 +1,36 @@
-# Ray.Aop PHP拡張
+# Ray.Aop PHP拡張機能
[![Build and Test PHP Extension](https://github.com/ray-di/ext-rayaop/actions/workflows/build.yml/badge.svg)](https://github.com/ray-di/ext-rayaop/actions/workflows/build.yml)
-[Ray.Aop](https://github.com/ray-di/Ray.Aop)のためのコアメソッドインターセプション機能を提供する低レベルのPHP拡張です。この拡張は単体での使用も可能ですが、Ray.Aopのより高度なAOP機能の基盤として設計されています。
+[Ray.Aop](https://github.com/ray-di/Ray.Aop)のコアとなるメソッドインターセプション機能を提供する低レベルPHP拡張機能です。この拡張機能は単独でも使用できますが、Ray.Aopのより高度なAOP機能の基盤として設計されています。
## 特徴
- 効率的な低レベルメソッドインターセプション
-- finalクラスおよびメソッドのインターセプトをサポート
-- 引数および戻り値の完全な変更サポート
-- `new`キーワードとのシームレスな互換性
-- スレッドセーフな操作サポート
+- finalクラスやメソッドのインターセプトに対応
+- パラメータと戻り値の完全な変更サポート
+- `new`キーワードとのシームレスな連携
+- スレッドセーフな動作のサポート
+- 包括的なエラーハンドリングとデバッグ機能
+- 適切なリソース管理による効率的なメモリ使用
## 要件
- PHP 8.1以上
-- Linux、macOS、またはWindowsでの適切なビルドツール
-- マルチスレッド環境ではスレッドセーフなPHPビルドを推奨
+- Linux、macOS、またはWindows(適切なビルドツールが必要)
+- マルチスレッド環境では、スレッドセーフビルドのPHPを推奨
## インストール
-1. リポジトリをクローンします:
+1. リポジトリのクローン:
```bash
git clone https://github.com/ray-di/ext-rayaop.git
cd ext-rayaop
```
-2. 拡張をビルドし、インストールします:
+2. ビルドとインストール:
```bash
phpize
./configure
@@ -36,42 +38,151 @@ make
make install
```
-3. php.iniファイルに次の行を追加します:
+3. php.iniに以下の行を追加:
```ini
-extension=rayaop.so # Unix/Linuxの場合
-extension=rayaop.dll # Windowsの場合
+extension=rayaop.so # Unix/Linux用
+extension=rayaop.dll # Windows用
+
+; オプション: デバッグモードの有効化
+; rayaop.debug_level = 1
```
-4. インストールを確認します:
+4. インストールの確認:
```bash
php -m | grep rayaop
```
+## API リファレンス
+
+### コア関数
+
+#### method_intercept_init()
+インターセプションシステムを初期化します。インターセプターの登録前に必ず呼び出す必要があります。
+
+```php
+bool method_intercept_init()
+```
+
+成功時は`true`、失敗時は`false`を返します。
+
+#### method_intercept()
+特定のクラスメソッドにインターセプターを登録します。
+
+```php
+bool method_intercept(string $class_name, string $method_name, Ray\Aop\MethodInterceptorInterface $interceptor)
+```
+
+- すでにインターセプターが登録されている場合は上書きされます
+- 成功時は`true`、失敗時は`false`を返します
+
+#### method_intercept_enable()
+メソッドインターセプションをグローバルに有効/無効にします。
+
+```php
+void method_intercept_enable(bool $enable)
+```
+
+### スレッドセーフティ
+
+この拡張機能は完全にスレッドセーフで、以下の機構を使用しています:
+
+- ミューテックスによるリソース保護
+- グローバル状態用のスレッドローカルストレージ
+- マルチスレッド環境での安全な初期化とクリーンアップ
+
+マルチスレッド環境(PHP-FPMなど)での使用時の注意点:
+- PHPでZTS(Zend Thread Safety)が有効になっていることを確認
+- 各スレッドが独自のインターセプション状態を維持
+- リソースのクリーンアップは自動的にスレッドごとに処理
+
+## エラーハンドリング
+
+### エラーコード
+
+拡張機能は以下のエラーコードを定義しています:
+
+- `RAYAOP_E_MEMORY_ALLOCATION (1)`: メモリ割り当て失敗
+- `RAYAOP_E_HASH_UPDATE (2)`: ハッシュテーブル更新失敗
+- `RAYAOP_E_INVALID_HANDLER (3)`: 無効なインターセプターハンドラー
+- `RAYAOP_E_MAX_DEPTH_EXCEEDED (4)`: 最大インターセプション深度超過
+- `RAYAOP_E_NULL_POINTER (5)`: NULLポインターエラー
+- `RAYAOP_E_INVALID_STATE (6)`: 無効な内部状態
+
+エラーはPHPのエラー報告システムを通じて報告されます。これらのエラーをキャッチして処理するには、エラー報告を有効にしてください。
+
+## デバッグモード
+
+php.iniでデバッグレベルを設定することでデバッグモードを有効にできます:
+
+```ini
+rayaop.debug_level = 1
+```
+
+デバッグ出力には以下が含まれます:
+- インターセプター登録イベント
+- メソッドインターセプションのトレース
+- リソースの割り当て/解放
+- エラー条件とスタックトレース
+
+## パフォーマンスに関する考慮事項
+
+### 実行深度
+
+無限再帰を防ぐため、最大インターセプション深度に制限があります:
+```php
+#define MAX_EXECUTION_DEPTH 100
+```
+
+ネストされたインターセプターを設計する際は、この制限を考慮してください。
+
+### メモリ管理
+
+拡張機能は慎重なメモリ管理を実装しています:
+- インターセプターリソースの自動クリーンアップ
+- PHPオブジェクトの適切な参照カウント
+- インターセプター置換時の即時リソース解放
+- リクエスト終了時のクリーンアップ
+
+### パフォーマンスへの影響
+
+メソッドインターセプションによるオーバーヘッド:
+- 直接のメソッド呼び出し:約0.1-0.2μsの追加オーバーヘッド
+- 単純なインターセプターでの呼び出し:約1-2μsのオーバーヘッド
+- 複雑なインターセプターは実装に応じてより多くのオーバーヘッドが発生する可能性があります
+
+パフォーマンスに関するヒント:
+- インターセプターはアプリケーション初期化時に登録
+- インターセプターの頻繁な登録/解除を避ける
+- パフォーマンスが重要なコードには単純なインターセプターを使用
+- 必要のない場合はインターセプションを無効化
+
## 設計の決定
-この拡張は、最小限で高パフォーマンスなメソッドインターセプション機能を提供します。
+この拡張機能は、最小限の高性能メソッドインターセプション機能を提供します:
-- メソッドごとに1つのインターセプター:各メソッドに対して最後に登録されたインターセプターが優先されます
-- finalクラスサポート:純粋なPHP実装と異なり、finalクラスおよびメソッドをインターセプト可能
-- 素のインターセプション:組み込みのマッチングや条件はありません(これらの機能はRay.Aopで提供されます)
-- スレッドセーフ:PHP-FPMなどのマルチスレッド環境でも安全に使用可能
+- メソッドあたり1つのインターセプター:最後に登録されたインターセプターが優先されます
+ - 新しいインターセプターを登録すると、以前のものは自動的に解除されます
+ - この設計により、予測可能な動作と最適なパフォーマンスを確保
+- finalクラスのサポート:Pure PHP実装では不可能なfinalクラスやメソッドのインターセプトが可能
+- 生のインターセプション:組み込みのマッチングや条件はなし(これらの機能にはRay.Aopを使用)
+- スレッドセーフ:PHP-FPMなどのマルチスレッド環境で安全に使用可能
## Ray.Aopとの関係
-この拡張は低レベルのメソッドインターセプションを提供し、[Ray.Aop](https://github.com/ray-di/Ray.Aop)は高レベルのAOP機能を提供します。
+この拡張機能は低レベルのメソッドインターセプションを提供し、[Ray.Aop](https://github.com/ray-di/Ray.Aop)は高レベルのAOP機能を提供します:
-Ray.Aopが提供する機能:
-- マッチャーを用いた条件付きインターセプション
-- メソッドごとの複数インターセプター
+Ray.Aopの提供する機能:
+- Matchersを使用した条件付きインターセプション
+- メソッドあたり複数のインターセプター
- 属性/アノテーションベースのインターセプション
- 高度なAOP機能
-両方を併用する場合:
+両者を併用する場合:
- Ray.Aopが高レベルのAOPロジックを処理
-- この拡張が低レベルのインターセプション機構を提供
-- Ray.Aopは、パフォーマンス向上のためにこの拡張を自動的に利用
+- この拡張機能が低レベルのインターセプション機構を提供
+- Ray.Aopは可能な場合、パフォーマンス向上のためにこの拡張機能を自動的に利用
-## 基本的な使い方
+## 基本的な使用方法
### シンプルなインターセプター
```php
@@ -79,38 +190,30 @@ class LoggingInterceptor implements Ray\Aop\MethodInterceptorInterface
{
public function intercept(object $object, string $method, array $params): mixed
{
- echo "Before {$method}\n";
+ echo "実行前 {$method}\n";
$result = $object->$method(...$params);
- echo "After {$method}\n";
+ echo "実行後 {$method}\n";
return $result;
}
}
-// インターセプターを登録
-method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor());
-```
-
-### メソッドインターセプションの設定
-```php
-// インターセプションシステムを初期化
+// 初期化とインターセプションの有効化
method_intercept_init();
-
-// メソッドインターセプションを有効化
method_intercept_enable(true);
-// インターセプターを登録
-method_intercept(MyClass::class, 'myMethod', new MyInterceptor());
+// インターセプターの登録
+method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor());
```
## 開発
### ビルドスクリプト
```bash
-./build.sh clean # ビルド環境をクリーン
-./build.sh prepare # ビルド環境を準備
-./build.sh build # 拡張をビルド
-./build.sh run # 拡張を実行
-./build.sh all # 全てのステップを実行
+./build.sh clean # ビルド環境のクリーン
+./build.sh prepare # ビルド環境の準備
+./build.sh build # 拡張機能のビルド
+./build.sh run # 拡張機能の実行
+./build.sh all # すべての手順を実行
```
### テスト
@@ -118,17 +221,30 @@ method_intercept(MyClass::class, 'myMethod', new MyInterceptor());
make test
```
-特定のテストを実行する場合:
+特定のテストの実行:
```bash
make test TESTS="-v tests/your_specific_test.phpt"
```
+## 既知の制限事項
+
+1. メソッドあたり1つのインターセプターのみ
+2. 実行深度の最大値は100レベル
+3. メソッドインターセプションのパターンマッチング機能なし
+4. インターセプターはメソッドごとに個別に登録が必要
+
## ライセンス
-[MIT License](LICENSE)
+[MITライセンス](LICENSE)
## 作者
Akihito Koriyama
-この拡張は、AIペアプログラミングの支援を受けて開発され、PHP拡張開発およびPECL基準に関する複雑さを克服するのに役立ちました。
\ No newline at end of file
+この拡張機能は、PHP拡張機能開発とPECL標準の複雑さをナビゲートするのに役立つAIペアプログラミングの支援を受けて開発されました。
+
+## 謝辞
+
+このプロジェクトは[JetBrains CLion](https://www.jetbrains.com/clion/)を使用して作成されました。CLionは[オープンソースライセンス](https://www.jetbrains.com/community/opensource/)で無料で利用できます。
+
+強力で使いやすい開発環境を提供してくださったJetBrainsに感謝いたします。
\ No newline at end of file
diff --git a/README.md b/README.md
index ba9356a..e0daf3e 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ Low-level PHP extension that provides core method interception functionality for
- Full parameter and return value modification support
- Works seamlessly with the `new` keyword
- Thread-safe operation support
+- Comprehensive error handling and debugging capabilities
+- Memory-efficient design with proper resource management
## Requirements
@@ -40,6 +42,9 @@ make install
```ini
extension=rayaop.so # For Unix/Linux
extension=rayaop.dll # For Windows
+
+; Optional: Enable debug mode
+; rayaop.debug_level = 1
```
4. Verify installation:
@@ -47,11 +52,117 @@ extension=rayaop.dll # For Windows
php -m | grep rayaop
```
+## API Reference
+
+### Core Functions
+
+#### method_intercept_init()
+Initializes the interception system. Must be called before registering any interceptors.
+
+```php
+bool method_intercept_init()
+```
+
+Returns `true` on success, `false` on failure.
+
+#### method_intercept()
+Registers an interceptor for a specific class method.
+
+```php
+bool method_intercept(string $class_name, string $method_name, Ray\Aop\MethodInterceptorInterface $interceptor)
+```
+
+- If an interceptor is already registered for the method, it will be replaced
+- Returns `true` on success, `false` on failure
+
+#### method_intercept_enable()
+Enables or disables method interception globally.
+
+```php
+void method_intercept_enable(bool $enable)
+```
+
+### Thread Safety
+
+The extension is fully thread-safe and uses the following mechanisms:
+
+- Mutex-based resource protection
+- Thread-local storage for global state
+- Safe initialization and cleanup in multi-threaded environments
+
+When using in multi-threaded environments (e.g., PHP-FPM):
+- Ensure ZTS (Zend Thread Safety) is enabled in PHP
+- Each thread maintains its own interception state
+- Resource cleanup is handled automatically per thread
+
+## Error Handling
+
+### Error Codes
+
+The extension defines the following error codes:
+
+- `RAYAOP_E_MEMORY_ALLOCATION (1)`: Memory allocation failure
+- `RAYAOP_E_HASH_UPDATE (2)`: Hash table update failure
+- `RAYAOP_E_INVALID_HANDLER (3)`: Invalid interceptor handler
+- `RAYAOP_E_MAX_DEPTH_EXCEEDED (4)`: Maximum interception depth exceeded
+- `RAYAOP_E_NULL_POINTER (5)`: Null pointer error
+- `RAYAOP_E_INVALID_STATE (6)`: Invalid internal state
+
+Errors are reported through PHP's error reporting system. Enable error reporting to catch and handle these errors.
+
+## Debug Mode
+
+Debug mode can be enabled by setting the debug level in php.ini:
+
+```ini
+rayaop.debug_level = 1
+```
+
+Debug output includes:
+- Interceptor registration events
+- Method interception traces
+- Resource allocation/deallocation
+- Error conditions and stack traces
+
+## Performance Considerations
+
+### Execution Depth
+
+The extension limits the maximum interception depth to prevent infinite recursion:
+```php
+#define MAX_EXECUTION_DEPTH 100
+```
+
+Consider this limit when designing nested interceptors.
+
+### Memory Management
+
+The extension implements careful memory management:
+- Automatic cleanup of interceptor resources
+- Proper reference counting for PHP objects
+- Immediate resource release when interceptors are replaced
+- Cleanup on request shutdown
+
+### Performance Impact
+
+Method interception adds minimal overhead:
+- Direct method calls: ~0.1-0.2μs additional overhead
+- Intercepted calls with simple interceptors: ~1-2μs overhead
+- Complex interceptors may add more overhead depending on their implementation
+
+Performance tips:
+- Register interceptors during application initialization
+- Avoid registering/unregistering interceptors frequently
+- Use simple interceptors for performance-critical code
+- Consider disabling interception when not needed
+
## Design Decisions
This extension provides minimal, high-performance method interception capabilities:
- One interceptor per method: The extension supports a single active interceptor per method, with the last registered interceptor taking precedence
+ - When registering a new interceptor for a method, the previous one is automatically unregistered
+ - This design ensures predictable behavior and optimal performance
- Final class support: Can intercept final classes and methods, unlike pure PHP implementations
- Raw interception: No built-in matching or conditions (use Ray.Aop for these features)
- Thread-safe: Safe to use in multi-threaded environments like PHP-FPM
@@ -86,20 +197,12 @@ class LoggingInterceptor implements Ray\Aop\MethodInterceptorInterface
}
}
-// Register the interceptor
-method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor());
-```
-
-### Method Interception Setup
-```php
-// Initialize the interception system
+// Initialize and enable interception
method_intercept_init();
-
-// Enable method interception
method_intercept_enable(true);
-// Register interceptors
-method_intercept(MyClass::class, 'myMethod', new MyInterceptor());
+// Register the interceptor
+method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor());
```
## Development
@@ -123,6 +226,13 @@ For specific tests:
make test TESTS="-v tests/your_specific_test.phpt"
```
+## Known Limitations
+
+1. Single interceptor per method
+2. Maximum execution depth of 100 levels
+3. No built-in pattern matching for method interception
+4. Interceptors must be registered individually for each method
+
## License
[MIT License](LICENSE)
diff --git a/build.sh b/build.sh
index 4117f68..38beac5 100755
--- a/build.sh
+++ b/build.sh
@@ -9,7 +9,7 @@ clean() {
prepare() {
echo "Preparing..."
phpize
- ./configure
+ ./configure CFLAGS="-g -O0"
}
build() {
@@ -23,10 +23,20 @@ install() {
}
run() {
- echo "Run..."
+ echo "Run smoke..."
php -dextension=modules/rayaop.so -ddisplay_errors=1 smoke.php
}
+demo() {
+ echo "Run demo..."
+ php -dextension=modules/rayaop.so -ddisplay_errors=1 demo/aop.php
+}
+
+debug() {
+ echo "Run demo..."
+ lldb -o run -- php -dextension=modules/rayaop.so -ddisplay_errors=1 Ray.Aop/demo/05-pecl.php
+}
+
case $1 in
clean)
clean
@@ -43,6 +53,12 @@ case $1 in
run)
run
;;
+ demo)
+ demo
+ ;;
+ debug)
+ debug
+ ;;
all)
clean
prepare
diff --git a/demo/README.md b/demo/README.md
new file mode 100644
index 0000000..a52387f
--- /dev/null
+++ b/demo/README.md
@@ -0,0 +1,32 @@
+# Ray.Aop Demo
+
+This is a demonstration of [Ray.Aop](https://github.com/ray-di/Ray.Aop), an Aspect Oriented Programming (AOP) framework for PHP.
+
+## Requirements
+
+- PHP 8.1 or higher
+- rayaop.so PHP extension (must be in ../modules/rayaop.so)
+
+## Running the Demo
+
+The demo can be run in several ways:
+
+1. Using run.php:
+```bash
+php run.php
+```
+
+2. Directly with extension loading:
+```bash
+php -dextension=../modules/rayaop.so aop.php
+```
+
+3. With additional PHP settings:
+```bash
+php -dextension=../modules/rayaop.so -dzend_extension=xdebug.so -dxdebug.mode=debug aop.php
+```
+
+## Project Structure
+
+- `aop.php`: Main demo file that showcases AOP functionality
+- `src/`: Source code directory containing the demo classes
diff --git a/demo/aop.php b/demo/aop.php
new file mode 100644
index 0000000..0435b7d
--- /dev/null
+++ b/demo/aop.php
@@ -0,0 +1,29 @@
+bind(
+ (new Matcher())->any(),
+ new IsContainsMatcher('charge'),
+ [new WeekendBlocker()]
+);
+$aspect->weave(__DIR__. '/src');
+
+try {
+ $billingService = new RealBillingService();
+ method_intercept_enable(true);
+ echo $billingService->chargeOrder();
+} catch (RuntimeException $e) {
+ echo $e->getMessage() . PHP_EOL;
+ exit(1);
+}
diff --git a/demo/composer.json b/demo/composer.json
new file mode 100644
index 0000000..38496ff
--- /dev/null
+++ b/demo/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "ray-di/ext-rayaop",
+ "description": "Demo code using Ray.Aop",
+ "type": "project",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "Demo\\": "src"
+ }
+ },
+ "authors": [
+ {
+ "name": "Akihito Koriyama",
+ "email": "akihito.koriyama@gmail.com"
+ }
+ ],
+ "require": {
+ "ray/aop": "^2.17"
+ }
+}
diff --git a/demo/run.php b/demo/run.php
new file mode 100644
index 0000000..85ad117
--- /dev/null
+++ b/demo/run.php
@@ -0,0 +1,3 @@
+name, $contains) !== false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function matchesMethod(ReflectionMethod $method, array $arguments): bool
+ {
+ [$contains] = $arguments;
+
+ return strpos($method->name, $contains) !== false;
+ }
+}
diff --git a/demo/src/RealBillingService.php b/demo/src/RealBillingService.php
new file mode 100644
index 0000000..bd6b624
--- /dev/null
+++ b/demo/src/RealBillingService.php
@@ -0,0 +1,15 @@
+getMethod()->getName() . ' not allowed on weekends!'
+ );
+ }
+
+ echo $invocation->getMethod()->getName() . ' allowed on weekday.' . PHP_EOL;
+
+ return $invocation->proceed();
+ }
+}
diff --git a/demo/tmp/.gitkeep b/demo/tmp/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/php_rayaop.h b/php_rayaop.h
index 6adbdaf..b2bdba8 100644
--- a/php_rayaop.h
+++ b/php_rayaop.h
@@ -1,22 +1,14 @@
-/* Header guard */
#ifndef PHP_RAYAOP_H
#define PHP_RAYAOP_H
-// #define RAYAOP_DEBUG 1
-
-/* Configuration header */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-/* Required system headers first */
-#include
-
-/* PHP Core headers in correct order */
#include "php.h"
#include "zend.h"
-#include "zend_types.h"
#include "zend_API.h"
+#include "zend_types.h"
#include "zend_ini.h"
#include "php_ini.h"
#include "ext/standard/info.h"
@@ -27,129 +19,81 @@
#include "TSRM.h"
#endif
+/* Export macros */
+#ifdef PHP_WIN32
+# define PHP_RAYAOP_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+# define PHP_RAYAOP_API __attribute__ ((visibility("default")))
+#else
+# define PHP_RAYAOP_API
+#endif
+
/* Constants */
#define MAX_EXECUTION_DEPTH 100
#define PHP_RAYAOP_VERSION "1.0.0"
#define RAYAOP_NS "Ray\\Aop\\"
/* Error codes */
-#define RAYAOP_E_MEMORY_ALLOCATION 1
-#define RAYAOP_E_HASH_UPDATE 2
-#define RAYAOP_E_INVALID_HANDLER 3
-#define RAYAOP_E_MAX_DEPTH_EXCEEDED 4
-
-/* Argument information declarations */
-ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3)
- ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0)
- ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0)
- ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0)
-ZEND_END_ARG_INFO()
-
-ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0)
-ZEND_END_ARG_INFO()
-
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_method_intercept_enable, 0, 1, IS_VOID, 0)
- ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0)
-ZEND_END_ARG_INFO()
-
-/* Debug mode configuration */
-#ifdef RAYAOP_DEBUG
-#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) \
- do { \
- php_printf("RAYAOP DEBUG [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
- } while (0)
-#else
-#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...)
-#endif
-
-/* Declare mutex type */
-#if defined(WIN32)
-#define MUTEX_T HANDLE
-#else
-#define MUTEX_T pthread_mutex_t*
-#endif
-extern MUTEX_T rayaop_mutex;
+#define RAYAOP_E_MEMORY_ALLOCATION 1
+#define RAYAOP_E_HASH_UPDATE 2
+#define RAYAOP_E_INVALID_HANDLER 3
+#define RAYAOP_E_MAX_DEPTH_EXCEEDED 4
+#define RAYAOP_E_NULL_POINTER 5
+#define RAYAOP_E_INVALID_STATE 6
#ifdef ZTS
-#define RAYAOP_G_LOCK() tsrm_mutex_lock(rayaop_mutex)
+extern MUTEX_T rayaop_mutex;
+#define RAYAOP_G_LOCK() tsrm_mutex_lock(rayaop_mutex)
#define RAYAOP_G_UNLOCK() tsrm_mutex_unlock(rayaop_mutex)
#else
#define RAYAOP_G_LOCK()
#define RAYAOP_G_UNLOCK()
#endif
-/* Module entry */
-extern zend_module_entry rayaop_module_entry;
-#define phpext_rayaop_ptr &rayaop_module_entry
-
-/* Interface class entry */
-extern zend_class_entry *ray_aop_method_interceptor_interface_ce;
-
-/* Windows DLL export */
-#ifdef PHP_WIN32
-#define PHP_RAYAOP_API __declspec(dllexport)
-#elif defined(__GNUC__) && __GNUC__ >= 4
-#define PHP_RAYAOP_API __attribute__ ((visibility("default")))
-#else
-#define PHP_RAYAOP_API
-#endif
-
-/* Intercept information structure */
typedef struct _php_rayaop_intercept_info {
- zend_string *class_name; /* Class name to intercept */
- zend_string *method_name; /* Method name to intercept */
- zval handler; /* Intercept handler */
- zend_bool is_enabled; /* Flag to enable/disable interception */
+ zend_string *class_name; /* Class name */
+ zend_string *method_name; /* Method name */
+ zval handler; /* Intercept handler object (only one) */
+ zend_bool is_enabled; /* Enabled flag */
} php_rayaop_intercept_info;
-/* Module globals structure */
ZEND_BEGIN_MODULE_GLOBALS(rayaop)
- HashTable *intercept_ht; /* Intercept hash table */
- zend_bool is_intercepting; /* Intercepting flag */
- uint32_t execution_depth; /* Execution depth counter */
- zend_bool method_intercept_enabled; /* Global interception enable flag */
- uint32_t debug_level; /* Debug level */
+ HashTable *intercept_ht; /* Hash table for intercept information */
+ zend_bool is_intercepting; /* Flag indicating if currently intercepting */
+ uint32_t execution_depth; /* Nesting depth of interception */
+ zend_bool method_intercept_enabled; /* Global enable/disable flag */
+ uint32_t debug_level; /* Debug level */
ZEND_END_MODULE_GLOBALS(rayaop)
-/* Global initializer */
-static void php_rayaop_init_globals(zend_rayaop_globals *globals);
-
-/* Globals access macro */
#ifdef ZTS
#define RAYAOP_G(v) TSRMG(rayaop_globals_id, zend_rayaop_globals *, v)
#else
#define RAYAOP_G(v) (rayaop_globals.v)
#endif
-/* Module functions */
+extern zend_module_entry rayaop_module_entry;
+#define phpext_rayaop_ptr &rayaop_module_entry
+
+extern zend_class_entry *ray_aop_method_interceptor_interface_ce;
+
+/* PHP lifecycle hooks */
PHP_MINIT_FUNCTION(rayaop);
PHP_MSHUTDOWN_FUNCTION(rayaop);
PHP_RINIT_FUNCTION(rayaop);
PHP_RSHUTDOWN_FUNCTION(rayaop);
PHP_MINFO_FUNCTION(rayaop);
-/* Extension functions */
+/* PHP functions */
PHP_FUNCTION(method_intercept);
PHP_FUNCTION(method_intercept_init);
PHP_FUNCTION(method_intercept_enable);
-/* Utility functions */
+/* API functions */
PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message);
-PHP_RAYAOP_API bool php_rayaop_should_intercept(zend_execute_data *execute_data);
+PHP_RAYAOP_API zend_bool php_rayaop_should_intercept(zend_execute_data *execute_data);
PHP_RAYAOP_API char *php_rayaop_generate_key(zend_string *class_name, zend_string *method_name, size_t *key_len);
PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_find_intercept_info(const char *key, size_t key_len);
-
-/* Memory management functions */
PHP_RAYAOP_API void php_rayaop_free_intercept_info(zval *zv);
PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_create_intercept_info(void);
-/* Debug functions */
-#ifdef RAYAOP_DEBUG
-void php_rayaop_debug_dump_intercept_info(void);
-void php_rayaop_debug_print_zval(zval *value);
-#endif
-
-/* Module globals declaration */
-ZEND_EXTERN_MODULE_GLOBALS(rayaop)
-
-#endif /* PHP_RAYAOP_H */
\ No newline at end of file
+#endif /* PHP_RAYAOP_H */
diff --git a/rayaop.c b/rayaop.c
index 31c85b6..3eeb7ca 100644
--- a/rayaop.c
+++ b/rayaop.c
@@ -1,7 +1,3 @@
-#ifdef ZTS
-MUTEX_T rayaop_mutex;
-#endif
-
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@@ -12,23 +8,23 @@ MUTEX_T rayaop_mutex;
ZEND_DECLARE_MODULE_GLOBALS(rayaop)
/* Global variable declarations */
-zend_class_entry *ray_aop_method_interceptor_interface_ce;
+zend_class_entry *ray_aop_method_interceptor_interface_ce = NULL;
static void (*php_rayaop_original_execute_ex)(zend_execute_data *execute_data) = NULL;
/* Argument information for the interceptor method */
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ray_aop_method_interceptor_intercept, 0, 3, IS_MIXED, 0)
- ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0)
- ZEND_ARG_TYPE_INFO(0, method, IS_STRING, 0)
- ZEND_ARG_TYPE_INFO(0, params, IS_ARRAY, 0)
+ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3)
+ ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0)
+ ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0)
ZEND_END_ARG_INFO()
-/* Interface methods */
-static zend_function_entry ray_aop_method_interceptor_interface_methods[] = {
- PHP_ABSTRACT_ME(Ray_Aop_MethodInterceptorInterface, intercept, arginfo_ray_aop_method_interceptor_intercept)
- PHP_FE_END
-};
+ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_method_intercept_enable, 0, 1, IS_VOID, 0)
+ ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0)
+ZEND_END_ARG_INFO()
-/* Module function declarations */
static const zend_function_entry rayaop_functions[] = {
PHP_FE(method_intercept, arginfo_method_intercept)
PHP_FE(method_intercept_init, arginfo_method_intercept_init)
@@ -36,38 +32,47 @@ static const zend_function_entry rayaop_functions[] = {
PHP_FE_END
};
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ray_aop_method_interceptor_intercept, 0, 3, IS_MIXED, 0)
+ ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0)
+ ZEND_ARG_TYPE_INFO(0, method, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO(0, params, IS_ARRAY, 0)
+ZEND_END_ARG_INFO()
+static const zend_function_entry ray_aop_method_interceptor_interface_methods[] = {
+ PHP_ABSTRACT_ME(Ray_Aop_MethodInterceptorInterface, intercept, arginfo_ray_aop_method_interceptor_intercept)
+ PHP_FE_END
+};
-/* Module globals initializer */
-static void php_rayaop_init_globals(zend_rayaop_globals *globals) {
- globals->intercept_ht = NULL;
- globals->is_intercepting = 0;
- globals->execution_depth = 0;
- globals->method_intercept_enabled = 0;
- globals->debug_level = 0;
-}
-
-/* Error handling function */
PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message) {
+ int error_level = E_ERROR;
+ const char *error_type = "Unknown Error";
switch (error_code) {
case RAYAOP_E_MEMORY_ALLOCATION:
- php_error_docref(NULL, E_ERROR, "Memory allocation failed: %s", message);
+ error_type = "Memory Allocation Error";
break;
case RAYAOP_E_HASH_UPDATE:
- php_error_docref(NULL, E_ERROR, "Hash table update failed: %s", message);
+ error_type = "Hash Table Error";
break;
case RAYAOP_E_INVALID_HANDLER:
- php_error_docref(NULL, E_WARNING, "Invalid handler: %s", message);
+ error_type = "Invalid Handler";
+ error_level = E_WARNING;
break;
case RAYAOP_E_MAX_DEPTH_EXCEEDED:
- php_error_docref(NULL, E_WARNING, "Maximum execution depth exceeded: %s", message);
+ error_type = "Max Depth Exceeded";
+ error_level = E_WARNING;
+ break;
+ case RAYAOP_E_NULL_POINTER:
+ error_type = "Null Pointer Error";
+ break;
+ case RAYAOP_E_INVALID_STATE:
+ error_type = "Invalid State";
break;
default:
- php_error_docref(NULL, E_ERROR, "RayAOP Error (%d): %s", error_code, message);
+ break;
}
+ php_error_docref(NULL, error_level, "[RayAOP] %s: %s", error_type, message);
}
-/* Memory management functions */
PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_create_intercept_info(void) {
php_rayaop_intercept_info *info = ecalloc(1, sizeof(php_rayaop_intercept_info));
if (!info) {
@@ -75,6 +80,7 @@ PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_create_intercept_info(void)
return NULL;
}
info->is_enabled = 1;
+ ZVAL_UNDEF(&info->handler);
return info;
}
@@ -94,28 +100,18 @@ PHP_RAYAOP_API void php_rayaop_free_intercept_info(zval *zv) {
RAYAOP_G_UNLOCK();
}
-/* Interception helper functions */
-PHP_RAYAOP_API bool php_rayaop_should_intercept(zend_execute_data *execute_data) {
- if (!RAYAOP_G(method_intercept_enabled)) {
- return false;
+PHP_RAYAOP_API char *php_rayaop_generate_key(zend_string *class_name, zend_string *method_name, size_t *key_len) {
+ if (!class_name || !method_name) {
+ return NULL;
}
-
- if (RAYAOP_G(execution_depth) >= MAX_EXECUTION_DEPTH) {
- php_rayaop_handle_error(RAYAOP_E_MAX_DEPTH_EXCEEDED, "Maximum execution depth reached");
- return false;
+ char *key;
+ int len = spprintf(&key, 0, "%s::%s", ZSTR_VAL(class_name), ZSTR_VAL(method_name));
+ if (len < 0) {
+ return NULL;
+ }
+ if (key_len) {
+ *key_len = (size_t)len;
}
-
- return execute_data &&
- execute_data->func &&
- execute_data->func->common.scope &&
- execute_data->func->common.function_name &&
- !RAYAOP_G(is_intercepting);
-}
-
-PHP_RAYAOP_API char *php_rayaop_generate_key(zend_string *class_name, zend_string *method_name, size_t *key_len) {
- char *key = NULL;
- *key_len = spprintf(&key, 0, "%s::%s", ZSTR_VAL(class_name), ZSTR_VAL(method_name));
- PHP_RAYAOP_DEBUG_PRINT("Generated key: %s", key);
return key;
}
@@ -129,53 +125,47 @@ PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_find_intercept_info(const c
return info;
}
-/* Parameter preparation and cleanup */
-static bool prepare_intercept_params(zend_execute_data *execute_data, zval *params, php_rayaop_intercept_info *info) {
- if (!execute_data->This.value.obj) {
- php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Object instance is NULL");
- return false;
+PHP_RAYAOP_API zend_bool php_rayaop_should_intercept(zend_execute_data *execute_data) {
+ if (!RAYAOP_G(method_intercept_enabled)) {
+ return 0;
}
- ZVAL_OBJ(¶ms[0], execute_data->This.value.obj);
- ZVAL_STR_COPY(¶ms[1], info->method_name);
-
- array_init(¶ms[2]);
- uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
- if (arg_count > 0) {
- zval *args = ZEND_CALL_ARG(execute_data, 1);
- for (uint32_t i = 0; i < arg_count; i++) {
- zval *arg = &args[i];
- if (!Z_ISUNDEF_P(arg)) {
- Z_TRY_ADDREF_P(arg);
- add_next_index_zval(¶ms[2], arg);
- }
- }
+ if (RAYAOP_G(execution_depth) >= MAX_EXECUTION_DEPTH) {
+ return 0;
}
- return true;
-}
-static void cleanup_intercept_params(zval *params) {
- zval_ptr_dtor(¶ms[1]);
- zval_ptr_dtor(¶ms[2]);
-}
+ /* If there's already an exception, do not intercept */
+ if (EG(exception)) {
+ return 0;
+ }
-/* Interception execution */
-static bool execute_intercept_handler(zval *handler, zval *params, zval *retval) {
- zval method_name;
- ZVAL_STRING(&method_name, "intercept");
+ if (!execute_data || !execute_data->func || !execute_data->func->common.scope || !execute_data->func->common.function_name) {
+ return 0;
+ }
- bool success = (call_user_function(NULL, handler, &method_name, retval, 3, params) == SUCCESS);
- zval_ptr_dtor(&method_name);
+ /* Ensure we're not trying to intercept internal functions */
+ if (execute_data->func->type == ZEND_INTERNAL_FUNCTION) {
+ return 0;
+ }
- if (!success) {
- php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Failed to execute intercept handler");
+ if (RAYAOP_G(is_intercepting)) {
+ return 0;
}
- return success;
+ return 1;
}
-/* Fixed rayaop_execute_ex function */
static void rayaop_execute_ex(zend_execute_data *execute_data) {
+ if (EG(exception)) {
+ /* If exception is already set, just run original execute_ex */
+ if (php_rayaop_original_execute_ex) {
+ php_rayaop_original_execute_ex(execute_data);
+ } else {
+ zend_execute_ex(execute_data);
+ }
+ return;
+ }
+
if (!php_rayaop_should_intercept(execute_data)) {
if (php_rayaop_original_execute_ex) {
php_rayaop_original_execute_ex(execute_data);
@@ -186,61 +176,128 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) {
}
RAYAOP_G(execution_depth)++;
- PHP_RAYAOP_DEBUG_PRINT("Execution depth: %d", RAYAOP_G(execution_depth));
zend_function *func = execute_data->func;
- size_t key_len;
+ if (EG(exception)) {
+ goto fallback;
+ }
+
+ size_t key_len = 0;
char *key = php_rayaop_generate_key(func->common.scope->name, func->common.function_name, &key_len);
+ if (!key) {
+ goto fallback;
+ }
php_rayaop_intercept_info *info = php_rayaop_find_intercept_info(key, key_len);
- if (info && info->is_enabled) {
- PHP_RAYAOP_DEBUG_PRINT("Executing intercept for %s", key);
+ if (!info || !info->is_enabled) {
+ efree(key);
+ goto fallback;
+ }
- if (Z_TYPE(info->handler) != IS_OBJECT) {
- php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Invalid interceptor type");
- if (php_rayaop_original_execute_ex) {
- php_rayaop_original_execute_ex(execute_data);
- }
- } else {
- zval retval;
- zval params[3];
-
- prepare_intercept_params(execute_data, params, info);
- if (!prepare_intercept_params(execute_data, params, info)) {
- cleanup_intercept_params(params);
- RAYAOP_G(is_intercepting) = 0;
- if (php_rayaop_original_execute_ex) {
- php_rayaop_original_execute_ex(execute_data);
- } else {
- zend_execute_ex(execute_data);
+ if (Z_TYPE(info->handler) != IS_OBJECT) {
+ efree(key);
+ goto fallback;
+ }
+
+ if (EG(exception)) {
+ efree(key);
+ goto fallback;
+ }
+
+ zval retval;
+ zval params[3];
+ ZVAL_UNDEF(&retval);
+
+ if (Z_TYPE(execute_data->This) != IS_OBJECT) {
+ efree(key);
+ goto fallback;
+ }
+
+ ZVAL_OBJ(¶ms[0], Z_OBJ(execute_data->This));
+ Z_ADDREF_P(¶ms[0]);
+
+ ZVAL_STR(¶ms[1], zend_string_copy(info->method_name));
+ array_init(¶ms[2]);
+
+ uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
+ if (arg_count > 0 && !EG(exception)) {
+ zval *args = ZEND_CALL_ARG(execute_data, 1);
+ if (args && !EG(exception)) {
+ for (uint32_t i = 0; i < arg_count; i++) {
+ zval *arg = &args[i];
+ if (!Z_ISUNDEF_P(arg) && !EG(exception)) {
+ Z_TRY_ADDREF_P(arg);
+ if (add_next_index_zval(¶ms[2], arg) == FAILURE) {
+ Z_TRY_DELREF_P(arg);
+ break;
+ }
}
- efree(key);
- RAYAOP_G(execution_depth)--;
- return;
}
- RAYAOP_G(is_intercepting) = 1;
+ }
+ }
- ZVAL_UNDEF(&retval);
- if (execute_intercept_handler(&info->handler, params, &retval)) {
- if (!Z_ISUNDEF(retval) && execute_data->return_value) {
- ZVAL_COPY(execute_data->return_value, &retval);
- }
- zval_ptr_dtor(&retval);
+ if (EG(exception)) {
+ /* Exception occurred during arg processing */
+ goto cleanup;
+ }
+
+ zval method_name;
+ ZVAL_STRING(&method_name, "intercept");
+
+ RAYAOP_G(is_intercepting) = 1;
+ int call_result = call_user_function(NULL, &info->handler, &method_name, &retval, 3, params);
+ if (call_result == SUCCESS && !EG(exception)) {
+ if (execute_data->return_value) {
+ if (!Z_ISUNDEF(retval)) {
+ ZVAL_COPY(execute_data->return_value, &retval); // Propagate explicit return values
+ } else {
+ ZVAL_NULL(execute_data->return_value); // Handle void or empty returns
}
+ }
+ } else if (call_result != SUCCESS) {
+ char error_msg[256];
+ snprintf(error_msg, sizeof(error_msg),
+ "Interceptor call failed for %s::%s",
+ ZSTR_VAL(info->class_name),
+ ZSTR_VAL(info->method_name));
+ php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, error_msg);
+ }
- cleanup_intercept_params(params);
- RAYAOP_G(is_intercepting) = 0;
+cleanup:
+ zval_ptr_dtor(&retval);
+ zval_ptr_dtor(&method_name);
+ zval_ptr_dtor(¶ms[1]);
+ zval_ptr_dtor(¶ms[2]);
+ zval_ptr_dtor(¶ms[0]);
+ RAYAOP_G(is_intercepting) = 0;
+ efree(key);
+
+ /* インターセプタ実行が完了し、例外が無い場合はここでreturnし、fallbackへ行かない */
+ if (!EG(exception)) {
+ RAYAOP_G(execution_depth)--;
+ return;
+ }
+
+fallback:
+ RAYAOP_G(execution_depth)--;
+
+ if (EG(exception)) {
+ /* If there's an exception now, just fallback to original or zend_execute_ex */
+ if (php_rayaop_original_execute_ex) {
+ php_rayaop_original_execute_ex(execute_data);
+ } else {
+ zend_execute_ex(execute_data);
}
} else {
+ /* If no exception and no interception, call original */
if (php_rayaop_original_execute_ex) {
php_rayaop_original_execute_ex(execute_data);
+ } else {
+ zend_execute_ex(execute_data);
}
}
-
- efree(key);
- RAYAOP_G(execution_depth)--;
}
-/* Implementation of method_intercept function */
+
PHP_FUNCTION(method_intercept) {
char *class_name, *method_name;
size_t class_name_len, method_name_len;
@@ -264,24 +321,41 @@ PHP_FUNCTION(method_intercept) {
char *key;
size_t key_len;
key = php_rayaop_generate_key(info->class_name, info->method_name, &key_len);
-
- RAYAOP_G_LOCK();
- if (zend_hash_str_update_ptr(RAYAOP_G(intercept_ht), key, key_len, info) == NULL) {
- RAYAOP_G_UNLOCK();
- efree(key);
+ if (!key) {
zval tmp_zv;
ZVAL_PTR(&tmp_zv, info);
php_rayaop_free_intercept_info(&tmp_zv);
- php_rayaop_handle_error(RAYAOP_E_HASH_UPDATE, "Failed to update intercept hash table");
RETURN_FALSE;
}
+
+ RAYAOP_G_LOCK();
+ php_rayaop_intercept_info *existing = zend_hash_str_find_ptr(RAYAOP_G(intercept_ht), key, key_len);
+ if (existing) {
+ /* 上書き: 前のhandlerを解放して新しいhandlerを登録 */
+ zval_ptr_dtor(&existing->handler);
+ ZVAL_COPY(&existing->handler, interceptor);
+ zend_string_release(existing->class_name);
+ zend_string_release(existing->method_name);
+ existing->class_name = info->class_name;
+ existing->method_name = info->method_name;
+ efree(info);
+ } else {
+ if (zend_hash_str_update_ptr(RAYAOP_G(intercept_ht), key, key_len, info) == NULL) {
+ RAYAOP_G_UNLOCK();
+ efree(key);
+ zval tmp_zv;
+ ZVAL_PTR(&tmp_zv, info);
+ php_rayaop_free_intercept_info(&tmp_zv);
+ php_rayaop_handle_error(RAYAOP_E_HASH_UPDATE, "Failed to update intercept hash table");
+ RETURN_FALSE;
+ }
+ }
RAYAOP_G_UNLOCK();
efree(key);
RETURN_TRUE;
}
-/* method_intercept_init function fix */
PHP_FUNCTION(method_intercept_init) {
RAYAOP_G_LOCK();
if (RAYAOP_G(intercept_ht)) {
@@ -299,7 +373,6 @@ PHP_FUNCTION(method_intercept_init) {
RETURN_TRUE;
}
-/* Implementation of method_intercept_enable function */
PHP_FUNCTION(method_intercept_enable) {
zend_bool enable;
ZEND_PARSE_PARAMETERS_START(1, 1)
@@ -310,37 +383,28 @@ PHP_FUNCTION(method_intercept_enable) {
RAYAOP_G(method_intercept_enabled) = enable;
if (enable) {
zend_execute_ex = rayaop_execute_ex;
- PHP_RAYAOP_DEBUG_PRINT("Method intercept enabled");
} else {
zend_execute_ex = php_rayaop_original_execute_ex;
- PHP_RAYAOP_DEBUG_PRINT("Method intercept disabled");
}
RAYAOP_G_UNLOCK();
}
-/* Module initialization */
PHP_MINIT_FUNCTION(rayaop) {
#ifdef ZTS
- /* First, allocate TSRMG */
- ts_allocate_id(&rayaop_globals_id, sizeof(zend_rayaop_globals),
- (ts_allocate_ctor)php_rayaop_init_globals, NULL);
-
- /* Then initialize mutex */
+ ts_allocate_id(&rayaop_globals_id, sizeof(zend_rayaop_globals), NULL, NULL);
rayaop_mutex = tsrm_mutex_alloc();
+
if (!rayaop_mutex) {
+ ts_free_id(rayaop_globals_id);
php_error_docref(NULL, E_ERROR, "Failed to allocate mutex for RayAOP");
return FAILURE;
}
-#else
- php_rayaop_init_globals(&rayaop_globals);
#endif
- // Register the interface
zend_class_entry ce;
INIT_NS_CLASS_ENTRY(ce, "Ray\\Aop", "MethodInterceptorInterface", ray_aop_method_interceptor_interface_methods);
ray_aop_method_interceptor_interface_ce = zend_register_internal_interface(&ce);
- // Initialize other settings
RAYAOP_G(method_intercept_enabled) = 1;
RAYAOP_G(debug_level) = 0;
php_rayaop_original_execute_ex = zend_execute_ex;
@@ -349,11 +413,8 @@ PHP_MINIT_FUNCTION(rayaop) {
return SUCCESS;
}
-
-/* Module shutdown */
PHP_MSHUTDOWN_FUNCTION(rayaop) {
#ifdef ZTS
- /* Free mutex */
if (rayaop_mutex) {
tsrm_mutex_free(rayaop_mutex);
rayaop_mutex = NULL;
@@ -392,7 +453,6 @@ PHP_RINIT_FUNCTION(rayaop) {
return SUCCESS;
}
-/* Request shutdown */
PHP_RSHUTDOWN_FUNCTION(rayaop) {
RAYAOP_G_LOCK();
if (RAYAOP_G(intercept_ht)) {
@@ -404,82 +464,17 @@ PHP_RSHUTDOWN_FUNCTION(rayaop) {
return SUCCESS;
}
-/* Module info */
PHP_MINFO_FUNCTION(rayaop) {
php_info_print_table_start();
php_info_print_table_header(2, "RayAOP Support", "enabled");
php_info_print_table_row(2, "Version", PHP_RAYAOP_VERSION);
php_info_print_table_row(2, "Debug Level",
- RAYAOP_G(debug_level) == 0 ? "None" :
- RAYAOP_G(debug_level) == 1 ? "Basic" : "Verbose");
+ RAYAOP_G(debug_level) == 0 ? "None" : "Basic");
php_info_print_table_row(2, "Method Intercept",
RAYAOP_G(method_intercept_enabled) ? "Enabled" : "Disabled");
php_info_print_table_end();
}
-#ifdef RAYAOP_DEBUG
-/* Debug functions */
-void php_rayaop_debug_print_zval(zval *value) {
- if (!value) {
- php_printf("NULL\n");
- return;
- }
-
- switch (Z_TYPE_P(value)) {
- case IS_NULL:
- php_printf("NULL\n");
- break;
- case IS_TRUE:
- php_printf("bool(true)\n");
- break;
- case IS_FALSE:
- php_printf("bool(false)\n");
- break;
- case IS_LONG:
- php_printf("int(%ld)\n", Z_LVAL_P(value));
- break;
- case IS_DOUBLE:
- php_printf("float(%g)\n", Z_DVAL_P(value));
- break;
- case IS_STRING:
- php_printf("string(%d) \"%s\"\n", Z_STRLEN_P(value), Z_STRVAL_P(value));
- break;
- case IS_ARRAY:
- php_printf("array(%d) {...}\n", zend_hash_num_elements(Z_ARRVAL_P(value)));
- break;
- case IS_OBJECT:
- php_printf("object(%s)#%d {...}\n",
- Z_OBJCE_P(value)->name->val, Z_OBJ_HANDLE_P(value));
- break;
- default:
- php_printf("unknown type(%d)\n", Z_TYPE_P(value));
- }
-}
-
-void php_rayaop_debug_dump_intercept_info(void) {
- RAYAOP_G_LOCK();
- if (RAYAOP_G(intercept_ht)) {
- php_printf("=== Intercept Information Dump ===\n");
- php_rayaop_intercept_info *info;
- zend_string *key;
- ZEND_HASH_FOREACH_STR_KEY_PTR(RAYAOP_G(intercept_ht), key, info) {
- if (key && info) {
- php_printf("Key: %s\n", ZSTR_VAL(key));
- php_printf(" Class: %s\n", ZSTR_VAL(info->class_name));
- php_printf(" Method: %s\n", ZSTR_VAL(info->method_name));
- php_printf(" Enabled: %d\n", info->is_enabled);
- php_printf(" Handler type: %d\n", Z_TYPE(info->handler));
- }
- } ZEND_HASH_FOREACH_END();
- php_printf("================================\n");
- } else {
- php_printf("No intercept information available\n");
- }
- RAYAOP_G_UNLOCK();
-}
-#endif
-
-/* Module entry */
zend_module_entry rayaop_module_entry = {
STANDARD_MODULE_HEADER,
"rayaop",
diff --git a/smoke.php b/smoke.php
index d4f5e42..e625e44 100644
--- a/smoke.php
+++ b/smoke.php
@@ -7,7 +7,7 @@ public function intercept(object $object, string $method, array $params): mixed
echo "Intercepted: " . get_class($object) . "::{$method}\n";
echo "Arguments: " . json_encode($params) . "\n";
- // 元のメソッドを呼び出し、結果を返す
+ // Call the original method and return the result
return call_user_func_array([$object, $method], $params);
}
}
@@ -40,11 +40,15 @@ public function nonInterceptedMethod($arg)
echo "Calling testMethod (should be intercepted)\n";
$result1 = $test->testMethod("test");
-echo "Result: $result1\n";
+echo "$result1\n";
echo "\nCalling nonInterceptedMethod (should not be intercepted)\n";
$result2 = $test->nonInterceptedMethod("test");
-echo "Result: $result2\n";
+echo "$result2\n";
echo "\nScript execution completed\n";
+$success = $result1 === 'Result: test' && $result2 === 'Non-intercepted result: test';
+echo $success ? 'Test Success.' : 'Test Failure.';
+echo PHP_EOL;
+exit($success ? 0 : 1);
diff --git a/tests/001-rayaop-basic.phpt b/tests/001-rayaop-basic.phpt
index dacc4b3..5491d77 100644
--- a/tests/001-rayaop-basic.phpt
+++ b/tests/001-rayaop-basic.phpt
@@ -32,6 +32,8 @@ var_dump(method_intercept_init());
// Register the interceptor
var_dump(method_intercept('TestClass', 'testMethod', new TestInterceptor()));
+method_intercept_enable(true);
+
$test = new TestClass();
echo $test->testMethod(" method called") . "\n";
@@ -44,4 +46,4 @@ bool(true)
bool(true)
bool(true)
Intercepted: Original method called
-Original method called without interception
\ No newline at end of file
+Original method called without interception
diff --git a/tests/008-rayaop-thread-safety.phpt b/tests/008-rayaop-thread-safety.phpt
index c537c79..ec2da8a 100644
--- a/tests/008-rayaop-thread-safety.phpt
+++ b/tests/008-rayaop-thread-safety.phpt
@@ -31,20 +31,23 @@ class TestClass {
method_intercept_init();
for ($i = 0; $i < 3; $i++) {
- $result = method_intercept(
+ $res = method_intercept(
TestClass::class,
'method',
new SafetyTestInterceptor($i)
);
- echo "" . PHP_EOL;
+ echo "" . PHP_EOL;
}
$test = new TestClass();
$paramsArray = [null, 'test', 'error', 'another test'];
+method_intercept_enable(true);
+
foreach ($paramsArray as $i => $param) {
$paramString = isset($param) ? $param : '';
echo "" . PHP_EOL;
+
try {
$result = $test->method($param);
echo "" . PHP_EOL;
@@ -54,7 +57,6 @@ foreach ($paramsArray as $i => $param) {
}
echo "" . PHP_EOL;
-?>
--EXPECT--
diff --git a/tests/010-rayaop-void-return.phpt b/tests/010-rayaop-void-return.phpt
new file mode 100644
index 0000000..19c3798
--- /dev/null
+++ b/tests/010-rayaop-void-return.phpt
@@ -0,0 +1,52 @@
+--TEST--
+RayAOP void return handling test
+--FILE--
+$method(...$params);
+ echo "After void method\n";
+ return $result;
+ }
+}
+
+class TestClass {
+ public function voidMethod() {
+ echo "Executing void method\n";
+ // 明示的なreturnなし
+ }
+
+ public function emptyReturnMethod() {
+ echo "Executing empty return method\n";
+ return; // 明示的な空return
+ }
+}
+
+method_intercept_init();
+method_intercept(TestClass::class, 'voidMethod', new VoidTestInterceptor());
+method_intercept(TestClass::class, 'emptyReturnMethod', new VoidTestInterceptor());
+method_intercept_enable(true);
+
+$test = new TestClass();
+
+echo "Testing void method:\n";
+$result = $test->voidMethod();
+var_dump($result);
+
+echo "\nTesting empty return method:\n";
+$result = $test->emptyReturnMethod();
+var_dump($result);
+
+--EXPECT--
+Testing void method:
+Before void method
+Executing void method
+After void method
+NULL
+
+Testing empty return method:
+Before void method
+Executing empty return method
+After void method
+NULL
\ No newline at end of file