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-di logo -[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