Skip to content
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

[FEATURE] Added attribute support #63

Open
wants to merge 6 commits into
base: 1.x
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
}
],
"require": {
"php": "^7.2.5|^8.0",
"php": "^7.2.5|^8",
"illuminate/auth": "^6.0|^7.0|^8.0|^9.0",
"illuminate/config": "^6.0|^7.0|^8.0|^9.0",
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0",
Expand Down
2 changes: 2 additions & 0 deletions src/Mappings/BelongsToOrganisation.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace LaravelDoctrine\ACL\Mappings;

use Attribute;
use Doctrine\Common\Annotations\Annotation;
use Illuminate\Contracts\Config\Repository;

/**
* @Annotation
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class BelongsToOrganisation extends RelationAnnotation
{
/**
Expand Down
2 changes: 2 additions & 0 deletions src/Mappings/BelongsToOrganisations.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace LaravelDoctrine\ACL\Mappings;

use Attribute;
use Doctrine\Common\Annotations\Annotation;
use Illuminate\Contracts\Config\Repository;

/**
* @Annotation
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class BelongsToOrganisations extends RelationAnnotation
{
/**
Expand Down
2 changes: 2 additions & 0 deletions src/Mappings/HasPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace LaravelDoctrine\ACL\Mappings;

use Attribute;
use Doctrine\Common\Annotations\Annotation;
use Illuminate\Contracts\Config\Repository;

/**
* @Annotation
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class HasPermissions extends RelationAnnotation
{
/**
Expand Down
2 changes: 2 additions & 0 deletions src/Mappings/HasRoles.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace LaravelDoctrine\ACL\Mappings;

use Attribute;
use Doctrine\Common\Annotations\Annotation;
use Illuminate\Contracts\Config\Repository;

/**
* @Annotation
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class HasRoles extends RelationAnnotation
{
/**
Expand Down
103 changes: 103 additions & 0 deletions src/Mappings/Readers/AttributeAnnotationReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace LaravelDoctrine\ACL\Mappings\Readers;

use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use ReflectionMethod;

final class AttributeAnnotationReader implements Reader
{
/**
* @var Reader
*/
private $annotationReader;

/**
* @var AttributeReader
*/
private $attributeReader;

public function __construct(AttributeReader $attributeReader, Reader $annotationReader)
{
$this->attributeReader = $attributeReader;
$this->annotationReader = $annotationReader;
}

/**
* @return Annotation[]
*/
public function getClassAnnotations(ReflectionClass $class): array
{
$annotations = $this->attributeReader->getClassAnnotations($class);

if ([] !== $annotations) {
return $annotations;
}

return $this->annotationReader->getClassAnnotations($class);
}

/**
* @param class-string<T> $annotationName the name of the annotation
*
* @return T|null the Annotation or NULL, if the requested annotation does not exist
*
* @template T
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$annotation = $this->attributeReader->getClassAnnotation($class, $annotationName);

if (null !== $annotation) {
return $annotation;
}

return $this->annotationReader->getClassAnnotation($class, $annotationName);
}

/**
* @return Annotation[]
*/
public function getPropertyAnnotations(\ReflectionProperty $property): array
{
$propertyAnnotations = $this->attributeReader->getPropertyAnnotations($property);

if ([] !== $propertyAnnotations) {
return $propertyAnnotations;
}

return $this->annotationReader->getPropertyAnnotations($property);
}

/**
* @param class-string<T> $annotationName the name of the annotation
*
* @return T|null the Annotation or NULL, if the requested annotation does not exist
*
* @template T
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName)
{
$annotation = $this->attributeReader->getPropertyAnnotation($property, $annotationName);

if (null !== $annotation) {
return $annotation;
}

return $this->annotationReader->getPropertyAnnotation($property, $annotationName);
}

public function getMethodAnnotations(ReflectionMethod $method): array
{
throw new \BadMethodCallException('Not implemented');
}

/**
* @return mixed
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
throw new \BadMethodCallException('Not implemented');
}
}
95 changes: 95 additions & 0 deletions src/Mappings/Readers/AttributeReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace LaravelDoctrine\ACL\Mappings\Readers;

use Attribute;
use Gedmo\Mapping\Annotation\Annotation;
use ReflectionClass;

final class AttributeReader
{
/** @var array<string,bool> */
private $isRepeatableAttribute = [];

/**
* @return array<Annotation|Annotation[]>
*/
public function getClassAnnotations(ReflectionClass $class): array
{
return $this->convertToAttributeInstances($class->getAttributes());
}

/**
* @phpstan-param class-string $annotationName
*
* @return Annotation|Annotation[]|null
*/
public function getClassAnnotation(ReflectionClass $class, string $annotationName)
{
return $this->getClassAnnotations($class)[$annotationName] ?? null;
}

/**
* @return array<Annotation|Annotation[]>
*/
public function getPropertyAnnotations(\ReflectionProperty $property): array
{
return $this->convertToAttributeInstances($property->getAttributes());
}

/**
* @phpstan-param class-string $annotationName
*
* @return Annotation|Annotation[]|null
*/
public function getPropertyAnnotation(\ReflectionProperty $property, string $annotationName)
{
return $this->getPropertyAnnotations($property)[$annotationName] ?? null;
}

/**
* @param array<\ReflectionAttribute> $attributes
*
* @return array<string, Annotation|Annotation[]>
*/
private function convertToAttributeInstances(array $attributes): array
{
$instances = [];

foreach ($attributes as $attribute) {
$attributeName = $attribute->getName();
assert(is_string($attributeName));
// Make sure we only get Gedmo Annotations
if (!is_subclass_of($attributeName, Annotation::class)) {
continue;
}

$instance = $attribute->newInstance();
assert($instance instanceof Annotation);

if ($this->isRepeatable($attributeName)) {
if (!isset($instances[$attributeName])) {
$instances[$attributeName] = [];
}

$instances[$attributeName][] = $instance;
} else {
$instances[$attributeName] = $instance;
}
}

return $instances;
}

private function isRepeatable(string $attributeClassName): bool
{
if (isset($this->isRepeatableAttribute[$attributeClassName])) {
return $this->isRepeatableAttribute[$attributeClassName];
}

$reflectionClass = new ReflectionClass($attributeClassName);
$attribute = $reflectionClass->getAttributes()[0]->newInstance();

return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0;
}
}
4 changes: 1 addition & 3 deletions src/Mappings/RelationAnnotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

namespace LaravelDoctrine\ACL\Mappings;

use Doctrine\Common\Annotations\Annotation;

abstract class RelationAnnotation extends Annotation implements ConfigAnnotation
abstract class RelationAnnotation implements ConfigAnnotation
{
/**
* @var string
Expand Down
8 changes: 7 additions & 1 deletion src/Mappings/Subscribers/MappedEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

namespace LaravelDoctrine\ACL\Mappings\Subscribers;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\Reader;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use Illuminate\Contracts\Config\Repository;
use LaravelDoctrine\ACL\Mappings\ConfigAnnotation;
use LaravelDoctrine\ACL\Mappings\Readers\AttributeAnnotationReader;
use LaravelDoctrine\ACL\Mappings\Readers\AttributeReader;
use ReflectionClass;
use ReflectionProperty;

Expand Down Expand Up @@ -52,7 +55,10 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
$metadata = $eventArgs->getClassMetadata();

if (! $this->reader) {
return;
$this->reader = new AttributeAnnotationReader(
new AttributeReader(),
new AnnotationReader()
);
}

if ($this->isInstantiable($metadata) && $this->shouldBeMapped($metadata)) {
Expand Down