Skip to content

Commit

Permalink
Merge pull request #10 from pimcore/proxy-docu
Browse files Browse the repository at this point in the history
proxy-docu
  • Loading branch information
herbertroth authored Aug 11, 2023
2 parents 65be88d + 42b1d33 commit eaaeb7b
Show file tree
Hide file tree
Showing 3 changed files with 316 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ Static Resolver Bundle is designed to encapsulate the usage of static calls with

## Documentation Overview
- [Installation](doc/01_Installation.md)
- [Resolver Service Usage](doc/02_Resolver_Service_Usage.md)
- [Resolver Service Usage](doc/02_Resolver_Service_Usage.md)
- [Event Proxy Service Usage](doc/03_Event_Proxy_Service_Usage.md)
- [Proxy Service Usage](doc/04_Proxy_Service_Usage.md)
119 changes: 119 additions & 0 deletions doc/03_Event_Proxy_Service_Usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@

# Event Proxy Service Usage

## Overview

`EventProxyService` is a service class that provides functionality to wrap an object's instance with `pre` and `post` method interceptors. These interceptors are respectively triggered right before or after the invocation of the specified methods.

When an interceptor is triggered, an event is dispatched using the `EventDispatcher`. The event name is composed of the lower case fully-qualified class name of the original object (with backslashes replaced by dots), the lower case method name, and the prefix (`.pre` or `.post`).

For example, for a pre-interceptor on the `save` method of a class named `App\Entity\User`, the event name will be `app.entity.user.save.pre`.

## `getEventDispatcherProxy()` Method


This method creates a proxy of the given instance and binds `pre` and `post` method interceptors to it.

```php
public function getEventDispatcherProxy(
object $instance,
array $preInterceptors = [],
array $postInterceptors = []
): object;
```

**Parameters:**

- `$instance`: The object instance that needs to be proxied.
- `$preInterceptors` (optional): An array of method names that should be intercepted before their invocation.
- `$postInterceptors` (optional): An array of method names that should be intercepted after their invocation.

**Returns:**

- A proxy of the given `$instance`.

:::info

Final classes can't be proxied.

:::

---

## Example Usage With Symfony's Dependency Injection

### Configuration (`services.yaml`)

```yaml
services:
# ... [other service definitions]

App\EventListener\InterceptorListener:
tags:
- { name: kernel.event_listener, event: 'app.entity.user.save.pre', method: 'onUserSavePre' }
- { name: kernel.event_listener, event: 'app.entity.user.save.post', method: 'onUserSavePost' }
```
### Listener Example
To respond to the dispatched events when interceptors are triggered, you can set up listeners. Here's an example of a listener for the `pre` interceptor of the `save` method on the `App\Entity\User` class:

```php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\GenericEvent;
class InterceptorListener
{
public function onUserSavePre(GenericEvent $event)
{
/** @var User $userInstance */
$userInstance = $event->getSubject();
// Your custom logic here. For instance:
// Logging, modifying data before save, etc.
}
public function onUserSavePost(GenericEvent $event)
{
/** @var User $userInstance */
$userInstance = $event->getSubject();
// Your custom logic here. For instance:
// Logging, modifying data after save, etc.
}
}
```

### Using `EventProxyService` in a Controller

```php
use Pimcore\Bundle\StaticResolverBundle\Proxy\Service\EventProxyService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\User;
class YourController extends AbstractController
{
public function someAction(EventProxyService $eventProxyService)
{
$originalObject = new User::getById('12');
$proxyObject = $eventProxyService->getEventDispatcherProxy(
$originalObject,
['save'], // Assuming 'save' is the method we want to pre-intercept
['save'] // Assuming 'save' is the method we want to post-intercept
);
$proxyObject->setLastname('Doe');
$proxyObject->save(); // This will trigger the interceptor(s) for the save method
// Your remaining code
}
}
```

:::info

When the pre-interceptor for the `save` method is triggered, the `onUserSavePre` method in the `InterceptorListener` class will be executed, and you can handle the event as needed.

:::
194 changes: 194 additions & 0 deletions doc/04_Proxy_Service_Usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Proxy Service Usage

## Overview

The `ProxyService` class offers the capability to generate proxy objects for specified classes and methods. It provides methods to create proxies in various configurations including a strict or decorator mode which restricts the proxy object to only expose methods specified by an interface.

## Methods

### `getProxyObject()`

- Retrieve a proxy object for a given class and method.
- The proxy will contain all methods of the original class.

- **Parameters:**
- `$className` (string): The fully qualified class name.
- `$method` (string): The method name to call.
- `$args` (array, optional): Arguments to pass to the method. Default is an empty array.

- **Returns:** object|null

### `getStrictProxyObject()`

- Retrieve a proxy object that strictly implements a specified interface.
- The proxy will only contain the methods of the specified interface, allowing you to limit access to methods.
- All methods from the given interface must be callabe in the original class.

- **Parameters:**
- `$className` (string): The fully qualified class name.
- `$method` (string): The method name to call.
- `$interface` (string): The interface that the returned proxy object must implement.
- `$args` (array, optional): Arguments to pass to the method. Default is an empty array.

- **Returns:** object|null

### `getDecoratorProxy()`

- Generate a proxy object that can optionally implement a specified interface.
- If an interface is provided, the proxy will only contain the methods of that interface.
- The interface can also include methods not present in the original class.
* These methods must be addressed by the original class's magic PHP functions, like `__call`.

- **Parameters:**
- `$className` (string): The fully qualified class name.
- `$method` (string): The method name to call.
- `$interface` (string, optional): The interface that the returned proxy object can implement.
- `$args` (array, optional): Arguments to pass to the method. Default is an empty array.

- **Returns:** object|null

## Example Extending UserInterface and Decorator Proxy
Let's suppose you wish to extend the functionality of the `User` class with a new method `getAdditionalData()`:

```php
interface MyExtendedInterface {
public function getFirstName();
public function getAdditionalData();
}
```

```php
$proxyService = new ProxyService(new RemoteObjectFactory());
$decoratorProxy = $proxyService->getDecoratorProxy(User::class, 'getById', MyExtendedInterface::class, ['12']);
```
When using the `getDecoratorProxy()` method with `MyExtendedInterface`, the `User` class should look like:

```php
class User {

private string $firstName;


public function __construct($firstName, $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}

public static function getById($id): User {
// Code to get user by id
}

public function getFirstName(): string
return $this->firstName;
}

public function __call($name, $arguments) {
// Handle methods that aren't explicitly defined in User but are in MyExtendedInterface
if ($name == "getAdditionalData") {
// logic for getAdditionalData
}
}
}
```

## Example Using an Incompatible Interface With Strict Proxy

Let's say you mistakenly use an interface that expects a method that isn't in the `User` class:

```php
interface IncompatibleInterface {
public function getNonExistentMethod();
}
```
### User Class

```php
class User{
private $firstName;
private $lastName;

public function __construct($firstName, $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}

public static function getById($id): User {
// Code to get user by id
}

public function getFirstName() {
return $this->firstName;
}

public function getLastName() {
return $this->lastName;
}
}
```
If you attempt to use `IncompatibleInterface` with the ProxyService, it will result in an error because the `User` class doesn't have `getNonExistentMethod()`.

```php
try {
$proxyService = new ProxyService(new RemoteObjectFactory());
try {
$strictProxyObject = $proxyService->getStrictProxyObject(User::class, 'getbyId', IncompatibleInterface::class, ['12']);
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage(); // This will output an error message indicating the incompatibility.
}
```
In this error case, the `getStrictProxyObject()` will throw an `InvalidServiceException` because of the attempt to proxy a method (`getNonExistentMethod()`) that does not exist in the original `User` class.

## Example Limiting UserInterface
The following interface ensures that only the `getFirstName()` method can be accessed:

```php
interface LimitedUserInterface {
public function getFirstName();
}
```

### User Class

```php
class User{
private $firstName;
private $lastName;

public function __construct($firstName, $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}

public static function getById($id): User {
// Code to get user by id
}

public function getFirstName() {
return $this->firstName;
}

public function getLastName() {
return $this->lastName;
}
}
```

### Example Usage of User Class
```php
$user = new User::getById(12);
echo $user->getFirstName(); // Outputs: John
echo $user->getLastName(); // Outputs: Doe
```

### Using ProxyService With LimitedUserInterface
You can use the ProxyService with `LimitedUserInterface` to get a decorator proxy object:

```php
$proxyService = new ProxyService(new RemoteObjectFactory());
$decoratorProxyObject = $proxyService->getDecoratorProxy(User::class, 'getById', LimitedUserInterface::class, ['12']);
echo $decoratorProxyObject->getFirstName(); // Outputs: John
// The following line would result in an error, even if it exist in the original User class:
// $decoratorProxyObject->getLastName();
```

**Note:** Other methods from the original User class are restricted in the proxy object due to `LimitedUserInterface`.

0 comments on commit eaaeb7b

Please sign in to comment.