Skip to content

Commit eaaeb7b

Browse files
authored
Merge pull request #10 from pimcore/proxy-docu
proxy-docu
2 parents 65be88d + 42b1d33 commit eaaeb7b

File tree

3 files changed

+316
-1
lines changed

3 files changed

+316
-1
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ Static Resolver Bundle is designed to encapsulate the usage of static calls with
99

1010
## Documentation Overview
1111
- [Installation](doc/01_Installation.md)
12-
- [Resolver Service Usage](doc/02_Resolver_Service_Usage.md)
12+
- [Resolver Service Usage](doc/02_Resolver_Service_Usage.md)
13+
- [Event Proxy Service Usage](doc/03_Event_Proxy_Service_Usage.md)
14+
- [Proxy Service Usage](doc/04_Proxy_Service_Usage.md)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
2+
# Event Proxy Service Usage
3+
4+
## Overview
5+
6+
`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.
7+
8+
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`).
9+
10+
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`.
11+
12+
## `getEventDispatcherProxy()` Method
13+
14+
15+
This method creates a proxy of the given instance and binds `pre` and `post` method interceptors to it.
16+
17+
```php
18+
public function getEventDispatcherProxy(
19+
object $instance,
20+
array $preInterceptors = [],
21+
array $postInterceptors = []
22+
): object;
23+
```
24+
25+
**Parameters:**
26+
27+
- `$instance`: The object instance that needs to be proxied.
28+
- `$preInterceptors` (optional): An array of method names that should be intercepted before their invocation.
29+
- `$postInterceptors` (optional): An array of method names that should be intercepted after their invocation.
30+
31+
**Returns:**
32+
33+
- A proxy of the given `$instance`.
34+
35+
:::info
36+
37+
Final classes can't be proxied.
38+
39+
:::
40+
41+
---
42+
43+
## Example Usage With Symfony's Dependency Injection
44+
45+
### Configuration (`services.yaml`)
46+
47+
```yaml
48+
services:
49+
# ... [other service definitions]
50+
51+
App\EventListener\InterceptorListener:
52+
tags:
53+
- { name: kernel.event_listener, event: 'app.entity.user.save.pre', method: 'onUserSavePre' }
54+
- { name: kernel.event_listener, event: 'app.entity.user.save.post', method: 'onUserSavePost' }
55+
```
56+
57+
### Listener Example
58+
59+
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:
60+
61+
```php
62+
namespace App\EventListener;
63+
64+
use Symfony\Component\EventDispatcher\GenericEvent;
65+
66+
class InterceptorListener
67+
{
68+
public function onUserSavePre(GenericEvent $event)
69+
{
70+
/** @var User $userInstance */
71+
$userInstance = $event->getSubject();
72+
73+
// Your custom logic here. For instance:
74+
// Logging, modifying data before save, etc.
75+
}
76+
77+
public function onUserSavePost(GenericEvent $event)
78+
{
79+
/** @var User $userInstance */
80+
$userInstance = $event->getSubject();
81+
82+
// Your custom logic here. For instance:
83+
// Logging, modifying data after save, etc.
84+
}
85+
}
86+
```
87+
88+
### Using `EventProxyService` in a Controller
89+
90+
```php
91+
use Pimcore\Bundle\StaticResolverBundle\Proxy\Service\EventProxyService;
92+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
93+
use App\Entity\User;
94+
95+
class YourController extends AbstractController
96+
{
97+
public function someAction(EventProxyService $eventProxyService)
98+
{
99+
$originalObject = new User::getById('12');
100+
101+
$proxyObject = $eventProxyService->getEventDispatcherProxy(
102+
$originalObject,
103+
['save'], // Assuming 'save' is the method we want to pre-intercept
104+
['save'] // Assuming 'save' is the method we want to post-intercept
105+
);
106+
107+
$proxyObject->setLastname('Doe');
108+
$proxyObject->save(); // This will trigger the interceptor(s) for the save method
109+
110+
// Your remaining code
111+
}
112+
}
113+
```
114+
115+
:::info
116+
117+
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.
118+
119+
:::

doc/04_Proxy_Service_Usage.md

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Proxy Service Usage
2+
3+
## Overview
4+
5+
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.
6+
7+
## Methods
8+
9+
### `getProxyObject()`
10+
11+
- Retrieve a proxy object for a given class and method.
12+
- The proxy will contain all methods of the original class.
13+
14+
- **Parameters:**
15+
- `$className` (string): The fully qualified class name.
16+
- `$method` (string): The method name to call.
17+
- `$args` (array, optional): Arguments to pass to the method. Default is an empty array.
18+
19+
- **Returns:** object|null
20+
21+
### `getStrictProxyObject()`
22+
23+
- Retrieve a proxy object that strictly implements a specified interface.
24+
- The proxy will only contain the methods of the specified interface, allowing you to limit access to methods.
25+
- All methods from the given interface must be callabe in the original class.
26+
27+
- **Parameters:**
28+
- `$className` (string): The fully qualified class name.
29+
- `$method` (string): The method name to call.
30+
- `$interface` (string): The interface that the returned proxy object must implement.
31+
- `$args` (array, optional): Arguments to pass to the method. Default is an empty array.
32+
33+
- **Returns:** object|null
34+
35+
### `getDecoratorProxy()`
36+
37+
- Generate a proxy object that can optionally implement a specified interface.
38+
- If an interface is provided, the proxy will only contain the methods of that interface.
39+
- The interface can also include methods not present in the original class.
40+
* These methods must be addressed by the original class's magic PHP functions, like `__call`.
41+
42+
- **Parameters:**
43+
- `$className` (string): The fully qualified class name.
44+
- `$method` (string): The method name to call.
45+
- `$interface` (string, optional): The interface that the returned proxy object can implement.
46+
- `$args` (array, optional): Arguments to pass to the method. Default is an empty array.
47+
48+
- **Returns:** object|null
49+
50+
## Example Extending UserInterface and Decorator Proxy
51+
Let's suppose you wish to extend the functionality of the `User` class with a new method `getAdditionalData()`:
52+
53+
```php
54+
interface MyExtendedInterface {
55+
public function getFirstName();
56+
public function getAdditionalData();
57+
}
58+
```
59+
60+
```php
61+
$proxyService = new ProxyService(new RemoteObjectFactory());
62+
$decoratorProxy = $proxyService->getDecoratorProxy(User::class, 'getById', MyExtendedInterface::class, ['12']);
63+
```
64+
When using the `getDecoratorProxy()` method with `MyExtendedInterface`, the `User` class should look like:
65+
66+
```php
67+
class User {
68+
69+
private string $firstName;
70+
71+
72+
public function __construct($firstName, $lastName) {
73+
$this->firstName = $firstName;
74+
$this->lastName = $lastName;
75+
}
76+
77+
public static function getById($id): User {
78+
// Code to get user by id
79+
}
80+
81+
public function getFirstName(): string
82+
return $this->firstName;
83+
}
84+
85+
public function __call($name, $arguments) {
86+
// Handle methods that aren't explicitly defined in User but are in MyExtendedInterface
87+
if ($name == "getAdditionalData") {
88+
// logic for getAdditionalData
89+
}
90+
}
91+
}
92+
```
93+
94+
## Example Using an Incompatible Interface With Strict Proxy
95+
96+
Let's say you mistakenly use an interface that expects a method that isn't in the `User` class:
97+
98+
```php
99+
interface IncompatibleInterface {
100+
public function getNonExistentMethod();
101+
}
102+
```
103+
### User Class
104+
105+
```php
106+
class User{
107+
private $firstName;
108+
private $lastName;
109+
110+
public function __construct($firstName, $lastName) {
111+
$this->firstName = $firstName;
112+
$this->lastName = $lastName;
113+
}
114+
115+
public static function getById($id): User {
116+
// Code to get user by id
117+
}
118+
119+
public function getFirstName() {
120+
return $this->firstName;
121+
}
122+
123+
public function getLastName() {
124+
return $this->lastName;
125+
}
126+
}
127+
```
128+
If you attempt to use `IncompatibleInterface` with the ProxyService, it will result in an error because the `User` class doesn't have `getNonExistentMethod()`.
129+
130+
```php
131+
try {
132+
$proxyService = new ProxyService(new RemoteObjectFactory());
133+
try {
134+
$strictProxyObject = $proxyService->getStrictProxyObject(User::class, 'getbyId', IncompatibleInterface::class, ['12']);
135+
} catch (InvalidArgumentException $e) {
136+
echo "Error: " . $e->getMessage(); // This will output an error message indicating the incompatibility.
137+
}
138+
```
139+
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.
140+
141+
## Example Limiting UserInterface
142+
The following interface ensures that only the `getFirstName()` method can be accessed:
143+
144+
```php
145+
interface LimitedUserInterface {
146+
public function getFirstName();
147+
}
148+
```
149+
150+
### User Class
151+
152+
```php
153+
class User{
154+
private $firstName;
155+
private $lastName;
156+
157+
public function __construct($firstName, $lastName) {
158+
$this->firstName = $firstName;
159+
$this->lastName = $lastName;
160+
}
161+
162+
public static function getById($id): User {
163+
// Code to get user by id
164+
}
165+
166+
public function getFirstName() {
167+
return $this->firstName;
168+
}
169+
170+
public function getLastName() {
171+
return $this->lastName;
172+
}
173+
}
174+
```
175+
176+
### Example Usage of User Class
177+
```php
178+
$user = new User::getById(12);
179+
echo $user->getFirstName(); // Outputs: John
180+
echo $user->getLastName(); // Outputs: Doe
181+
```
182+
183+
### Using ProxyService With LimitedUserInterface
184+
You can use the ProxyService with `LimitedUserInterface` to get a decorator proxy object:
185+
186+
```php
187+
$proxyService = new ProxyService(new RemoteObjectFactory());
188+
$decoratorProxyObject = $proxyService->getDecoratorProxy(User::class, 'getById', LimitedUserInterface::class, ['12']);
189+
echo $decoratorProxyObject->getFirstName(); // Outputs: John
190+
// The following line would result in an error, even if it exist in the original User class:
191+
// $decoratorProxyObject->getLastName();
192+
```
193+
194+
**Note:** Other methods from the original User class are restricted in the proxy object due to `LimitedUserInterface`.

0 commit comments

Comments
 (0)