Skip to content
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
1 change: 1 addition & 0 deletions Controller/Annotations/NamePrefix.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @Annotation
* @Target("CLASS")
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
class NamePrefix extends Annotation
{
}
Expand Down
107 changes: 102 additions & 5 deletions Controller/Annotations/NoRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,116 @@
namespace HandcraftedInTheAlps\RestRoutingBundle\Controller\Annotations;

use FOS\RestBundle\Controller\Annotations\NoRoute as OldNoRoute;
use Symfony\Component\Routing\Annotation\Route as BaseRoute;
use Symfony\Component\Routing\Annotation\Route as BaseAnnotationRoute;
use Symfony\Component\Routing\Attribute\Route as BaseAttributeRoute;

if (class_exists(BaseAttributeRoute::class)) {
/**
* Compatibility layer for Symfony 6.4 and later.
*
* @internal
*/
class CompatRoute extends BaseAttributeRoute
{
}
} else {
/**
* Compatibility layer for Symfony 6.3 and earlier.
*
* @internal
*/
class CompatRoute extends BaseAnnotationRoute
{
}
}

/**
* No Route annotation class.
*
* @Annotation
* @Target({"METHOD","CLASS"})
*/
class NoRoute extends BaseRoute
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class NoRoute extends CompatRoute
{
public function __construct(array $data)
{
parent::__construct($data);
/**
* @param array|string $data
* @param array|string|null $path
* @param array<string|\Stringable> $requirements
* @param string[]|string $methods
* @param string[]|string $schemes
*
* @throws \TypeError if the $data argument is an unsupported type
*/
public function __construct(
$data = [],
$path = null,
string $name = null,
array $requirements = [],
array $options = [],
array $defaults = [],
string $host = null,
$methods = [],
$schemes = [],
string $condition = null,
int $priority = null,
string $locale = null,
string $format = null,
bool $utf8 = null,
bool $stateless = null,
string $env = null
) {
// Use Reflection to get the constructor from the parent class two levels up (accounting for our compat definition)
$method = (new \ReflectionClass($this))->getParentClass()->getParentClass()->getMethod('__construct');

// The $data constructor parameter was removed in Symfony 6.0 in favor of named arguments
if ('data' === $method->getParameters()[0]->getName()) {
parent::__construct(
$data,
$path,
$name,
$requirements,
$options,
$defaults,
$host,
$methods,
$schemes,
$condition,
$priority,
$locale,
$format,
$utf8,
$stateless,
$env
);
} else {
if (\is_string($data)) {
$data = ['path' => $data];
} elseif (!\is_array($data)) {
throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data)));
} elseif (0 !== \count($data) && [] === array_intersect(array_keys($data), ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env'])) {
$localizedPaths = $data;
$data = ['path' => $localizedPaths];
}

parent::__construct(
$data['path'] ?? $path,
$data['name'] ?? $name,
$data['requirements'] ?? $requirements,
$data['options'] ?? $options,
$data['defaults'] ?? $defaults,
$data['host'] ?? $host,
$data['methods'] ?? $methods,
$data['schemes'] ?? $schemes,
$data['condition'] ?? $condition,
$data['priority'] ?? $priority,
$data['locale'] ?? $locale,
$data['format'] ?? $format,
$data['utf8'] ?? $utf8,
$data['stateless'] ?? $stateless,
$data['env'] ?? $env
);
}

if (!$this->getMethods()) {
$this->setMethods((array) $this->getMethod());
Expand Down
1 change: 1 addition & 0 deletions Controller/Annotations/Prefix.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @Annotation
* @Target("CLASS")
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
class Prefix extends Annotation
{
}
Expand Down
1 change: 1 addition & 0 deletions Controller/Annotations/RouteResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* @Annotation
* @Target("CLASS")
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
class RouteResource
{
/**
Expand Down
1 change: 1 addition & 0 deletions Controller/Annotations/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @Annotation
* @Target("CLASS")
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
class Version extends Annotation
{
}
Expand Down
71 changes: 56 additions & 15 deletions Routing/Loader/Reader/RestActionReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -433,12 +433,22 @@ private function getMethodArguments(\ReflectionMethod $method): array
// check if a parameter is coming from the request body
$ignoreParameters = [];
if (class_exists(ParamConverter::class)) {
$ignoreParameters = array_map(function ($annotation) {
return
$annotation instanceof ParamConverter &&
'fos_rest.request_body' === $annotation->getConverter()
? $annotation->getName() : null;
}, $this->annotationReader->getMethodAnnotations($method));
$ignoreParameters = array_merge(
array_map(function ($annotation) {
return
$annotation instanceof ParamConverter &&
'fos_rest.request_body' === $annotation->getConverter()
? $annotation->getName() : null;
}, $this->annotationReader->getMethodAnnotations($method)),
\PHP_VERSION_ID > 80000 ? array_map(function (ParamConverter $reflectionAttribute) {
$attribute = $reflectionAttribute->newInstance();

return
$attribute instanceof ParamConverter &&
'fos_rest.request_body' === $attribute->getConverter()
? $attribute->getName() : null;
}, $method->getAttributes(ParamConverter::class)) : []
);
}

// ignore several type hinted arguments
Expand Down Expand Up @@ -586,15 +596,26 @@ private function readRouteAnnotation(\ReflectionMethod $reflectionMethod): array
private function readClassAnnotation(\ReflectionClass $reflectionClass, string $annotationName): ?RouteAnnotation
{
$annotationClass = "HandcraftedInTheAlps\\RestRoutingBundle\\Controller\\Annotations\\$annotationName";
$oldAnnotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";

if (class_exists($annotationClass)) {
if (\PHP_VERSION_ID > 80000 && $attribute = $reflectionClass->getAttributes($annotationClass, \ReflectionAttribute::IS_INSTANCEOF)) {
return $attribute[0]->newInstance();
}

if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, $annotationClass)) {
return $annotation;
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, $annotationClass)) {
return $annotation;
}
}

$oldAnnotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
if (class_exists($oldAnnotationClass)) {
if (\PHP_VERSION_ID > 80000 && $oldAttribute = $reflectionClass->getAttributes($oldAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)) {
return $oldAttribute[0]->newInstance();
}

if ($oldAnnotation = $this->annotationReader->getClassAnnotation($reflectionClass, $oldAnnotationClass)) {
return $oldAnnotation;
if ($oldAnnotation = $this->annotationReader->getClassAnnotation($reflectionClass, $oldAnnotationClass)) {
return $oldAnnotation;
}
}

return null;
Expand All @@ -606,15 +627,23 @@ private function readMethodAnnotation(\ReflectionMethod $reflectionMethod, strin
$oldAnnotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";

if (class_exists($annotationClass)) {
if (\PHP_VERSION_ID > 80000 && $attribute = $reflectionMethod->getAttributes($annotationClass, \ReflectionAttribute::IS_INSTANCEOF)) {
return $attribute[0]->newInstance();
}

if ($annotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $annotationClass)) {
return $annotation;
}
}

if (class_exists($oldAnnotationClass)
&& $oldAnnotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $oldAnnotationClass)
) {
return $oldAnnotation;
if (class_exists($oldAnnotationClass)) {
if (\PHP_VERSION_ID > 80000 && $oldAttribute = $reflectionMethod->getAttributes($oldAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)) {
return $oldAttribute[0]->newInstance();
}

if ($oldAnnotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $oldAnnotationClass)) {
return $oldAnnotation;
}
}

return null;
Expand All @@ -637,6 +666,18 @@ private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, stri
}
}

if (\PHP_VERSION_ID > 80000) {
/** @var \ReflectionAttribute[] $reflectionAttributes */
$reflectionAttributes = array_merge(
class_exists($annotationClass) ? $reflectionMethod->getAttributes($annotationClass, \ReflectionAttribute::IS_INSTANCEOF) : [],
class_exists($oldAnnotationClass) ? $reflectionMethod->getAttributes($oldAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) : []
);

foreach ($reflectionAttributes as $reflectionAttribute) {
$annotations[] = $reflectionAttribute->newInstance();
}
}

return $annotations;
}

Expand Down
34 changes: 26 additions & 8 deletions Routing/Loader/Reader/RestControllerReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,38 +49,56 @@ public function getActionReader(): RestActionReader
return $this->actionReader;
}

/**
* @param \ReflectionClass $reflectionClass the ReflectionClass of the class from which
* the class annotations should be read
* @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
*/
private function readClassAnnotation(\ReflectionClass $reflectionClass, string $annotationName): ?object
{
if (\PHP_VERSION_ID > 80000 && $attributes = $reflectionClass->getAttributes($annotationName, \ReflectionAttribute::IS_INSTANCEOF)) {
return $attributes[0]->newInstance();
}

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

public function read(\ReflectionClass $reflectionClass): RestRouteCollection
{
$collection = new RestRouteCollection();
$collection->addResource(new FileResource($reflectionClass->getFileName()));

// read prefix annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, Prefix::class)) {
if ($annotation = $this->readClassAnnotation($reflectionClass, Prefix::class)) {
$this->actionReader->setRoutePrefix($annotation->value);
} elseif ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, OldPrefix::class)) {
} elseif ($annotation = $this->readClassAnnotation($reflectionClass, OldPrefix::class)) {
$this->actionReader->setRoutePrefix($annotation->value);
}

// read name-prefix annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, NamePrefix::class)) {
if ($annotation = $this->readClassAnnotation($reflectionClass, NamePrefix::class)) {
$this->actionReader->setNamePrefix($annotation->value);
} elseif ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, OldNamePrefix::class)) {
} elseif ($annotation = $this->readClassAnnotation($reflectionClass, OldNamePrefix::class)) {
$this->actionReader->setNamePrefix($annotation->value);
}

// read version annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, Version::class)) {
if ($annotation = $this->readClassAnnotation($reflectionClass, Version::class)) {
$this->actionReader->setVersions($annotation->value);
} elseif ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, OldVersion::class)) {
} elseif ($annotation = $this->readClassAnnotation($reflectionClass, OldVersion::class)) {
$this->actionReader->setVersions($annotation->value);
}

$resource = [];
// read route-resource annotation
if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, RouteResource::class)) {
if ($annotation = $this->readClassAnnotation($reflectionClass, RouteResource::class)) {
$resource = explode('_', $annotation->resource);
$this->actionReader->setPluralize($annotation->pluralize);
} elseif ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, OldRouteResource::class)) {
} elseif ($annotation = $this->readClassAnnotation($reflectionClass, OldRouteResource::class)) {
$resource = explode('_', $annotation->resource);
$this->actionReader->setPluralize($annotation->pluralize);
} elseif ($reflectionClass->implementsInterface(ClassResourceInterface::class)
Expand Down