Skip to content

Commit

Permalink
Merge pull request #7 from ray-di/method_intercept_init2
Browse files Browse the repository at this point in the history
 Introduce method_intercept_init(),  method_intercept_enable() function
  • Loading branch information
koriym authored Nov 5, 2024
2 parents 98d3b5e + f50ea2f commit 08eed49
Show file tree
Hide file tree
Showing 14 changed files with 883 additions and 571 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

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

- name: Set up PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
Expand All @@ -34,9 +34,13 @@ jobs:
./configure
make
- name: Create php.ini
run: |
echo "extension=$(pwd)/modules/rayaop.so" > php.ini
- name: Run tests
id: run_tests
run: make test
run: PHP_INI_SCAN_DIR=$(pwd) make test
continue-on-error: true

- name: Run demo with debug logging
Expand Down Expand Up @@ -78,15 +82,15 @@ jobs:
- name: Upload Valgrind log file
if: (steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure') && always()
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: valgrind-log
path: valgrind-out.txt
if-no-files-found: warn

- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: test-logs
path: |
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@
# Ignore directories and its content
/Makefile.fragments
/Makefile.objects
/cmake-build-debug
/cmake-build-debug
/modules/rayaop.so
180 changes: 76 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,165 +4,137 @@

<img src="https://ray-di.github.io/images/logo.svg" alt="ray-di logo" width="150px;">

A PHP extension that provides Aspect-Oriented Programming (AOP) functionality for method interception, designed to complement Ray.Aop.
Low-level PHP extension that provides core method interception functionality for [Ray.Aop](https://github.com/ray-di/Ray.Aop). While this extension can be used standalone, it is designed to be a foundation for Ray.Aop's more sophisticated AOP features.

## Features

- Efficient method interception for specific classes
- Apply custom logic before and after method execution
- Works with `final` classes and methods
- Efficient low-level method interception
- Support for intercepting final classes and methods
- Full parameter and return value modification support
- Works seamlessly with the `new` keyword
- Thread-safe operation support

## Requirements

- PHP 8.1 or higher
- Linux, macOS, or Windows with appropriate build tools
- Thread-safe PHP build recommended for multi-threaded environments

## Installation

1. Clone the repository:

```
```bash
git clone https://github.com/ray-di/ext-rayaop.git
cd ext-rayaop
```

2. Build and install the extension:

```
```bash
phpize
./configure
make --enable-rayaop-quiet // Suppress E_NOTICE to indicate experimental.
make
make install
```

3. Add the following line to your php.ini file:

```ini
extension=rayaop.so # For Unix/Linux
extension=rayaop.dll # For Windows
```
extension=rayaop.so

4. Verify installation:
```bash
php -m | grep rayaop
```

## About this Extension
## Design Decisions

This PECL extension is designed to enhance Ray.Aop by providing method interception capabilities at a lower level. While it can be used independently, it's primarily intended to be used in conjunction with Ray.Aop for optimal functionality.
This extension provides minimal, high-performance method interception capabilities:

Key points:
- The extension intentionally binds only one interceptor per method for simplicity and performance.
- Multiple interceptor chaining should be implemented in PHP code, either using Ray.Aop or custom implementation.
- The main advantages are the ability to intercept `final` classes/methods and unrestricted use of the `new` keyword.
- One interceptor per method: The extension supports a single active interceptor per method, with the last registered interceptor taking precedence
- 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

## Usage
## Relationship with Ray.Aop

### Defining an Interceptor
This extension provides low-level method interception, while [Ray.Aop](https://github.com/ray-di/Ray.Aop) offers high-level AOP features:

Create a class that implements the `Ray\Aop\MethodInterceptorInterface`:
Ray.Aop provides:
- Conditional interception using Matchers
- Multiple interceptors per method
- Attribute/Annotation based interception
- Sophisticated AOP features

```php
namespace Ray\Aop {
interface MethodInterceptorInterface
{
public function intercept(object $object, string $method, array $params): mixed;
}
}
When both are used together:
- Ray.Aop handles the high-level AOP logic
- This extension provides the low-level interception mechanism
- Ray.Aop automatically utilizes this extension when available for better performance

## Basic Usage

class MyInterceptor implements Ray\Aop\MethodInterceptorInterface
### Simple Interceptor
```php
class LoggingInterceptor implements Ray\Aop\MethodInterceptorInterface
{
public function intercept(object $object, string $method, array $params): mixed
{
echo "Before method execution\n";
$result = call_user_func_array([$object, $method], $params);
echo "After method execution\n";
echo "Before {$method}\n";
$result = $object->$method(...$params);
echo "After {$method}\n";
return $result;
}
}
```

### Registering an Interceptor

Use the `method_intercept` function to register an interceptor for a specific class and method:

```php
$interceptor = new MyInterceptor();
method_intercept('TestClass', 'testMethod', $interceptor);
// Register the interceptor
method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor());
```

### Complete Example

### Method Interception Setup
```php
class TestClass
{
public function testMethod($arg)
{
echo "TestClass::testMethod($arg) called\n";
return "Result: $arg";
}
}
// Initialize the interception system
method_intercept_init();

$interceptor = new MyInterceptor();
method_intercept('TestClass', 'testMethod', $interceptor);
// Enable method interception
method_intercept_enable(true);

$test = new TestClass();
$result = $test->testMethod("test");
echo "Final result: $result\n";
// Register interceptors
method_intercept(MyClass::class, 'myMethod', new MyInterceptor());
```

Output:
```
Before method execution
TestClass::testMethod(test) called
After method execution
Final result: Result: test
```

## Integration with Ray.Aop

For more complex AOP scenarios, it's recommended to use this extension in combination with [Ray.Aop](https://github.com/ray-di/Ray.Aop). Ray.Aop provides a higher-level API for managing multiple interceptors and more advanced AOP features.

## The Power of AOP

Aspect-Oriented Programming (AOP) is a powerful paradigm that complements Object-Oriented Programming (OOP) in building more flexible and maintainable software systems. By using AOP:

1. **Separation of Concerns**: You can cleanly separate cross-cutting concerns (like logging, security, or transaction management) from your core business logic.

2. **Enhanced Modularity**: AOP allows you to modularize system-wide concerns that would otherwise be scattered across multiple classes.

3. **Improved Code Reusability**: Aspects can be reused across different parts of your application, reducing code duplication.

4. **Easier Maintenance**: By centralizing certain behaviors, AOP can make your codebase easier to maintain and evolve over time.

5. **Non-invasive Changes**: You can add new behaviors to existing code without modifying the original classes, adhering to the Open/Closed Principle.

6. **Dynamic Behavior Modification**: With this PECL extension, you can even apply aspects to final classes and methods, providing unprecedented flexibility in your system design.
## Development

By combining the strengths of OOP and AOP, developers can create more robust, flexible, and easier-to-maintain software architectures. This PECL extension, especially when used in conjunction with Ray.Aop, opens up new possibilities for structuring your PHP applications, allowing you to tackle complex problems with elegance and efficiency.

## Build Script

The `build.sh` script provides various operations for building and managing the extension:
### Build Script
```bash
./build.sh clean # Clean build environment
./build.sh prepare # Prepare build environment
./build.sh build # Build extension
./build.sh run # Run extension
./build.sh all # Execute all steps
```

```sh
./build.sh clean # Clean the build environment
./build.sh prepare # Prepare the build environment
./build.sh build # Build the extension
./build.sh run # Run the extension
./build.sh all # Execute all the above steps
### Testing
```bash
make test
```

Use `./build.sh all` for a complete build and installation process.
For specific tests:
```bash
make test TESTS="-v tests/your_specific_test.phpt"
```

## Running Tests
## License

To run the tests for this extension, use the following command:
[MIT License](LICENSE)

```sh
make test
```
## Author

This command will execute the PHP extension's test suite, which is the standard method for testing PHP extensions.
Akihito Koriyama

If you need to run specific tests or want more verbose output, you can use:
This extension was developed with the assistance of AI pair programming, which helped navigate the complexities of PHP extension development and PECL standards.

```sh
make test TESTS="-v tests/your_specific_test.phpt"
```
## Acknowledgments

Replace `your_specific_test.phpt` with the actual test file you want to run.
This project was created using [JetBrains CLion](https://www.jetbrains.com/clion/), which is available for free with an [Open Source License](https://www.jetbrains.com/community/opensource/).

We'd like to express our gratitude to JetBrains for providing such a powerful and user-friendly development environment.
30 changes: 6 additions & 24 deletions config.m4
Original file line number Diff line number Diff line change
@@ -1,44 +1,26 @@
dnl $Id$
dnl config.m4 for extension rayaop

dnl Include PECL configuration macro
dnl link https://github.com/php/pecl-tools/blob/master/autoconf/pecl.m4
sinclude(./autoconf/pecl.m4)
PHP_ARG_ENABLE(rayaop, whether to enable rayaop,
[ --enable-rayaop Enable rayaop])

dnl Include macro for detecting PHP executable
dnl link https://github.com/php/pecl-tools/blob/master/autoconf/php-executable.m4
sinclude(./autoconf/php-executable.m4)

dnl Initialize PECL extension
dnl link https://github.com/php/pecl-tools/blob/master/pecl.m4#L229
PECL_INIT([rayaop])

dnl Add configuration option to enable the extension
dnl link https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/External-Shell-Variables.html
PHP_ARG_ENABLE(rayaop, whether to enable rayaop, [ --enable-rayaop Enable rayaop])

dnl Process if the extension is enabled
if test "$PHP_RAYAOP" != "no"; then
dnl Define whether the extension is enabled
dnl link https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/Defining-Variables.html
AC_DEFINE(HAVE_RAYAOP, 1, [whether rayaop is enabled])

dnl Add new PHP extension
dnl link https://www.phpinternalsbook.com/build_system/build_system.html
PHP_NEW_EXTENSION(rayaop, rayaop.c, $ext_shared)

dnl Add Makefile fragment
dnl link https://www.phpinternalsbook.com/build_system/build_system.html#php-add-makefile-fragment
PHP_ADD_MAKEFILE_FRAGMENT

dnl Add instruction to install header files
dnl link https://www.phpinternalsbook.com/build_system/build_system.html#php-install-headers
PHP_INSTALL_HEADERS([ext/rayaop], [php_rayaop.h])

dnl Add quiet mode option
PHP_ARG_ENABLE(rayaop-quiet, whether to suppress experimental notices,
[ --enable-rayaop-quiet Suppress experimental notices], no, yes)
[ --enable-rayaop-quiet Suppress experimental notices], no, yes)

if test "$PHP_RAYAOP_QUIET" != "no"; then
AC_DEFINE(RAYAOP_QUIET, 1, [Whether to suppress experimental notices])
AC_DEFINE(RAYAOP_QUIET, 1, [Whether to suppress experimental notices])
fi
fi
fi
Loading

0 comments on commit 08eed49

Please sign in to comment.