Installation:
composer require eloquent/pops
(Note: Despite being archived, the package remains functional for PHP 8.1+.)
First Use Case: Wrap an object to modify its behavior transparently. Example:
use Eloquent\Pops\ProxyObject;
class UppercaseProxy extends ProxyObject {
public function popsCall($method, array &$arguments) {
return strtoupper(parent::popsCall($method, $arguments));
}
}
$original = new stdClass();
$original->message = "hello";
$proxy = new UppercaseProxy($original);
echo $proxy->message; // Outputs: "HELLO"
Key Classes:
ProxyObject: For object wrapping.ProxyArray: For array wrapping.ProxyPrimitive: For primitive values (e.g., strings, numbers).SafeProxy: Mark objects/values to exclude from recursive proxying.popsCall(), __get(), recursive proxies).ProxyInterface, SafeInterface, and type-specific interfaces (e.g., ProxyObjectInterface) for method signatures.Transparent Method Interception:
Override popsCall() to modify method return values or arguments:
class LoggingProxy extends ProxyObject {
public function popsCall($method, array &$arguments) {
$result = parent::popsCall($method, $arguments);
logger()->info("Method {$method} called, returned: " . json_encode($result));
return $result;
}
}
Property Modification:
Override __get()/__set() to transform properties:
class SanitizedProxy extends ProxyObject {
public function __get($property) {
return htmlspecialchars(parent::__get($property));
}
}
Recursive Proxying:
Use Proxy::proxy() with nested structures (arrays/objects):
$data = ['user' => ['name' => 'Alice', 'email' => 'alice@example.com']];
$proxy = Proxy::proxy($data, true); // Recursively wrap all values
echo $proxy['user']['name']; // Escaped/sanitized if proxy handles it
Safe Values: Exclude specific values from proxying:
$safeValue = SafeProxy::proxy("Do not modify me!");
$proxy = Proxy::proxy(['safe' => $safeValue, 'normal' => 'modify']);
Laravel Service Providers: Register a global proxy wrapper for requests/responses:
public function boot() {
$this->app->resolving('request', function ($request) {
return Proxy::proxy($request, true);
});
}
Middleware: Use proxies to sanitize input/output:
public function handle($request, Closure $next) {
$sanitizedRequest = Proxy::proxy($request->all(), true);
return $next($sanitizedRequest);
}
Eloquent Models: Wrap model attributes for validation/logging:
class User extends Model {
public function getAttributes() {
return Proxy::proxy(parent::getAttributes(), true);
}
}
API Responses: Apply consistent formatting to JSON responses:
return Proxy::proxy($data, true)->toJson();
| Pattern | Use Case | Example Class |
|---|---|---|
| Method Wrapper | Modify method return values | LoggingProxy |
| Property Wrapper | Sanitize/transform properties | SanitizedProxy |
| Recursive Wrapper | Deeply nest proxies (e.g., arrays) | OutputEscaperProxy |
| Safe Guard | Exclude values from proxying | SafeProxy |
| Type-Specific | Handle primitives/arrays/objects | ProxyPrimitive, ProxyArray |
Reference Arguments:
&$argument cannot be called directly via $proxy->method($arg).popsCall() with pre-bound references:
$var = null;
$proxy->popsCall('method', [&$var]);
Recursive Overhead:
SafeProxy for large/unmodifiable structures or limit recursion depth.Magic Methods Conflicts:
__call()/__get() may clash with PHP’s magic methods.popsCall() for methods and __get()/__set() for properties.PHP 8.1+ Deprecations:
popsArray()) are deprecated in favor of popsValue().ProxyInterface::popsValue():
$value = $proxy->popsValue(); // Replaces type-specific methods
Circular References:
A->B->A).visited tracker in recursive proxies or use SafeProxy for shared objects.Verify Proxy Chain:
Use get_class($proxy) to confirm the proxy type. For nested proxies, recursively check each level.
Log Proxy Calls:
Add debug output in popsCall() to trace method invocations:
public function popsCall($method, array &$arguments) {
logger()->debug("Proxying {$method} on " . get_class($this->popsValue()));
return parent::popsCall($method, $arguments);
}
Check for SafeProxies:
If a value isn’t being proxied, ensure it’s not wrapped in SafeProxy:
if ($value instanceof SafeInterface) {
logger()->warning("Skipping SafeProxy for: " . gettype($value->popsValue()));
}
Custom Proxy Classes:
Extend base classes (ProxyObject, ProxyArray) to add domain-specific logic:
class CachedProxy extends ProxyObject {
private $cache = [];
public function popsCall($method, array &$arguments) {
$key = md5($method . serialize($arguments));
return $this->cache[$key] ?? ($this->cache[$key] = parent::popsCall($method, $arguments));
}
}
Dynamic Proxying:
Use Proxy::proxy() with closures to conditionally wrap values:
$proxy = Proxy::proxy($value, function ($value) {
return is_string($value) && strpos($value, '<script>') !== false;
});
Interface Enforcement:
Ensure proxies implement ProxyInterface to guarantee consistency:
class MyProxy extends ProxyObject implements ProxyInterface {
// ...
}
Integration with Laravel:
Proxy::sanitize()).Recursion Depth: The package doesn’t limit recursion depth by default. For large structures, set a custom limit:
Proxy::setMaxRecursionDepth(10); // Default is often higher
Primitive Handling:
Primitives (e.g., int, string) are wrapped in ProxyPrimitive. Override __toString() for custom behavior:
class FormattedProxyPrimitive extends ProxyPrimitive {
public function __toString() {
return date('Y-m-d', $this->popsValue());
}
}
Type Juggler:
Proxies preserve the original type. For example, a proxied int remains an int (but wrapped). Cast explicitly if needed:
$proxy = Proxy::proxy(42);
$int = (int) $proxy; // Explicit cast
How can I help you explore Laravel packages today?