Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"name": "rcrdortiz/axpecto",
"description": "PHP meta framework for building modern, AI oriented, aspect orientated development frameworks.",
"description": "PHP metaframework for modern, AI‑augmented, aspect‑oriented development.",
"type": "library",
"license": "MIT",
"version": "1.0.2",
"require": {
"php": ">=8.3",
Expand All @@ -20,7 +21,6 @@
"src/functions.php"
]
},
"license": "GNU",
"authors": [
{
"name": "Richard Ortiz",
Expand Down
57 changes: 3 additions & 54 deletions src/Annotation/Annotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,21 @@
namespace Axpecto\Annotation;

use Attribute;
use Axpecto\ClassBuilder\BuildHandler;
use Axpecto\MethodExecution\MethodExecutionHandler;

/**
* Class Annotation
*
* Represents an abstract base class for annotations in an Aspect-Oriented Programming (AOP) system.
* Represents a base class for annotations in an Aspect-Oriented Programming (AOP) system.
* Annotations can have associated handler classes that define how they are processed during
* method execution or build phases. This class provides mechanisms to retrieve those handlers and
* associate annotations with specific classes and methods.
*
* @package Axpecto\Aop
*
* @TODO Refactor this and possibly create a hierarchy of annotations with Annotation -> BuildAnnotation -> MethodExecutionAnnotation.
*/
#[Attribute]
class Annotation {

/**
* The handler for processing the method execution annotation.
*
* @var MethodExecutionHandler|null
*/
protected ?MethodExecutionHandler $methodExecutionHandler = null;

/**
* The builder for the annotation, used during the build phase.
*
* @var BuildHandler|null
*/
protected ?BuildHandler $builder = null;

/**
* The class associated with the annotation.
*
Expand All @@ -47,42 +32,6 @@ class Annotation {
*/
protected ?string $annotatedMethod = null;

/**
* Gets the BuildHandler for this annotation, if available.
*
* @return BuildHandler|null The builder for the annotation, or null if not set.
*/
public function getBuilder(): ?BuildHandler {
return $this->builder;
}

/**
* Checks if this annotation is meant for the build phase.
*
* @return bool True if the annotation is used for building, false otherwise.
*/
public function isBuildAnnotation(): bool {
return $this->builder instanceof BuildHandler;
}

/**
* Gets the MethodExecutionHandler for this annotation, if available.
*
* @return MethodExecutionHandler|null The handler for method execution, or null if not set.
*/
public function getMethodExecutionHandler(): ?MethodExecutionHandler {
return $this->methodExecutionHandler;
}

/**
* Checks if this annotation is meant for method execution interception.
*
* @return bool True if it is a method execution annotation, false otherwise.
*/
public function isMethodExecutionAnnotation(): bool {
return $this->methodExecutionHandler instanceof MethodExecutionHandler;
}

/**
* Sets the class name that this annotation is associated with.
*
Expand Down
203 changes: 114 additions & 89 deletions src/Annotation/AnnotationReader.php
Original file line number Diff line number Diff line change
@@ -1,146 +1,171 @@
<?php

declare( strict_types=1 );

namespace Axpecto\Annotation;

use Axpecto\Collection\Klist;
use Axpecto\Container\Container;
use Axpecto\Reflection\ReflectionUtils;
use Exception;
use ReflectionAttribute;
use ReflectionException;
use ReflectionMethod;
use ReflectionParameter;

/**
* Class AnnotationReader
*
* This class is responsible for reading annotations on classes and methods, specifically
* for use in an Aspect-Oriented Programming (AOP) context. It handles fetching annotations,
* including build-related annotations, and supports dependency injection for these annotations.
* Reads PHP8 attributes and turns them into AOP-style Annotation instances,
* filtering by class vs. method targets and injecting their properties via DI.
*
* @template T
* @template A of Annotation
* @psalm-consistent-constructor
*/
class AnnotationReader {

/**
* Constructor for AnnotationReader.
*
* @param Container $container The dependency injection container.
* @param ReflectionUtils $reflect Utility for handling reflection of classes and methods.
*/
public function __construct(
private readonly Container $container,
private readonly ReflectionUtils $reflect,
private readonly ReflectionUtils $reflection
) {
}

/**
* Fetches annotations for a specific method.
* Fetch all annotations of a given type on a class.
*
* @param class-string<T> $class The fully qualified class name.
* @param string $method The method name.
* @param string $annotationClass The annotation class to filter.
* @template T
* @param class-string<T> $class
* @param class-string<A> $annotationClass
*
* @return Klist<Annotation> A list of annotations for the method.
* @throws ReflectionException|Exception
* @return Klist<T>
* @throws ReflectionException
*/
public function getMethodAnnotations( string $class, string $method, string $annotationClass = Annotation::class ): Klist {
return $this->mapAttributesToAnnotations(
attributes: $this->reflect->getMethodAttributes( $class, $method ),
annotationClass: $annotationClass
);
public function getClassAnnotations(
string $class,
string $annotationClass,
): Klist {
$raw = $this->reflection->getClassAttributes( $class );

return $this
->filterAndInject( $raw, $annotationClass )
->map( fn( Annotation $ann ): Annotation => $ann->setAnnotatedClass( $class ) );
}

/**
* Fetches build-related annotations for a method.
* Fetch all annotations of a given type on a method.
*
* @param class-string<T> $class The fully qualified class name.
* @param string $method The method name.
* @param string $annotationClass The annotation class to filter.
* @template T
* @param class-string<T> $class
* @param string $method
* @param class-string<A> $annotationClass
*
* @return Klist<Annotation> A list of build annotations for the method.
* @return Klist<T>
* @throws ReflectionException
*/
public function getMethodExecutionAnnotations( string $class, string $method, string $annotationClass = Annotation::class ): Klist {
return $this->getMethodAnnotations( $class, $method, $annotationClass )
->filter( fn( Annotation $annotation ) => $annotation->isMethodExecutionAnnotation() )
->map( fn( Annotation $annotation ) => $annotation->setAnnotatedClass( $class )->setAnnotatedMethod( $method ) );
public function getMethodAnnotations(
string $class,
string $method,
string $annotationClass,
): Klist {
$raw = $this->reflection->getMethodAttributes( $class, $method );

return $this
->filterAndInject( $raw, $annotationClass )
->map( fn( Annotation $ann ): Annotation => $ann
->setAnnotatedClass( $class )
->setAnnotatedMethod( $method )
);
}

/**
* Fetches build-related annotations for a method.
* Fetch both class‑level and method‑level annotations of a given type.
*
* @param class-string<T> $class The fully qualified class name.
* @param string $method The method name.
* @param string $annotationClass The annotation class to filter.
* @template T
* @param class-string<T> $class
* @param class-string<A> $annotationClass
*
* @return Klist<Annotation> A list of build annotations for the method.
* @return Klist<T>
* @throws ReflectionException
*/
public function getMethodBuildAnnotations( string $class, string $method, string $annotationClass = Annotation::class ): Klist {
return $this->getMethodAnnotations( $class, $method, $annotationClass )
->filter( fn( Annotation $annotation ) => $annotation->isBuildAnnotation() )
->map( fn( Annotation $annotation ) => $annotation->setAnnotatedClass( $class )->setAnnotatedMethod( $method ) );
public function getAllAnnotations(
string $class,
string $annotationClass = Annotation::class
): Klist {
$classAnns = $this->getClassAnnotations( $class, $annotationClass );
$methodAnns = $this->reflection
->getAnnotatedMethods( $class, $annotationClass )
->map( fn( \ReflectionMethod $m ) => $this->getMethodAnnotations( $class, $m->getName(), $annotationClass )
)
->flatten();

return $classAnns->merge( $methodAnns );
}

/**
* Fetches all build-related annotations for a class, including its methods.
* Fetch all annotations of a given type on one of a method’s parameters.
*
* @param class-string<T> $class The fully qualified class name.
* @param string $annotationClass The annotation class to filter.
* @template T
* @param class-string<T> $class
* @param string $method
* @param string $parameterName
* @param class-string<A> $annotationClass
*
* @return Klist<Annotation> A list of all build annotations for the class.
* @return Klist<A>
* @throws ReflectionException
*/
public function getAllBuildAnnotations( string $class, string $annotationClass = Annotation::class ): Klist {
return $this->reflect
->getAnnotatedMethods( $class )
->map( fn( ReflectionMethod $method ) => $this->getMethodBuildAnnotations( $class, $method->getName(), $annotationClass ) )
->flatten()
->merge( $this->getClassBuildAnnotations( $class, $annotationClass ) );
}
public function getParameterAnnotations(
string $class,
string $method,
string $parameterName,
string $annotationClass,
): Klist {
$parameter = listFrom( $this->reflection->getClassMethod( $class, $method )->getParameters() )
->filter( fn( ReflectionParameter $p ) => $p->getName() === $parameterName )
->firstOrNull();

/**
* Fetches annotations for a class.
*
* @param class-string<T> $class The fully qualified class name.
* @param string $annotationClass The annotation class to filter.
*
* @return Klist<Annotation> A list of annotations for the class.
* @throws ReflectionException|Exception
*/
public function getClassAnnotations( string $class, string $annotationClass = Annotation::class ): Klist {
return $this->mapAttributesToAnnotations(
attributes: $this->reflect->getClassAttributes( $class ),
annotationClass: $annotationClass
);
if ( ! $parameter ) {
return emptyList();
}

return listFrom( $parameter->getAttributes() )
->map( fn( ReflectionAttribute $p ) => $p->newInstance() )
->maybe( fn( Klist $attributes ) => $this->filterAndInject( $attributes, $annotationClass ) )
->foreach( fn( Annotation $ann ) => $ann->setAnnotatedClass( $class )->setAnnotatedMethod( $method ) );
}

/**
* Fetches build-related annotations for a class.
* Fetch a single annotation of a given type on a property.
*
* @param class-string<T> $class The fully qualified class name.
* @param string $annotationClass The annotation class to filter.
* @template T
* @param class-string<T> $class
* @param string $property
* @param class-string<A> $annotationClass
*
* @return Klist<Annotation> A list of build annotations for the class.
* @throws ReflectionException|Exception
* @return A|null
* @throws ReflectionException
*/
public function getClassBuildAnnotations( string $class, string $annotationClass = Annotation::class ): Klist {
return $this->getClassAnnotations( $class, $annotationClass )
->filter( fn( Annotation $annotation ) => $annotation->isBuildAnnotation() )
->map( fn( Annotation $annotation ) => $annotation->setAnnotatedClass( $class ) );
public function getPropertyAnnotation(
string $class,
string $property,
string $annotationClass = Annotation::class
): mixed {
$attributes = $this->reflection
->getReflectionClass( $class )
->getProperty( $property )
->getAttributes();

return listFrom( $attributes )
->map( fn( ReflectionAttribute $a ) => $a->newInstance() )
->maybe( fn( Klist $attributes ) => $this->filterAndInject( $attributes, $annotationClass ) )
->firstOrNull()
?->setAnnotatedClass( $class );
}

/**
* Maps attributes to annotations and applies property injection.
*
* @param Klist $attributes A list of attributes.
* @param string $annotationClass The annotation class to filter.
* @template T of Annotation
* @param Klist<Annotation> $instances
* @param class-string<T> $annotationClass
*
* @return Klist A list of filtered and injected annotations.
* @throws Exception
* @return Klist<T>
*/
private function mapAttributesToAnnotations( Klist $attributes, string $annotationClass ): Klist {
return $attributes
->filter( fn( $annotation ) => $annotation instanceof $annotationClass )
->foreach( fn( $annotation ) => $this->container->applyPropertyInjection( $annotation ) );
private function filterAndInject( Klist $instances, string $annotationClass ): Klist {
return $instances
->filter( fn( $i ) => is_a( $i, $annotationClass, true ) )
->foreach( fn( Annotation $ann ) => $this->container->applyPropertyInjection( $ann ) );
}
}
25 changes: 25 additions & 0 deletions src/Annotation/BuildAnnotation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Axpecto\Annotation;

use Attribute;
use Axpecto\ClassBuilder\BuildHandler;

#[Attribute]
class BuildAnnotation extends Annotation {
/**
* The builder for the annotation, used during the build phase.
*
* @var BuildHandler|null
*/
protected ?BuildHandler $builder = null;

/**
* Gets the BuildHandler for this annotation, if available.
*
* @return BuildHandler|null The builder for the annotation, or null if not set.
*/
public function getBuilder(): ?BuildHandler {
return $this->builder;

Check warning on line 23 in src/Annotation/BuildAnnotation.php

View check run for this annotation

Codecov / codecov/patch

src/Annotation/BuildAnnotation.php#L22-L23

Added lines #L22 - L23 were not covered by tests
}
}
Loading