Skip to content

Commit

Permalink
Merge pull request #493 from goaop/feature/refactor-core-to-php82
Browse files Browse the repository at this point in the history
Feature - refactor framework core to php82
  • Loading branch information
lisachenko authored Apr 1, 2024
2 parents 91dbacb + 4117b7d commit a0930b2
Show file tree
Hide file tree
Showing 35 changed files with 362 additions and 518 deletions.
3 changes: 2 additions & 1 deletion demos/Demo/Aspect/PropertyInterceptorAspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use Go\Aop\Aspect;
use Go\Aop\Intercept\FieldAccess;
use Go\Aop\Intercept\FieldAccessType;
use Go\Lang\Attribute\Around;

/**
Expand All @@ -29,7 +30,7 @@ class PropertyInterceptorAspect implements Aspect
#[Around("access(public|protected|private Demo\Example\PropertyDemo->*)")]
public function aroundFieldAccess(FieldAccess $fieldAccess): void
{
$isRead = $fieldAccess->getAccessType() == FieldAccess::READ;
$isRead = $fieldAccess->getAccessType() === FieldAccessType::READ;
// proceed all internal advices
$fieldAccess->proceed();

Expand Down
90 changes: 45 additions & 45 deletions src/Aop/Framework/AbstractInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
namespace Go\Aop\Framework;

use Closure;
use Go\Aop\AspectException;
use Go\Aop\Intercept\Interceptor;
use Go\Aop\OrderedAdvice;
use Go\Core\AspectKernel;
use ReflectionFunction;
use ReflectionMethod;
Expand All @@ -31,7 +33,8 @@
*
* After and before interceptors are simple closures that will be invoked after and before main invocation.
*
* Framework models an interceptor as an PHP-closure, maintaining a chain of interceptors "around" the joinpoint:
* Framework models an interceptor as an PHP {@see Closure}, maintaining a chain of interceptors "around" the joinpoint:
* <pre>
* public function (Joinpoint $joinPoint)
* {
* echo 'Before action';
Expand All @@ -41,98 +44,95 @@
*
* return $result;
* }
* </pre>
*/
abstract class AbstractInterceptor implements Interceptor, OrderedAdvice
{
/**
* Local cache of advices for faster unserialization on big projects
*
* @var array<Closure>
*/
protected static array $localAdvicesCache = [];

/**
* Pointcut expression string which was used for this interceptor
*/
protected string $pointcutExpression;

/**
* Closure to call
*/
protected Closure $adviceMethod;

/**
* Advice order
* @var (array&array<string, Closure>) Local hashmap of advices for faster unserialization
*/
private int $adviceOrder;
private static array $localAdvicesCache = [];

/**
* Default constructor for interceptor
*/
public function __construct(Closure $adviceMethod, int $adviceOrder = 0, string $pointcutExpression = '')
{
$this->adviceMethod = $adviceMethod;
$this->adviceOrder = $adviceOrder;
$this->pointcutExpression = $pointcutExpression;
}
public function __construct(
protected readonly Closure $adviceMethod,
private readonly int $adviceOrder = 0,
protected readonly string $pointcutExpression = ''
) {}

/**
* Serialize advice method into array
* Serializes advice closure into array
*
* @return array{name: string, class?: string}
*/
public static function serializeAdvice(Closure $adviceMethod): array
{
$refAdvice = new ReflectionFunction($adviceMethod);
$reflectionAdvice = new ReflectionFunction($adviceMethod);
$scopeReflectionClass = $reflectionAdvice->getClosureScopeClass();

$packedAdvice = ['name' => $reflectionAdvice->name];
if (!isset($scopeReflectionClass)) {
throw new AspectException('Could not pack an interceptor without aspect name');
}
$packedAdvice['class'] = $scopeReflectionClass->name;

return [
'method' => $refAdvice->name,
'class' => $refAdvice->getClosureScopeClass()->name
];
return $packedAdvice;
}

/**
* Unserialize an advice
*
* @param array $adviceData Information about advice
* @param array{name: string, class?: string} $adviceData Information about advice
*/
public static function unserializeAdvice(array $adviceData): Closure
{
// General unpacking supports only aspect's advices
if (!isset($adviceData['class'])) {
throw new AspectException('Could not unpack an interceptor without aspect name');
}
$aspectName = $adviceData['class'];
$methodName = $adviceData['method'];
$methodName = $adviceData['name'];

if (!isset(static::$localAdvicesCache["$aspectName->$methodName"])) {
$aspect = AspectKernel::getInstance()->getContainer()->getAspect($aspectName);
$refMethod = new ReflectionMethod($aspectName, $methodName);
$advice = $refMethod->getClosure($aspect);
// With aspect name and method name, we can restore back a closure for it
if (!isset(self::$localAdvicesCache["$aspectName->$methodName"])) {
$aspect = AspectKernel::getInstance()->getContainer()->getAspect($aspectName);
$advice = (new ReflectionMethod($aspectName, $methodName))->getClosure($aspect);

static::$localAdvicesCache["$aspectName->$methodName"] = $advice;
assert(isset($advice), 'getClosure() can not be null on modern PHP versions');
self::$localAdvicesCache["$aspectName->$methodName"] = $advice;
}

return static::$localAdvicesCache["$aspectName->$methodName"];
return self::$localAdvicesCache["$aspectName->$methodName"];
}

/**
* Returns the advice order
*/
public function getAdviceOrder(): int
{
return $this->adviceOrder;
}

/**
* Getter for extracting the advice closure from Interceptor
*
* @internal
*/
public function getRawAdvice(): Closure
{
return $this->adviceMethod;
}

/**
* Serializes an interceptor into it's representation
* Serializes an interceptor into it's array shape representation
*
* @return non-empty-array<string, mixed>
*/
final public function __serialize(): array
{
// Compressing state representation to avoid default values, eg pointcutExpression = '' or adviceOrder = 0
$state = array_filter(get_object_vars($this));

// Override closure with array representation to enable serialization
$state['adviceMethod'] = static::serializeAdvice($this->adviceMethod);

return $state;
Expand All @@ -141,7 +141,7 @@ final public function __serialize(): array
/**
* Un-serializes an interceptor from it's stored state
*
* @param array $state The stored representation of the interceptor.
* @param array{adviceMethod: array{name: string, class?: string}} $state The stored representation of the interceptor.
*/
final public function __unserialize(array $state): void
{
Expand Down
16 changes: 3 additions & 13 deletions src/Aop/Framework/AbstractInvocation.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,16 @@
abstract class AbstractInvocation extends AbstractJoinpoint implements Invocation
{
/**
* Arguments for invocation
* @var array<mixed> Arguments for invocation, can be mutated by the {@see setArguments()} method
*/
protected array $arguments = [];

/**
* Gets arguments for current invocation
*
* @api
*/
public function getArguments(): array
final public function getArguments(): array
{
return $this->arguments;
}

/**
* Sets arguments for current invocation
*
* @api
*/
public function setArguments(array $arguments): void
final public function setArguments(array $arguments): void
{
$this->arguments = $arguments;
}
Expand Down
50 changes: 11 additions & 39 deletions src/Aop/Framework/AbstractJoinpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
use Go\Aop\AdviceBefore;
use Go\Aop\Intercept\Interceptor;
use Go\Aop\Intercept\Joinpoint;

use function is_array;
use Go\Aop\OrderedAdvice;

/**
* Abstract joinpoint for framework
Expand All @@ -33,26 +32,11 @@
*/
abstract class AbstractJoinpoint implements Joinpoint
{
/**
* List of advices (interceptors)
*
* NB: All current children assume that each advice is Interceptor now.
* Whereas, it isn't correct logically, this can be used to satisfy PHPStan check now.
*
* @var array<Interceptor>
*/
protected array $advices = [];

/**
* Current advice index
*/
protected int $current = 0;

/**
* Stack frames to work with recursive calls or with cross-calls inside object
*/
protected array $stackFrames = [];

/**
* Recursion level for invocation
*/
Expand All @@ -63,10 +47,7 @@ abstract class AbstractJoinpoint implements Joinpoint
*
* @param array<Interceptor> $advices List of advices (interceptors)
*/
public function __construct(array $advices)
{
$this->advices = $advices;
}
public function __construct(protected readonly array $advices = []) {}

/**
* Sorts advices by priority
Expand All @@ -80,23 +61,12 @@ public static function sortAdvices(array $advices): array
$sortedAdvices = $advices;
uasort(
$sortedAdvices,
function (Advice $first, Advice $second) {
switch (true) {
case $first instanceof AdviceBefore && !($second instanceof AdviceBefore):
return -1;

case $first instanceof AdviceAround && !($second instanceof AdviceAround):
return 1;

case $first instanceof AdviceAfter && !($second instanceof AdviceAfter):
return $second instanceof AdviceBefore ? 1 : -1;

case ($first instanceof OrderedAdvice && $second instanceof OrderedAdvice):
return $first->getAdviceOrder() - $second->getAdviceOrder();

default:
return 0;
}
fn(Advice $first, Advice $second) => match (true) {
$first instanceof AdviceBefore && !($second instanceof AdviceBefore) => -1,
$first instanceof AdviceAround && !($second instanceof AdviceAround) => 1,
$first instanceof AdviceAfter && !($second instanceof AdviceAfter) => $second instanceof AdviceBefore ? 1 : -1,
$first instanceof OrderedAdvice && $second instanceof OrderedAdvice => $first->getAdviceOrder() - $second->getAdviceOrder(),
default => 0,
}
);

Expand All @@ -106,7 +76,9 @@ function (Advice $first, Advice $second) {
/**
* Replace concrete advices with list of ids
*
* @param Advice[][][] $advices List of advices
* @param array<array<array<string, Advice|Interceptor>>> $advices List of advices
*
* @return array<array<array<string>>> Sorted identifier of advices/interceptors
*/
public static function flatAndSortAdvices(array $advices): array
{
Expand Down
Loading

0 comments on commit a0930b2

Please sign in to comment.