diff --git a/CHANGELOG.md b/CHANGELOG.md
index ccf3e311..7511b967 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ Changelog
* [Feature] **PHP 8.1+ enum interception** — instance and static methods on both unit (pure) and backed enums can now be intercepted by aspects. The enum body is extracted into a trait (`Foo__AopProxied`); a proxy enum re-declares the cases and dispatches intercepted methods via per-method `static $__joinPoint` caching. Built-in enum methods (`cases`, `from`, `tryFrom`) and initialization joinpoints are never woven.
* [Feature] `self::` in proxied classes now resolves to the proxy class naturally (via PHP trait semantics), removing the need for `SelfValueTransformer`.
* [Feature] **First-class callable syntax** — generated proxy code and invocation constructors use PHP 8.1+ first-class callable syntax (`$this->__aop__method(...)`, `parent::method(...)`, `\func(...)`) to reference original method and function bodies, eliminating the need for `Closure::bind` at construction time.
+* [BC BREAK] Removed DeclareError support, including the `DeclareError` attribute, `DeclareErrorInterceptor`, and `PointcutBuilder::declareError()`. Use `Before` or `Around` interceptors to emit user warnings or throw exceptions instead.
* [Removed] `SelfValueTransformer` and `SelfValueVisitor` — no longer needed with the trait-based engine.
* [Performance] **Direct static joinpoint initialization** — leveraging PHP 8.3+ support for dynamic expressions in static variable initializers, all generated proxy method bodies now initialize their static joinpoint variables directly.
diff --git a/demos/Demo/Aspect/AwesomeAspectKernel.php b/demos/Demo/Aspect/AwesomeAspectKernel.php
index 5c129faf..cfe49449 100644
--- a/demos/Demo/Aspect/AwesomeAspectKernel.php
+++ b/demos/Demo/Aspect/AwesomeAspectKernel.php
@@ -25,7 +25,6 @@ class AwesomeAspectKernel extends AspectKernel
*/
protected function configureAop(AspectContainer $container): void
{
- $container->registerAspect(new DeclareErrorAspect());
$container->registerAspect(new CachingAspect());
$container->registerAspect(new LoggingAspect());
$container->registerAspect(new IntroductionAspect());
diff --git a/demos/Demo/Aspect/DeclareErrorAspect.php b/demos/Demo/Aspect/DeclareErrorAspect.php
deleted file mode 100644
index 1930b488..00000000
--- a/demos/Demo/Aspect/DeclareErrorAspect.php
+++ /dev/null
@@ -1,28 +0,0 @@
-
- *
- * This source file is subject to the license that is bundled
- * with this source code in the file LICENSE.
- */
-
-namespace Demo\Aspect;
-
-use Go\Aop\Aspect;
-use Go\Lang\Attribute\DeclareError;
-
-/**
- * This aspect can be very useful for development to generate an error when executing prohibited methods
- */
-class DeclareErrorAspect implements Aspect
-{
- /**
- * Prevent developers from using this method by always generating a warning
- */
- #[DeclareError('execution(public Demo\Example\ErrorDemo->notSoGoodMethod(*))', level: E_USER_WARNING)]
- protected string $badMethod = 'Method can generate division by zero! Do not use it!';
-}
diff --git a/demos/Demo/Example/ErrorDemo.php b/demos/Demo/Example/ErrorDemo.php
deleted file mode 100644
index a4b3e62b..00000000
--- a/demos/Demo/Example/ErrorDemo.php
+++ /dev/null
@@ -1,40 +0,0 @@
-
- *
- * This source file is subject to the license that is bundled
- * with this source code in the file LICENSE.
- */
-
-namespace Demo\Example;
-
-use Deprecated;
-
-/**
- * In this class we use system functions that will be intercepted by aspect
- */
-class ErrorDemo
-{
- /**
- * Some old method that is used by system in production, but shouldn't be used for the new code
- */
- #[Deprecated]
- public function oldMethod(): void
- {
- echo "Hello, I'm old method", PHP_EOL;
- }
-
- /**
- * Method that is very tricky and should generate a notice
- */
- public function notSoGoodMethod(): float
- {
- $value = round(microtime(true)) % 3; // Sometimes this equal to 0
-
- return rand(1, 10) / $value;
- }
-}
diff --git a/demos/index.php b/demos/index.php
index bde7129c..ee1ae71b 100644
--- a/demos/index.php
+++ b/demos/index.php
@@ -52,7 +52,6 @@
Fluent Interface
Human live advices
Dynamic traits and interfaces
- Declare runtime errors
Documentation
@@ -120,7 +119,6 @@
use Demo\Example\CacheableDemo;
use Demo\Example\DynamicMethodsDemo;
-use Demo\Example\ErrorDemo;
use Demo\Example\FunctionDemo;
use Demo\Example\HumanDemo;
use Demo\Example\IntroductionDemo;
@@ -217,14 +215,6 @@
$example->testStringable();
break;
- case 'declare-errors':
- $aspectName = 'Demo\Aspect\DeclareErrorAspect';
-
- $example = new ErrorDemo();
- $example->oldMethod();
- $example->notSoGoodMethod();
- break;
-
default:
}
?>
diff --git a/src/Aop/AGENTS.md b/src/Aop/AGENTS.md
index b58e5502..9caf9c28 100644
--- a/src/Aop/AGENTS.md
+++ b/src/Aop/AGENTS.md
@@ -42,7 +42,7 @@ Proxy generators use TypeGenerator::renderTypeForPhpDoc() to emit V as 2nd gener
## Attributes (src/Lang/Attribute/)
- Advice: #[Before], #[After], #[Around], #[AfterThrowing]
-- Declaration: #[Aspect], #[Pointcut], #[DeclareError], #[DeclareParents]
+- Declaration: #[Aspect], #[Pointcut], #[DeclareParents]
- Base: AbstractAttribute, AbstractInterceptor, Interceptor (interface)
## Features (src/Aop/Features.php)
diff --git a/src/Aop/Framework/DeclareErrorInterceptor.php b/src/Aop/Framework/DeclareErrorInterceptor.php
deleted file mode 100644
index a311480a..00000000
--- a/src/Aop/Framework/DeclareErrorInterceptor.php
+++ /dev/null
@@ -1,98 +0,0 @@
-
- *
- * This source file is subject to the license that is bundled
- * with this source code in the file LICENSE.
- */
-
-namespace Go\Aop\Framework;
-
-use Closure;
-use Go\Aop\Intercept\Interceptor;
-use Go\Aop\Intercept\Joinpoint;
-use Stringable;
-
-/**
- * Interceptor to dynamically trigger a user notice/warning/error on method call
- *
- * This interceptor can be used as active replacement for the "deprecated" tag or to notify about
- * probable issues with specific method.
- *
- * @phpstan-type InterceptorState array{message: non-empty-string, level: positive-int, pointcutExpression: string}
- */
-final readonly class DeclareErrorInterceptor implements Interceptor
-{
- private Closure $adviceMethod;
-
- /**
- * Default constructor for interceptor
- *
- * @param non-empty-string $message Error message to show for this interceptor
- * @param positive-int $level Default level of error, only E_USER_* constants
- * @param string $pointcutExpression Pointcut expression used
- */
- public function __construct(
- private string $message,
- private int $level,
- private string $pointcutExpression
- ) {
- $this->adviceMethod = self::declareErrorAdvice(...);
- }
-
- public function invoke(Joinpoint $joinpoint): mixed
- {
- ($this->adviceMethod)($joinpoint, $this->message, $this->level);
-
- return $joinpoint->proceed();
- }
-
- /**
- * Serializes an interceptor into its array shape representation
- *
- * @phpstan-return InterceptorState
- */
- final public function __serialize(): array
- {
- return [
- 'message' => $this->message,
- 'level' => $this->level,
- 'pointcutExpression' => $this->pointcutExpression,
- ];
- }
-
-
- /**
- * Un-serializes an interceptor from its stored state
- *
- * @phpstan-param InterceptorState $state The stored representation of the interceptor.
- */
- final public function __unserialize(array $state): void
- {
- [
- 'message' => $this->message,
- 'level' => $this->level,
- 'pointcutExpression' => $this->pointcutExpression
- ] = $state;
- $this->adviceMethod = self::declareErrorAdvice(...);
- }
-
- /**
- * Returns an advice
- */
- private static function declareErrorAdvice(Stringable $joinPoint, string $message, int $level): void
- {
- $message = vsprintf(
- '[AOP Declare Error]: %s has an error: "%s"',
- [
- (string) $joinPoint,
- $message
- ]
- );
- trigger_error($message, $level);
- }
-}
diff --git a/src/Aop/Pointcut/AttributePointcut.php b/src/Aop/Pointcut/AttributePointcut.php
index ef99d56b..a9d23c51 100644
--- a/src/Aop/Pointcut/AttributePointcut.php
+++ b/src/Aop/Pointcut/AttributePointcut.php
@@ -59,7 +59,7 @@ final public function matches(
$instanceToCheck = $reflector;
}
- if (!isset($instanceToCheck) || $instanceToCheck instanceof ReflectionFileNamespace) {
+ if ($instanceToCheck instanceof ReflectionFileNamespace) {
return false;
}
diff --git a/src/Aop/Support/PointcutBuilder.php b/src/Aop/Support/PointcutBuilder.php
index c7a6260c..937086d6 100644
--- a/src/Aop/Support/PointcutBuilder.php
+++ b/src/Aop/Support/PointcutBuilder.php
@@ -18,7 +18,6 @@
use Go\Aop\Framework\AfterThrowingInterceptor;
use Go\Aop\Framework\AroundInterceptor;
use Go\Aop\Framework\BeforeInterceptor;
-use Go\Aop\Framework\DeclareErrorInterceptor;
use Go\Core\AspectContainer;
/**
@@ -72,18 +71,6 @@ public function around(string $pointcutExpression, Closure $adviceToInvoke): voi
$this->registerAdviceInContainer($pointcutExpression, $interceptor);
}
- /**
- * Declares the error message for specific pointcut expression with concrete error level
- *
- * @param non-empty-string $message Error message to show for this intercepton
- * @param positive-int $errorLevel Default level of error, only E_USER_* constants
- */
- public function declareError(string $pointcutExpression, string $message, int $errorLevel = E_USER_ERROR): void
- {
- $interceptor = new DeclareErrorInterceptor($message, $errorLevel, $pointcutExpression);
- $this->registerAdviceInContainer($pointcutExpression, $interceptor);
- }
-
/**
* General method to register advices
*/
diff --git a/src/Core/IntroductionAspectExtension.php b/src/Core/IntroductionAspectExtension.php
index 8d8abae3..807fa190 100644
--- a/src/Core/IntroductionAspectExtension.php
+++ b/src/Core/IntroductionAspectExtension.php
@@ -14,12 +14,10 @@
use Go\Aop\Advice;
use Go\Aop\Aspect;
-use Go\Aop\Framework\DeclareErrorInterceptor;
use Go\Aop\Framework\TraitIntroductionInfo;
use Go\Aop\Pointcut;
use Go\Aop\Support\GenericPointcutAdvisor;
use Go\Lang\Attribute\AbstractAttribute;
-use Go\Lang\Attribute\DeclareError;
use Go\Lang\Attribute\DeclareParents;
use ReflectionClass;
use ReflectionProperty;
@@ -51,11 +49,6 @@ public function load(Aspect $aspect, ReflectionClass $reflectionAspect): array
$advisor = new GenericPointcutAdvisor($pointcut, $advice);
$loadedItems[$propertyId] = $advisor;
- } elseif ($attribute instanceof DeclareError) {
- $pointcut = $this->parsePointcut($aspect, $reflectionAspect, $attribute->expression);
- $advice = $this->getAdvice($attribute, $aspect, $aspectProperty);
-
- $loadedItems[$propertyId] = new GenericPointcutAdvisor($pointcut, $advice);
} else {
throw new UnexpectedValueException('Unsupported attribute class: ' . get_class($attribute));
}
@@ -76,27 +69,10 @@ protected function getAdvice(
ReflectionProperty $aspectProperty
): Advice {
return match (true) {
- $interceptorAttribute instanceof DeclareError =>
- $this->createDeclareErrorAdvice($aspectProperty, $interceptorAttribute),
$interceptorAttribute instanceof DeclareParents =>
new TraitIntroductionInfo($interceptorAttribute->traitName, $interceptorAttribute->interfaceName),
default =>
throw new UnexpectedValueException('Unsupported attribute class: ' . get_class($interceptorAttribute)),
};
}
-
- /**
- * Creates a DeclareErrorInterceptor after validating the property's default value.
- *
- * @throws \UnexpectedValueException if the property default value is not a non-empty string
- */
- private function createDeclareErrorAdvice(ReflectionProperty $aspectProperty, DeclareError $attribute): DeclareErrorInterceptor
- {
- $errorMessage = $aspectProperty->getDefaultValue();
- if (!is_string($errorMessage) || $errorMessage === '') {
- throw new \UnexpectedValueException('DeclareError property must have a non-empty string default value');
- }
-
- return new DeclareErrorInterceptor($errorMessage, $attribute->level, $attribute->expression);
- }
}
diff --git a/src/Lang/Attribute/DeclareError.php b/src/Lang/Attribute/DeclareError.php
deleted file mode 100644
index 6666fa9c..00000000
--- a/src/Lang/Attribute/DeclareError.php
+++ /dev/null
@@ -1,34 +0,0 @@
-
- *
- * This source file is subject to the license that is bundled
- * with this source code in the file LICENSE.
- */
-
-namespace Go\Lang\Attribute;
-
-use Attribute;
-
-/**
- * Declares error attribute
- */
-#[Attribute(Attribute::TARGET_PROPERTY)]
-class DeclareError extends AbstractAttribute
-{
- /**
- * @inheritdoc
- * @param int&(\E_USER_NOTICE|\E_USER_WARNING|\E_USER_ERROR|\E_USER_DEPRECATED) $level Default level of error, only E_USER_* constants
- */
- public function __construct(
- string $expression,
- readonly public int $level = E_USER_NOTICE,
- int $order = 0,
- ) {
- parent::__construct($expression, $order);
- }
-}
diff --git a/tests/Aop/Framework/DeclareErrorInterceptorTest.php b/tests/Aop/Framework/DeclareErrorInterceptorTest.php
deleted file mode 100644
index 066266e8..00000000
--- a/tests/Aop/Framework/DeclareErrorInterceptorTest.php
+++ /dev/null
@@ -1,53 +0,0 @@
-
- *
- * This source file is subject to the license that is bundled
- * with this source code in the file LICENSE.
- */
-
-namespace Go\Aop\Framework;
-
-use Go\Aop\Intercept\Joinpoint;
-use PHPUnit\Framework\TestCase;
-
-class DeclareErrorInterceptorTest extends TestCase
-{
- public function testCanSerializeAndUnserialize(): void
- {
- $interceptor = new DeclareErrorInterceptor('Deprecated method', E_USER_DEPRECATED, 'execution(Some::method)');
-
- $serialized = serialize($interceptor);
- $unserialized = unserialize($serialized);
-
- $this->assertInstanceOf(DeclareErrorInterceptor::class, $unserialized);
- }
-
- public function testUnserializedInterceptorPreservesMessageAndLevel(): void
- {
- $interceptor = new DeclareErrorInterceptor('Deprecated method', E_USER_DEPRECATED, 'execution(Some::method)');
-
- /** @var DeclareErrorInterceptor $unserialized */
- $unserialized = unserialize(serialize($interceptor));
-
- $joinpoint = $this->createMock(Joinpoint::class);
- $joinpoint->expects($this->once())->method('proceed')->willReturn(null);
-
- $capturedMessage = null;
- $capturedLevel = null;
- set_error_handler(function (int $level, string $message) use (&$capturedMessage, &$capturedLevel): bool {
- $capturedLevel = $level;
- $capturedMessage = $message;
- return true;
- });
- $unserialized->invoke($joinpoint);
- restore_error_handler();
-
- $this->assertSame(E_USER_DEPRECATED, $capturedLevel);
- $this->assertStringContainsString('Deprecated method', $capturedMessage);
- }
-}