Skip to content

Implemented Telemetry, Cache, DefaultImplementation. Refactored components #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
{
"name": "rcrdortiz/axpecto",
"description": "PHP meta‑framework for modern, AI‑augmented, aspect‑oriented development.",
"keywords": [
"php",
"aop",
"dependency-injection",
"collections",
"meta-framework",
"ai"
],
"type": "library",
"version": "1.0.4",
"license": "MIT",
Expand Down
1 change: 0 additions & 1 deletion src/Annotation/Annotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*
* @package Axpecto\Aop
*
* @TODO Refactor this and possibly create a hierarchy of annotations with Annotation -> BuildAnnotation -> MethodExecutionAnnotation.
*/
#[Attribute]
class Annotation {
Expand Down
144 changes: 74 additions & 70 deletions src/Annotation/AnnotationReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,167 +5,171 @@
namespace Axpecto\Annotation;

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

/**
* Reads PHP8 attributes and turns them into AOP-style Annotation instances,
* filtering by class vs. method targets and injecting their properties via DI.
* AnnotationReader
*
* Reads PHP 8 attributes and turns them into Annotation instances,
* filtering by class, and annotating each instance
* with its declaring class and/or method name.
*
* @template A of Annotation
* @psalm-consistent-constructor
*/
class AnnotationReader {
/**
* @param ReflectionUtils $reflection
* Used to fetch native PHP ReflectionAttribute instances.
*/
public function __construct(
private readonly Container $container,
private readonly ReflectionUtils $reflection
) {
}

/**
* Fetch all annotations of a given type on a class.
* Fetch all annotations of the given type on a class.
*
* @template T
* @param class-string<T> $class
* @param class-string<A> $annotationClass
* @template T of Annotation
* @param class-string<A> $class
* @param class-string<T> $annotationClass
*
* @return Klist<T> A list of instantiated annotations, each with its
* ->setAnnotatedClass($class) already applied.
*
* @return Klist<T>
* @throws ReflectionException
*/
public function getClassAnnotations(
string $class,
string $annotationClass,
string $annotationClass
): Klist {
$raw = $this->reflection->getClassAttributes( $class );

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

/**
* Fetch all annotations of a given type on a method.
* Fetch all annotations of the given type on a specific method.
*
* @template T
* @param class-string<T> $class
* @template T of Annotation
* @param class-string<A> $class
* @param string $method
* @param class-string<A> $annotationClass
* @param class-string<T> $annotationClass
*
* @return Klist<T> A list of instantiated annotations, each with
* ->setAnnotatedClass($class)
* and ->setAnnotatedMethod($method) applied.
*
* @return Klist<T>
* @throws ReflectionException
*/
public function getMethodAnnotations(
string $class,
string $method,
string $annotationClass,
string $annotationClass
): Klist {
$raw = $this->reflection->getMethodAttributes( $class, $method );

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

/**
* Fetch both classlevel and methodlevel annotations of a given type.
* Fetch both class-level and method-level annotations of a given type.
*
* @template T
* @param class-string<T> $class
* @param class-string<A> $annotationClass
* @template T of Annotation
* @param class-string<A> $class
* @param class-string<T> $annotationClass
*
* @return Klist<T> All matching annotations on the class itself
* and on any of its methods.
*
* @return Klist<T>
* @throws ReflectionException
*/
public function getAllAnnotations(
string $class,
string $annotationClass = Annotation::class
string $annotationClass,
): Klist {
$classAnns = $this->getClassAnnotations( $class, $annotationClass );
$classAnns = $this->getClassAnnotations( $class, $annotationClass );

$methodAnns = $this->reflection
->getAnnotatedMethods( $class, $annotationClass )
->map( fn( \ReflectionMethod $m ) => $this->getMethodAnnotations( $class, $m->getName(), $annotationClass )
)
->map( fn( ReflectionMethod $m ) => $this->getMethodAnnotations( $class, $m->getName(), $annotationClass ) )
->flatten();

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

/**
* Fetch all annotations of a given type on one of a method’s parameters.
* Fetch all annotations of the given type on a single method parameter.
*
* @template T
* @param class-string<T> $class
* @template T of Annotation
* @param class-string<A> $class
* @param string $method
* @param string $parameterName
* @param class-string<A> $annotationClass
* @param class-string<T> $annotationClass
*
* @return Klist<T> A list (possibly empty) of annotations on that parameter,
* each with ->setAnnotatedClass() and ->setAnnotatedMethod().
*
* @return Klist<A>
* @throws ReflectionException
*/
public function getParameterAnnotations(
string $class,
string $method,
string $parameterName,
string $annotationClass,
string $annotationClass
): Klist {
$parameter = listFrom( $this->reflection->getClassMethod( $class, $method )->getParameters() )
$param = listFrom( $this->reflection->getClassMethod( $class, $method )->getParameters() )
->filter( fn( ReflectionParameter $p ) => $p->getName() === $parameterName )
->firstOrNull();

if ( ! $parameter ) {
if ( $param === null ) {
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 ) );
return listFrom( $param->getAttributes() )
->map( fn( ReflectionAttribute $attr ) => $attr->newInstance() )
->filter( fn( $inst ) => $inst instanceof $annotationClass )
->map( fn( Annotation $ann ) => $ann
->setAnnotatedClass( $class )
->setAnnotatedMethod( $method )
);
}

/**
* Fetch a single annotation of a given type on a property.
* Fetch exactly one annotation of the given type on a class property.
*
* @template T
* @param class-string<T> $class
* @template T of Annotation
* @param class-string<object> $class
* @param string $property
* @param class-string<A> $annotationClass
* @param class-string<T> $annotationClass
*
* @return T The first matching annotation, or null if none.
*
* @return A|null
* @throws ReflectionException
*/
public function getPropertyAnnotation(
string $class,
string $property,
string $annotationClass = Annotation::class
): mixed {
$attributes = $this->reflection
): ?Annotation {
$attrs = $this->reflection
->getReflectionClass( $class )
->getProperty( $property )
->getAttributes();

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

/**
* @template T of Annotation
* @param Klist<Annotation> $instances
* @param class-string<T> $annotationClass
*
* @return Klist<T>
*/
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 ) );
}
}
123 changes: 123 additions & 0 deletions src/Annotation/AnnotationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

namespace Axpecto\Annotation;

use Axpecto\Collection\Klist;
use Axpecto\Container\DependencyResolver;
use ReflectionException;

/**
* Reads PHP8 attributes and turns them into AOP-style Annotation instances,
* filtering by class vs. method targets and injecting their properties via DI.
*
* @template A of Annotation
* @psalm-consistent-constructor
*/
class AnnotationService {

public function __construct(
private readonly AnnotationReader $reader,
private readonly DependencyResolver $dependencyResolver,
) {
}

/**
* Fetch all annotations of a given type on a class.
*
* @template T
* @param class-string<T> $class
* @param class-string<A> $annotationClass
*
* @return Klist<A>
* @throws ReflectionException
*/
public function getClassAnnotations(

Check failure on line 34 in src/Annotation/AnnotationService.php

View workflow job for this annotation

GitHub Actions / psalm

PossiblyUnusedMethod

src/Annotation/AnnotationService.php:34:18: PossiblyUnusedMethod: Cannot find any calls to method Axpecto\Annotation\AnnotationService::getClassAnnotations (see https://psalm.dev/087)
string $class,
string $annotationClass,
): Klist {
return $this->reader->getClassAnnotations( $class, $annotationClass )
->foreach( fn( Annotation $a ) => $this->dependencyResolver->applyPropertyInjection( $a ) );
}

/**
* Fetch all annotations of a given type on a method.
*
* @template T
* @param class-string<T> $class
* @param string $method
* @param class-string<A> $annotationClass
*
* @return Klist<T>
* @throws ReflectionException
*/
public function getMethodAnnotations(
string $class,
string $method,
string $annotationClass,
): Klist {
return $this->reader->getMethodAnnotations( $class, $method, $annotationClass )
->foreach( fn( Annotation $a ) => $this->dependencyResolver->applyPropertyInjection( $a ) );
}

/**
* Fetch both class‑level and method‑level annotations of a given type.
*
* @template T
* @param class-string<T> $class
* @param class-string<A> $annotationClass
*
* @return Klist<T>
* @throws ReflectionException
*/
public function getAllAnnotations(
string $class,
string $annotationClass = Annotation::class
): Klist {
return $this->reader->getAllAnnotations( $class, $annotationClass )
->foreach( fn( Annotation $a ) => $this->dependencyResolver->applyPropertyInjection( $a ) );
}

/**
* Fetch all annotations of a given type on one of a method’s parameters.
*
* @template T
* @param class-string<T> $class
* @param string $method
* @param string $parameterName
* @param class-string<A> $annotationClass
*
* @return Klist<A>
* @throws ReflectionException
*/
public function getParameterAnnotations(

Check failure on line 92 in src/Annotation/AnnotationService.php

View workflow job for this annotation

GitHub Actions / psalm

PossiblyUnusedMethod

src/Annotation/AnnotationService.php:92:18: PossiblyUnusedMethod: Cannot find any calls to method Axpecto\Annotation\AnnotationService::getParameterAnnotations (see https://psalm.dev/087)
string $class,
string $method,
string $parameterName,
string $annotationClass,
): Klist {
return $this->reader->getParameterAnnotations( $class, $method, $parameterName, $annotationClass )
->foreach( fn( Annotation $a ) => $this->dependencyResolver->applyPropertyInjection( $a ) );
}

/**
* Fetch a single annotation of a given type on a property.
*
* @template T
* @param class-string<T> $class
* @param string $property
* @param class-string<A> $annotationClass
*
* @return A|null
* @throws ReflectionException
*/
public function getPropertyAnnotation(

Check failure on line 113 in src/Annotation/AnnotationService.php

View workflow job for this annotation

GitHub Actions / psalm

PossiblyUnusedMethod

src/Annotation/AnnotationService.php:113:18: PossiblyUnusedMethod: Cannot find any calls to method Axpecto\Annotation\AnnotationService::getPropertyAnnotation (see https://psalm.dev/087)
string $class,
string $property,
string $annotationClass = Annotation::class
): mixed {
$annotation = $this->reader->getPropertyAnnotation( $class, $property, $annotationClass );
$this->dependencyResolver->applyPropertyInjection( $annotation );

return $annotation;

Check failure on line 121 in src/Annotation/AnnotationService.php

View workflow job for this annotation

GitHub Actions / psalm

UnevaluatedCode

src/Annotation/AnnotationService.php:121:3: UnevaluatedCode: Expressions after return/throw/continue (see https://psalm.dev/084)
}
}
Loading
Loading