✨ Implement ObjectProxy class
This commit is contained in:
parent
5e431a716a
commit
d857775289
10 changed files with 417 additions and 79 deletions
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of danjones000/object-spy
|
||||
*
|
||||
* danjones000/object-spy is open source software: you can distribute
|
||||
* it and/or modify it under the terms of the MIT License
|
||||
* (the "License"). You may not use this file except in
|
||||
* compliance with the License.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*
|
||||
* @copyright Copyright (c) Dan Jones <danjones@goodevilgenius.org>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Danjones000\Spy;
|
||||
|
||||
/**
|
||||
* An example class to act as a starting point for developing your library
|
||||
*/
|
||||
class Example
|
||||
{
|
||||
/**
|
||||
* Returns a greeting statement using the provided name
|
||||
*/
|
||||
public function greet(string $name = 'World'): string
|
||||
{
|
||||
return "Hello, {$name}!";
|
||||
}
|
||||
}
|
||||
157
src/ObjectProxy.php
Normal file
157
src/ObjectProxy.php
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of danjones000/object-spy
|
||||
*
|
||||
* danjones000/object-spy is open source software: you can distribute
|
||||
* it and/or modify it under the terms of the MIT License
|
||||
* (the "License"). You may not use this file except in
|
||||
* compliance with the License.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*
|
||||
* @copyright Copyright (c) Dan Jones <danjones@goodevilgenius.org>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Danjones000\Spy;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function class_exists;
|
||||
use function get_class;
|
||||
use function is_null;
|
||||
use function is_object;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Proxies all calls to another object/class, via Closures.
|
||||
*
|
||||
* This allows access to private/protected methods and properties without Reflection.
|
||||
*/
|
||||
class ObjectProxy
|
||||
{
|
||||
protected ?string $class;
|
||||
protected ?object $object;
|
||||
protected Closure $caller;
|
||||
protected Closure $getter;
|
||||
protected Closure $setter;
|
||||
|
||||
/**
|
||||
* @param string|object $classOrObject
|
||||
*
|
||||
* @psalm-suppress RedundantConditionGivenDocblockType
|
||||
*/
|
||||
public function __construct($classOrObject)
|
||||
{
|
||||
[$this->object, $this->class] = is_object($classOrObject) ?
|
||||
[$classOrObject, get_class($classOrObject)] :
|
||||
(
|
||||
is_string($classOrObject) && class_exists($classOrObject) ?
|
||||
[null, $classOrObject] :
|
||||
[null, null]
|
||||
);
|
||||
$this->assertValid();
|
||||
}
|
||||
|
||||
protected function assertValid(): void
|
||||
{
|
||||
if (is_null($this->class)) {
|
||||
throw new InvalidArgumentException('Must specify a class, or object');
|
||||
}
|
||||
|
||||
if (!class_exists($this->class)) {
|
||||
throw new InvalidArgumentException('Must specify a valid class');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closure used by $this->__call.
|
||||
*
|
||||
* @psalm-suppress PossiblyNullArgument
|
||||
*/
|
||||
protected function getCaller(): Closure
|
||||
{
|
||||
return $this->caller ??= (
|
||||
$this->object ?
|
||||
fn (string $method, array $args) => $this->$method(...$args) :
|
||||
static fn (string $method, array $args) => static::$method(...$args)
|
||||
)->bindTo($this->object, $this->class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closure used by $this->__get.
|
||||
*
|
||||
* @psalm-suppress PossiblyNullArgument
|
||||
* @psalm-suppress UnusedClosureParam
|
||||
*/
|
||||
protected function getGetter(): Closure
|
||||
{
|
||||
return $this->getter ??= (
|
||||
$this->object ?
|
||||
fn (string $key) => $this->$key :
|
||||
static fn (string $key) => static::$$key
|
||||
)->bindTo($this->object, $this->class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closure used by $this->__set.
|
||||
*
|
||||
* @psalm-suppress PossiblyNullArgument
|
||||
* @psalm-suppress MixedAssignment
|
||||
*/
|
||||
protected function getSetter(): Closure
|
||||
{
|
||||
return $this->setter ??= (
|
||||
$this->object ?
|
||||
fn (string $key, $value) => $this->$key = $value :
|
||||
static fn (string $key, $value) => static::$$key = $value
|
||||
)->bindTo($this->object, $this->class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run arbitry code on object, with access to private/protected props/methods.
|
||||
*
|
||||
* @param mixed[] $args
|
||||
* @return mixed
|
||||
* @psalm-suppress PossiblyNullArgument
|
||||
* @psalm-suppress PossiblyInvalidFunctionCall
|
||||
*/
|
||||
public function call(callable $cb, array ...$args)
|
||||
{
|
||||
return (Closure::fromCallable($cb)->bindTo($this->object, $this->class))(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $method, array $args)
|
||||
{
|
||||
return ($this->getCaller())($method, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return ($this->getGetter())($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function __set(string $key, $value)
|
||||
{
|
||||
return ($this->getSetter())($key, $value);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue