-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from pimcore/proxy-docu
proxy-docu
- Loading branch information
Showing
3 changed files
with
316 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |