diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index b912e993..a95aa369 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -17,38 +17,37 @@ jobs:
fail-fast: false
matrix:
php-version:
- - "7.3"
- - "7.4"
- - "8.0"
- "8.1"
- "8.2"
- "8.3"
dependencies:
- "highest"
- "lowest"
+ remove-annotations:
+ - "yes"
+ - "no"
symfony-require:
- "^3.0"
- "^4.0"
- "^5.0"
+ - "^6.0"
include:
- php-version: 8.4
symfony-require: "^5.0"
composer-options: "--ignore-platform-req=php+" # TODO remove once phpspec/prophecy supports PHP 8.4
- - php-version: 8.0
- symfony-require: "^6.0"
- - php-version: 8.1
- symfony-require: "^6.0"
- - php-version: 8.2
- symfony-require: "^6.0"
- - php-version: 8.3
- symfony-require: "^6.0"
- php-version: 8.4
symfony-require: "^6.0"
composer-options: "--ignore-platform-req=php+" # TODO remove once phpspec/prophecy supports PHP 8.4
- php-version: 8.2
symfony-require: "^7.0"
+ - php-version: 8.2
+ symfony-require: "^7.0"
+ remove-annotations: "yes"
+ - php-version: 8.3
+ symfony-require: "^7.0"
- php-version: 8.3
symfony-require: "^7.0"
+ remove-annotations: "yes"
- php-version: 8.4
symfony-require: "^7.0"
composer-options: "--ignore-platform-req=php+" # TODO remove once phpspec/prophecy supports PHP 8.4
@@ -65,6 +64,13 @@ jobs:
ini-values: "zend.assertions=1"
tools: "flex"
+ - name: "Remove remove-annotations if required"
+ if: "${{ matrix.remove-annotations == 'yes' }}"
+ env:
+ SYMFONY_REQUIRE: "${{ matrix.symfony-require }}"
+ run: |
+ composer remove --no-update --dev doctrine/annotations
+
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
env:
diff --git a/.github/workflows/coding-standards.yaml b/.github/workflows/coding-standards.yaml
index acb0cc78..581d9299 100644
--- a/.github/workflows/coding-standards.yaml
+++ b/.github/workflows/coding-standards.yaml
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
php-version:
- - "7.4"
+ - "8.1"
steps:
- name: "Checkout"
diff --git a/README.md b/README.md
index 07a0d40d..8987fbe2 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,17 @@ This will resolve the latest stable version.
Otherwise, install the library and setup the autoloader yourself.
+If you want to use [**annotations**](#annotations) for configuration you need
+to install the `doctrine/annotations` package:
+
+```sh
+composer require doctrine/annotations
+```
+
+If your app uses PHP 8.1 or higher it is recommended to use native PHP
+attributes.
+In this case you don't need to install the Doctrine package.
+
### Working With Symfony
There is a bundle for that! Install the
diff --git a/composer.json b/composer.json
index 387935dd..edfc7429 100644
--- a/composer.json
+++ b/composer.json
@@ -17,22 +17,22 @@
}
],
"require": {
- "php": "^7.2 | ^8.0",
- "doctrine/annotations": "^1.13.2 || ^2.0",
+ "php": "^8.1",
"jms/metadata": "^2.0",
"jms/serializer": "^3.18.2",
- "symfony/expression-language": "~3.0 || ~4.0 || ~5.0 || ~6.0 || ~7.0"
+ "symfony/expression-language": "^3.4.47 || ~4.0 || ~5.0 || ~6.0 || ~7.0"
},
"require-dev": {
- "phpunit/phpunit": "^7 | ^9.5.10",
+ "phpunit/phpunit": "^9.5.10",
+ "doctrine/annotations": "^1.13.2 || ^2.0",
"doctrine/coding-standard": "^12.0",
"doctrine/persistence": "^1.3.4 | ^2.0 | ^3.0",
"pagerfanta/core": "^2.4 || ^3.0",
"phpdocumentor/type-resolver": "^1.5.1",
"phpspec/prophecy-phpunit": "^2.0.1",
"phpspec/prophecy": "^1.16",
- "symfony/routing": "~3.0 || ~4.0 || ~5.0 || ~6.0 || ~7.0",
- "symfony/yaml": "~3.0 || ~4.0 || ~5.0 || ~6.0 || ~7.0",
+ "symfony/routing": "^3.4.47 || ~4.0 || ~5.0 || ~6.0 || ~7.0",
+ "symfony/yaml": "^3.4.47 || ~4.0 || ~5.0 || ~6.0 || ~7.0",
"twig/twig": "^1.43 || ^2.13 || ^3.0"
},
"suggest": {
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 60a288af..d90ad003 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -48,6 +48,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Configuration/Annotation/Embedded.php b/src/Configuration/Annotation/Embedded.php
index 25dd394d..0cb4bc3f 100644
--- a/src/Configuration/Annotation/Embedded.php
+++ b/src/Configuration/Annotation/Embedded.php
@@ -39,9 +39,10 @@ class Embedded
public $exclusion = null;
/**
- * @param string|array $content
+ * @param array|string|null $values
+ * @param array|string|null $content
*/
- public function __construct(array $values = [], $content = null, ?string $type = null, ?string $xmlElementName = null, ?Exclusion $exclusion = null)
+ public function __construct($values = [], $content = null, ?string $type = null, ?string $xmlElementName = null, ?Exclusion $exclusion = null)
{
$this->loadAnnotationParameters(get_defined_vars());
}
diff --git a/src/Configuration/Annotation/Exclusion.php b/src/Configuration/Annotation/Exclusion.php
index 27061ab6..2966b7bb 100644
--- a/src/Configuration/Annotation/Exclusion.php
+++ b/src/Configuration/Annotation/Exclusion.php
@@ -44,7 +44,10 @@ final class Exclusion
*/
public $excludeIf = null;
- public function __construct(array $values = [], ?array $groups = null, ?string $sinceVersion = null, ?string $untilVersion = null, ?int $maxDepth = null, ?string $excludeIf = null)
+ /**
+ * @param array|null $values
+ */
+ public function __construct($values = [], ?array $groups = null, ?string $sinceVersion = null, ?string $untilVersion = null, ?int $maxDepth = null, ?string $excludeIf = null)
{
$this->loadAnnotationParameters(get_defined_vars());
}
diff --git a/src/Configuration/Annotation/Relation.php b/src/Configuration/Annotation/Relation.php
index 398bce3a..54eb2a95 100644
--- a/src/Configuration/Annotation/Relation.php
+++ b/src/Configuration/Annotation/Relation.php
@@ -44,10 +44,11 @@ final class Relation
public $exclusion = null;
/**
+ * @param array|string|null $values
* @param string|Route $href
* @param string|Embedded $embedded
*/
- public function __construct(array $values = [], ?string $name = null, $href = null, $embedded = null, array $attributes = [], ?Exclusion $exclusion = null)
+ public function __construct($values = [], ?string $name = null, $href = null, $embedded = null, array $attributes = [], ?Exclusion $exclusion = null)
{
$this->loadAnnotationParameters(get_defined_vars());
}
diff --git a/src/Configuration/Annotation/RelationProvider.php b/src/Configuration/Annotation/RelationProvider.php
index 4b74dc9f..3c8ae3ab 100644
--- a/src/Configuration/Annotation/RelationProvider.php
+++ b/src/Configuration/Annotation/RelationProvider.php
@@ -20,7 +20,10 @@ class RelationProvider
*/
public $name;
- public function __construct(array $values = [], ?string $name = null)
+ /**
+ * @param array|string|null $values
+ */
+ public function __construct($values = [], ?string $name = null)
{
$this->loadAnnotationParameters(get_defined_vars());
}
diff --git a/src/Configuration/Annotation/Route.php b/src/Configuration/Annotation/Route.php
index 629fc758..65d6313f 100644
--- a/src/Configuration/Annotation/Route.php
+++ b/src/Configuration/Annotation/Route.php
@@ -38,10 +38,11 @@ class Route
public $generator = null;
/**
+ * @param array|string|null $values
* @param array|string $parameters
* @param bool|string $absolute
*/
- public function __construct(array $values = [], ?string $name = null, $parameters = null, $absolute = false, ?string $generator = null)
+ public function __construct($values = [], ?string $name = null, $parameters = null, $absolute = false, ?string $generator = null)
{
$this->loadAnnotationParameters(get_defined_vars());
}
diff --git a/src/HateoasBuilder.php b/src/HateoasBuilder.php
index 81a5fb9a..3f3be6e2 100644
--- a/src/HateoasBuilder.php
+++ b/src/HateoasBuilder.php
@@ -8,6 +8,7 @@
use Doctrine\Common\Annotations\FileCacheReader;
use Hateoas\Configuration\Metadata\ConfigurationExtensionInterface;
use Hateoas\Configuration\Metadata\Driver\AnnotationDriver;
+use Hateoas\Configuration\Metadata\Driver\AttributeDriver;
use Hateoas\Configuration\Metadata\Driver\ExtensionDriver;
use Hateoas\Configuration\Metadata\Driver\XmlDriver;
use Hateoas\Configuration\Metadata\Driver\YamlDriver;
@@ -385,33 +386,31 @@ public function replaceMetadataDir(string $dir, string $namespacePrefix = ''): H
private function buildMetadataFactory(): MetadataFactoryInterface
{
+ $expressionEvaluator = $this->getExpressionEvaluator();
+
+ $typeParser = new Parser();
+
$annotationReader = $this->annotationReader;
+ $drivers = [new AttributeDriver($expressionEvaluator, $this->chainProvider, $typeParser)];
- if (null === $annotationReader) {
+ if (null === $annotationReader && class_exists(AnnotationReader::class)) {
$annotationReader = new AnnotationReader();
if (null !== $this->cacheDir) {
$this->createDir($this->cacheDir . '/annotations');
$annotationReader = new FileCacheReader($annotationReader, $this->cacheDir . '/annotations', $this->debug);
}
- }
- $expressionEvaluator = $this->getExpressionEvaluator();
-
- $typeParser = new Parser();
+ $drivers[] = new AnnotationDriver($annotationReader, $expressionEvaluator, $this->chainProvider, $typeParser);
+ }
if (!empty($this->metadataDirs)) {
- $fileLocator = new FileLocator($this->metadataDirs);
- $metadataDriver = new DriverChain([
- new YamlDriver($fileLocator, $expressionEvaluator, $this->chainProvider, $typeParser),
- new XmlDriver($fileLocator, $expressionEvaluator, $this->chainProvider, $typeParser),
- new AnnotationDriver($annotationReader, $expressionEvaluator, $this->chainProvider, $typeParser),
- ]);
- } else {
- $metadataDriver = new AnnotationDriver($annotationReader, $expressionEvaluator, $this->chainProvider, $typeParser);
+ $fileLocator = new FileLocator($this->metadataDirs);
+ $drivers[] = new YamlDriver($fileLocator, $expressionEvaluator, $this->chainProvider, $typeParser);
+ $drivers[] = new XmlDriver($fileLocator, $expressionEvaluator, $this->chainProvider, $typeParser);
}
- $metadataDriver = new ExtensionDriver($metadataDriver, $this->configurationExtensions);
+ $metadataDriver = new ExtensionDriver(new DriverChain($drivers), $this->configurationExtensions);
$metadataFactory = new MetadataFactory($metadataDriver, null, $this->debug);
$metadataFactory->setIncludeInterfaces($this->includeInterfaceMetadata);
diff --git a/src/Representation/AbstractSegmentedRepresentation.php b/src/Representation/AbstractSegmentedRepresentation.php
index f0e67d92..d824b027 100644
--- a/src/Representation/AbstractSegmentedRepresentation.php
+++ b/src/Representation/AbstractSegmentedRepresentation.php
@@ -9,6 +9,7 @@
/**
* @Serializer\ExclusionPolicy("all")
*/
+#[Serializer\ExclusionPolicy('all')]
abstract class AbstractSegmentedRepresentation extends RouteAwareRepresentation
{
/**
@@ -18,6 +19,9 @@ abstract class AbstractSegmentedRepresentation extends RouteAwareRepresentation
*
* @var int
*/
+ #[Serializer\Expose]
+ #[Serializer\Type('integer')]
+ #[Serializer\XmlAttribute]
private $limit;
/**
@@ -27,6 +31,9 @@ abstract class AbstractSegmentedRepresentation extends RouteAwareRepresentation
*
* @var int
*/
+ #[Serializer\Expose]
+ #[Serializer\Type('integer')]
+ #[Serializer\XmlAttribute]
private $total;
/**
diff --git a/src/Representation/CollectionRepresentation.php b/src/Representation/CollectionRepresentation.php
index 29537f32..4dd0b9cc 100644
--- a/src/Representation/CollectionRepresentation.php
+++ b/src/Representation/CollectionRepresentation.php
@@ -16,6 +16,14 @@
* embedded = @Hateoas\Embedded("expr(object.getResources())")
* )
*/
+#[Serializer\ExclusionPolicy('all')]
+#[Serializer\XmlRoot('collection')]
+#[Hateoas\Relation(
+ 'items',
+ embedded: new Hateoas\Embedded(
+ content: 'expr(object.getResources())',
+ ),
+)]
class CollectionRepresentation
{
/**
diff --git a/src/Representation/OffsetRepresentation.php b/src/Representation/OffsetRepresentation.php
index bf2847b7..61048ede 100644
--- a/src/Representation/OffsetRepresentation.php
+++ b/src/Representation/OffsetRepresentation.php
@@ -54,6 +54,50 @@
* )
* )
*/
+#[Serializer\ExclusionPolicy('all')]
+#[Serializer\XmlRoot('collection')]
+#[Serializer\AccessorOrder(order: 'custom', custom: ['offset', 'limit', 'total'])]
+#[Hateoas\Relation(
+ 'first',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters(0))',
+ absolute: 'expr(object.isAbsolute())',
+ ),
+)]
+#[Hateoas\Relation(
+ 'last',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters((object.getTotal() - 1) - (object.getTotal() - 1) % object.getLimit()))',
+ absolute: 'expr(object.isAbsolute())',
+ ),
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(object.getTotal() === null)',
+ )
+)]
+#[Hateoas\Relation(
+ 'next',
+ href: new Hateoas\Route(
+ name: 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters(object.getOffset() + object.getLimit()))',
+ absolute: 'expr(object.isAbsolute())'
+ ),
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(object.getTotal() !== null && (object.getOffset() + object.getLimit()) >= object.getTotal())',
+ ),
+)]
+#[Hateoas\Relation(
+ 'previous',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters((object.getOffset() > object.getLimit()) ? object.getOffset() - object.getLimit() : 0))',
+ absolute: 'expr(object.isAbsolute())',
+ ),
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(! object.getOffset())',
+ ),
+)]
class OffsetRepresentation extends AbstractSegmentedRepresentation
{
/**
@@ -62,6 +106,8 @@ class OffsetRepresentation extends AbstractSegmentedRepresentation
*
* @var int
*/
+ #[Serializer\Expose]
+ #[Serializer\XmlAttribute]
private $offset;
/**
diff --git a/src/Representation/PaginatedRepresentation.php b/src/Representation/PaginatedRepresentation.php
index ef864bc5..73beeb6c 100644
--- a/src/Representation/PaginatedRepresentation.php
+++ b/src/Representation/PaginatedRepresentation.php
@@ -54,6 +54,50 @@
* )
* )
*/
+#[Serializer\ExclusionPolicy('all')]
+#[Serializer\XmlRoot('collection')]
+#[Serializer\AccessorOrder(order: 'custom', custom: ['page', 'limit', 'pages', 'total'])]
+#[Hateoas\Relation(
+ 'first',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters(1))',
+ absolute: 'expr(object.isAbsolute())',
+ ),
+)]
+#[Hateoas\Relation(
+ 'last',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters(object.getPages()))',
+ absolute: 'expr(object.isAbsolute())'
+ ),
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(object.getPages() === null)',
+ ),
+)]
+#[Hateoas\Relation(
+ 'next',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters(object.getPage() + 1))',
+ absolute: 'expr(object.isAbsolute())',
+ ),
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(object.getPages() !== null && (object.getPage() + 1) > object.getPages())',
+ ),
+)]
+#[Hateoas\Relation(
+ 'previous',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters(object.getPage() - 1))',
+ absolute: 'expr(object.isAbsolute())',
+ ),
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr((object.getPage() - 1) < 1)',
+ ),
+)]
class PaginatedRepresentation extends AbstractSegmentedRepresentation
{
/**
@@ -63,6 +107,9 @@ class PaginatedRepresentation extends AbstractSegmentedRepresentation
*
* @var int
*/
+ #[Serializer\Expose]
+ #[Serializer\Type('integer')]
+ #[Serializer\XmlAttribute]
private $page;
/**
@@ -72,6 +119,9 @@ class PaginatedRepresentation extends AbstractSegmentedRepresentation
*
* @var int
*/
+ #[Serializer\Expose]
+ #[Serializer\Type('integer')]
+ #[Serializer\XmlAttribute]
private $pages;
/**
diff --git a/src/Representation/RouteAwareRepresentation.php b/src/Representation/RouteAwareRepresentation.php
index 6532c90b..9dac48d0 100644
--- a/src/Representation/RouteAwareRepresentation.php
+++ b/src/Representation/RouteAwareRepresentation.php
@@ -19,6 +19,15 @@
* )
* )
*/
+#[Serializer\ExclusionPolicy('all')]
+#[Hateoas\Relation(
+ 'self',
+ href: new Hateoas\Route(
+ 'expr(object.getRoute())',
+ parameters: 'expr(object.getParameters())',
+ absolute: 'expr(object.isAbsolute())',
+ ),
+)]
class RouteAwareRepresentation
{
/**
@@ -27,6 +36,8 @@ class RouteAwareRepresentation
*
* @var mixed
*/
+ #[Serializer\Inline]
+ #[Serializer\Expose]
private $inline;
/**
diff --git a/src/Representation/VndErrorRepresentation.php b/src/Representation/VndErrorRepresentation.php
index 756beb41..1072603f 100644
--- a/src/Representation/VndErrorRepresentation.php
+++ b/src/Representation/VndErrorRepresentation.php
@@ -33,6 +33,29 @@
* )
* )
*/
+#[Serializer\ExclusionPolicy('all')]
+#[Serializer\XmlRoot('resource')]
+#[Hateoas\Relation(
+ 'help',
+ href: 'expr(object.getHelp())',
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(object.getHelp() === null)',
+ ),
+)]
+#[Hateoas\Relation(
+ 'describes',
+ href: 'expr(object.getDescribes())',
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(object.getDescribes() === null)',
+ ),
+)]
+#[Hateoas\Relation(
+ 'about',
+ href: 'expr(object.getAbout())',
+ exclusion: new Hateoas\Exclusion(
+ excludeIf: 'expr(object.getAbout() === null)',
+ ),
+)]
class VndErrorRepresentation
{
/**
@@ -41,6 +64,8 @@ class VndErrorRepresentation
*
* @var string
*/
+ #[Serializer\Expose]
+ #[Serializer\Type('string')]
private $message;
/**
@@ -50,6 +75,9 @@ class VndErrorRepresentation
*
* @var int
*/
+ #[Serializer\Expose]
+ #[Serializer\XmlAttribute]
+ #[Serializer\Type('integer')]
private $logref;
/**
diff --git a/tests/Hateoas/Tests/Configuration/Metadata/Driver/AnnotationDriverTest.php b/tests/Hateoas/Tests/Configuration/Metadata/Driver/AnnotationDriverTest.php
index 7c7556e0..9aa85059 100644
--- a/tests/Hateoas/Tests/Configuration/Metadata/Driver/AnnotationDriverTest.php
+++ b/tests/Hateoas/Tests/Configuration/Metadata/Driver/AnnotationDriverTest.php
@@ -9,6 +9,13 @@
class AnnotationDriverTest extends AbstractDriverTest
{
+ public function setUp(): void
+ {
+ if (!class_exists(AnnotationReader::class)) {
+ $this->markTestSkipped('AnnotationReader is not available');
+ }
+ }
+
public function createDriver()
{
return new AnnotationDriver(
diff --git a/tests/Hateoas/Tests/Configuration/Metadata/Driver/AttributeDriverTest.php b/tests/Hateoas/Tests/Configuration/Metadata/Driver/AttributeDriverTest.php
index 98a6c105..5e7e8e3e 100644
--- a/tests/Hateoas/Tests/Configuration/Metadata/Driver/AttributeDriverTest.php
+++ b/tests/Hateoas/Tests/Configuration/Metadata/Driver/AttributeDriverTest.php
@@ -5,17 +5,10 @@
namespace Hateoas\Tests\Configuration\Metadata\Driver;
use Hateoas\Configuration\Metadata\Driver\AttributeDriver;
-use Hateoas\Tests\Fixtures\UserPhpAttributes;
+use Hateoas\Tests\Fixtures\Attribute\User;
class AttributeDriverTest extends AbstractDriverTest
{
- public function setUp(): void
- {
- if (PHP_VERSION_ID < 80100) {
- $this->markTestSkipped('AttributeDriver is available as of PHP 8.1.0');
- }
- }
-
public function createDriver()
{
return new AttributeDriver(
@@ -27,6 +20,6 @@ public function createDriver()
protected function getUserClass(): string
{
- return UserPhpAttributes::class;
+ return User::class;
}
}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/AdrienBrault.php b/tests/Hateoas/Tests/Fixtures/Attribute/AdrienBrault.php
new file mode 100644
index 00000000..5a5e49ec
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/AdrienBrault.php
@@ -0,0 +1,76 @@
+reference2 = $reference2;
+ }
+
+ public function getReference2()
+ {
+ return $this->reference2;
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/CircularReference2.php b/tests/Hateoas/Tests/Fixtures/Attribute/CircularReference2.php
new file mode 100644
index 00000000..47e0912e
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/CircularReference2.php
@@ -0,0 +1,28 @@
+reference1 = $reference1;
+ }
+
+ public function getReference1()
+ {
+ return $this->reference1;
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/Computer.php b/tests/Hateoas/Tests/Fixtures/Attribute/Computer.php
new file mode 100644
index 00000000..722eae26
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/Computer.php
@@ -0,0 +1,26 @@
+name = $name;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/Foo1.php b/tests/Hateoas/Tests/Fixtures/Attribute/Foo1.php
new file mode 100644
index 00000000..c50c10eb
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/Foo1.php
@@ -0,0 +1,19 @@
+a = new Gh236Bar();
+ $this->a->inner = new Gh236Bar();
+
+ $this->b = new Gh236Bar();
+ $this->b->xxx = 'zzz';
+ $this->b->inner = new Gh236Bar();
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/Post.php b/tests/Hateoas/Tests/Fixtures/Attribute/Post.php
new file mode 100644
index 00000000..3b0abc90
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/Post.php
@@ -0,0 +1,29 @@
+ 'expr(object.getId())'],
+ ),
+)]
+class Post
+{
+ private $id;
+
+ public function __construct($id)
+ {
+ $this->id = $id;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/Smartphone.php b/tests/Hateoas/Tests/Fixtures/Attribute/Smartphone.php
new file mode 100644
index 00000000..6790d414
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/Smartphone.php
@@ -0,0 +1,26 @@
+name = $name;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/User.php b/tests/Hateoas/Tests/Fixtures/Attribute/User.php
new file mode 100644
index 00000000..dc80426a
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/User.php
@@ -0,0 +1,49 @@
+ 'application/json'])]
+#[Hateoas\Relation('foo', href: new Hateoas\Route(name: 'user_get', parameters: ['id' => 'expr(object.getId())']), embedded: 'expr(object.getFoo())')]
+#[Hateoas\Relation('bar', href: 'foo', embedded: new Hateoas\Embedded(content: 'data', xmlElementName: 'barTag'))]
+#[Hateoas\Relation('baz', href: new Hateoas\Route(name: 'user_get', parameters: ['id' => 'expr(object.getId())'], absolute: true), embedded: 'expr(object.getFoo())')]
+#[Hateoas\Relation('boom', href: new Hateoas\Route(name: 'user_get', parameters: ['id' => 'expr(object.getId())'], absolute: false), embedded: 'expr(object.getFoo())')]
+#[Hateoas\Relation('badaboom', embedded: 'expr(object.getFoo())')]
+#[Hateoas\Relation(
+ 'hello',
+ href: '/hello',
+ exclusion: new Hateoas\Exclusion(
+ groups: ['group1', 'group2'],
+ sinceVersion: '1',
+ untilVersion: '2.2',
+ maxDepth: 42,
+ excludeIf: 'foo',
+ ),
+ embedded: new Hateoas\Embedded(
+ 'hello',
+ xmlElementName: 'barTag',
+ type: 'string',
+ exclusion: new Hateoas\Exclusion(
+ groups: ['group3', 'group4'],
+ sinceVersion: '1.1',
+ untilVersion: '2.3',
+ maxDepth: 43,
+ excludeIf: 'bar',
+ )
+ )
+)]
+#[Hateoas\Relation(name: 'attribute_with_expression', href: 'baz', attributes: ['baz' => 'expr(object.getId())'])]
+#[Hateoas\RelationProvider(name: 'Hateoas\Tests\Fixtures\Attribute\User::getRelations')]
+class User
+{
+ /**
+ * do not use for functional testing
+ */
+ public static function getRelations()
+ {
+ return [];
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/UsersRepresentation.php b/tests/Hateoas/Tests/Fixtures/Attribute/UsersRepresentation.php
new file mode 100644
index 00000000..ad71c41b
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/UsersRepresentation.php
@@ -0,0 +1,22 @@
+inline = $inline;
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/Will.php b/tests/Hateoas/Tests/Fixtures/Attribute/Will.php
new file mode 100644
index 00000000..4539c275
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/Will.php
@@ -0,0 +1,41 @@
+ 'expr(object.getId())'],
+ ),
+)]
+#[Hateoas\Relation(
+ 'post',
+ href: "expr(link(object.getPost(), 'self', true))",
+)]
+class Will
+{
+ private $id;
+
+ private $post;
+
+ public function __construct($id, ?Post $post = null)
+ {
+ $this->id = $id;
+ $this->post = $post;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function getPost()
+ {
+ return $this->post;
+ }
+}
diff --git a/tests/Hateoas/Tests/Fixtures/Attribute/WithAlternativeRouter.php b/tests/Hateoas/Tests/Fixtures/Attribute/WithAlternativeRouter.php
new file mode 100644
index 00000000..e823c5d8
--- /dev/null
+++ b/tests/Hateoas/Tests/Fixtures/Attribute/WithAlternativeRouter.php
@@ -0,0 +1,19 @@
+ 'hello'],
+ generator: 'my_generator',
+ ),
+)]
+class WithAlternativeRouter
+{
+}
diff --git a/tests/Hateoas/Tests/Fixtures/UserPhpAttributes.php b/tests/Hateoas/Tests/Fixtures/UserPhpAttributes.php
deleted file mode 100644
index ea57bbd8..00000000
--- a/tests/Hateoas/Tests/Fixtures/UserPhpAttributes.php
+++ /dev/null
@@ -1,54 +0,0 @@
- 'application/json'])]
-#[Hateoas\Relation(
- name: 'foo',
- href: new Hateoas\Route(name: 'user_get', parameters: ['id' => 'expr(object.getId())']),
- embedded: 'expr(object.getFoo())'
-)]
-#[Hateoas\Relation(name: 'bar', href: 'foo', embedded: new Hateoas\Embedded(content: 'data', xmlElementName: 'barTag'))]
-#[Hateoas\Relation(
- name: 'baz',
- href: new Hateoas\Route(name: 'user_get', parameters: ['id' => 'expr(object.getId())'], absolute: true),
- embedded: 'expr(object.getFoo())'
-)]
-#[Hateoas\Relation(
- name: 'boom',
- href: new Hateoas\Route(name: 'user_get', parameters: ['id' => 'expr(object.getId())'], absolute: false),
- embedded: 'expr(object.getFoo())'
-)]
-#[Hateoas\Relation(name: 'badaboom', embedded: 'expr(object.getFoo())')]
-#[Hateoas\Relation(
- name: 'hello',
- href: '/hello',
- embedded: new Hateoas\Embedded(
- content: 'hello',
- type: 'string',
- xmlElementName: 'barTag',
- exclusion: new Hateoas\Exclusion(
- groups: ['group3', 'group4'],
- sinceVersion: '1.1',
- untilVersion: '2.3',
- maxDepth: 43,
- excludeIf: 'bar'
- )
- ),
- exclusion: new Hateoas\Exclusion(
- groups: ['group1', 'group2'],
- sinceVersion: '1',
- untilVersion: '2.2',
- maxDepth: 42,
- excludeIf: 'foo'
- )
-)]
-#[Hateoas\Relation(name: 'attribute_with_expression', href: 'baz', attributes: ['baz' => 'expr(object.getId())'])]
-#[Hateoas\RelationProvider(name: 'Hateoas\Tests\Fixtures\User::getRelations')]
-class UserPhpAttributes
-{
-}
diff --git a/tests/Hateoas/Tests/HateoasBuilderTest.php b/tests/Hateoas/Tests/HateoasBuilderTest.php
index 7bb1050a..70e7d926 100644
--- a/tests/Hateoas/Tests/HateoasBuilderTest.php
+++ b/tests/Hateoas/Tests/HateoasBuilderTest.php
@@ -4,8 +4,10 @@
namespace Hateoas\Tests;
+use Doctrine\Common\Annotations\AnnotationReader;
use Hateoas\HateoasBuilder;
use Hateoas\Tests\Fixtures\AdrienBrault;
+use Hateoas\Tests\Fixtures\Attribute;
use Hateoas\Tests\Fixtures\CircularReference1;
use Hateoas\Tests\Fixtures\CircularReference2;
use Hateoas\Tests\Fixtures\NoAnnotations;
@@ -27,12 +29,13 @@ public function testBuild()
$this->assertInstanceOf(SerializerInterface::class, $hateoas);
}
- public function testSerializeAdrienBraultWithExclusion()
+ /**
+ * @dataProvider getTestSerializeAdrienBraultWithExclusionData
+ */
+ public function testSerializeAdrienBraultWithExclusion($adrienBrault, $fakeAdrienBrault)
{
$hateoas = HateoasBuilder::buildHateoas();
- $adrienBrault = new AdrienBrault();
- $fakeAdrienBrault = new AdrienBrault();
$fakeAdrienBrault->firstName = 'John';
$fakeAdrienBrault->lastName = 'Smith';
@@ -68,6 +71,26 @@ public function testSerializeAdrienBraultWithExclusion()
);
}
+ private static function getTestSerializeAdrienBraultWithExclusionData(): iterable
+ {
+ yield [
+ new Attribute\AdrienBrault(),
+ new Attribute\AdrienBrault(),
+ ];
+
+ if (class_exists(AnnotationReader::class)) {
+ yield [
+ new AdrienBrault(),
+ new AdrienBrault(),
+ ];
+
+ yield [
+ new Attribute\AdrienBraultAttributesAndAnnotations(),
+ new Attribute\AdrienBraultAttributesAndAnnotations(),
+ ];
+ }
+ }
+
public function testAlternativeUrlGenerator()
{
$brokenUrlGenerator = new CallableUrlGenerator(function ($name, $parameters) {
@@ -78,6 +101,12 @@ public function testAlternativeUrlGenerator()
->setUrlGenerator('my_generator', $brokenUrlGenerator)
->build();
+ if (class_exists(AnnotationReader::class)) {
+ $withAlternativeRouter = new WithAlternativeRouter();
+ } else {
+ $withAlternativeRouter = new Attribute\WithAlternativeRouter();
+ }
+
$this->assertSame(
<<
@@ -87,7 +116,7 @@ public function testAlternativeUrlGenerator()
XML
,
- $hateoas->serialize(new WithAlternativeRouter(), 'xml')
+ $hateoas->serialize($withAlternativeRouter, 'xml')
);
}
@@ -95,8 +124,14 @@ public function testCyclicalReferences()
{
$hateoas = HateoasBuilder::create()->build();
- $reference1 = new CircularReference1();
- $reference2 = new CircularReference2();
+ if (class_exists(AnnotationReader::class)) {
+ $reference1 = new CircularReference1();
+ $reference2 = new CircularReference2();
+ } else {
+ $reference1 = new Attribute\CircularReference1();
+ $reference2 = new Attribute\CircularReference2();
+ }
+
$reference1->setReference2($reference2);
$reference2->setReference1($reference1);
@@ -134,7 +169,11 @@ public function testWithNullInEmbedded()
{
$hateoas = HateoasBuilder::create()->build();
- $reference1 = new CircularReference1();
+ if (class_exists(AnnotationReader::class)) {
+ $reference1 = new CircularReference1();
+ } else {
+ $reference1 = new Attribute\CircularReference1();
+ }
$this->assertSame(
<<expectException(\RuntimeException::class);
- $this->expectExceptionMessage('Can not find the relation "unknown-rel" for the "Hateoas\Tests\Fixtures\Will" class');
- $this->assertNull($this->hateoas->getLinkHelper()->getLinkHref(new Will(123), 'unknown-rel'));
- $this->assertNull($this->hateoas->getLinkHelper()->getLinkHref(new Will(123), 'unknown-rel', true));
+ $this->expectExceptionMessage(sprintf('Can not find the relation "unknown-rel" for the "%s" class', $className));
+ $this->assertNull($this->hateoas->getLinkHelper()->getLinkHref(new $className(123), 'unknown-rel'));
+ $this->assertNull($this->hateoas->getLinkHelper()->getLinkHref(new $className(123), 'unknown-rel', true));
}
public function testGetLinkHrefUrl()
{
- $this->assertEquals('/users/123', $this->hateoas->getLinkHelper()->getLinkHref(new Will(123), 'self'));
- $this->assertEquals('/users/123', $this->hateoas->getLinkHelper()->getLinkHref(new Will(123), 'self', false));
+ if (class_exists(AnnotationReader::class)) {
+ $className = Will::class;
+ } else {
+ $className = Attribute\Will::class;
+ }
+
+ $this->assertEquals('/users/123', $this->hateoas->getLinkHelper()->getLinkHref(new $className(123), 'self'));
+ $this->assertEquals('/users/123', $this->hateoas->getLinkHelper()->getLinkHref(new $className(123), 'self', false));
}
public function testGetLinkHrefUrlWithAbsoluteTrue()
{
- $this->assertEquals('http://example.com/users/123', $this->hateoas->getLinkHelper()->getLinkHref(new Will(123), 'self', true));
+ if (class_exists(AnnotationReader::class)) {
+ $className = Will::class;
+ } else {
+ $className = Attribute\Will::class;
+ }
+
+ $this->assertEquals('http://example.com/users/123', $this->hateoas->getLinkHelper()->getLinkHref(new $className(123), 'self', true));
}
}
diff --git a/tests/Hateoas/Tests/Representation/OffsetRepresentationTest.php b/tests/Hateoas/Tests/Representation/OffsetRepresentationTest.php
index 4d1d7cdb..a2bc486b 100644
--- a/tests/Hateoas/Tests/Representation/OffsetRepresentationTest.php
+++ b/tests/Hateoas/Tests/Representation/OffsetRepresentationTest.php
@@ -4,8 +4,10 @@
namespace Hateoas\Tests\Representation;
+use Doctrine\Common\Annotations\AnnotationReader;
use Hateoas\Representation\CollectionRepresentation;
use Hateoas\Representation\OffsetRepresentation;
+use Hateoas\Tests\Fixtures\Attribute;
use Hateoas\Tests\Fixtures\UsersRepresentation;
class OffsetRepresentationTest extends RepresentationTestCase
@@ -65,6 +67,13 @@ public function testSerialize()
,
$this->halHateoas->serialize($collection, 'xml')
);
+
+ if (class_exists(AnnotationReader::class)) {
+ $usersRepresentation = new UsersRepresentation($collection);
+ } else {
+ $usersRepresentation = new Attribute\UsersRepresentation($collection);
+ }
+
$this->assertSame(
<<
@@ -82,7 +91,7 @@ public function testSerialize()
XML
,
- $this->hateoas->serialize(new UsersRepresentation($collection), 'xml')
+ $this->hateoas->serialize($usersRepresentation, 'xml')
);
$this->assertSame(
@@ -99,7 +108,7 @@ public function testSerialize()
XML
,
- $this->halHateoas->serialize(new UsersRepresentation($collection), 'xml')
+ $this->halHateoas->serialize($usersRepresentation, 'xml')
);
$this->assertSame(
diff --git a/tests/Hateoas/Tests/Representation/PaginatedRepresentationTest.php b/tests/Hateoas/Tests/Representation/PaginatedRepresentationTest.php
index c5b40edf..f7d96c37 100644
--- a/tests/Hateoas/Tests/Representation/PaginatedRepresentationTest.php
+++ b/tests/Hateoas/Tests/Representation/PaginatedRepresentationTest.php
@@ -4,8 +4,10 @@
namespace Hateoas\Tests\Representation;
+use Doctrine\Common\Annotations\AnnotationReader;
use Hateoas\Representation\CollectionRepresentation;
use Hateoas\Representation\PaginatedRepresentation;
+use Hateoas\Tests\Fixtures\Attribute;
use Hateoas\Tests\Fixtures\UsersRepresentation;
class PaginatedRepresentationTest extends RepresentationTestCase
@@ -65,6 +67,13 @@ public function testSerialize()
,
$this->halHateoas->serialize($collection, 'xml')
);
+
+ if (class_exists(AnnotationReader::class)) {
+ $usersRepresentation = new UsersRepresentation($collection);
+ } else {
+ $usersRepresentation = new Attribute\UsersRepresentation($collection);
+ }
+
$this->assertSame(
<<
@@ -82,7 +91,7 @@ public function testSerialize()
XML
,
- $this->hateoas->serialize(new UsersRepresentation($collection), 'xml')
+ $this->hateoas->serialize($usersRepresentation, 'xml')
);
$this->assertSame(
<<halHateoas->serialize(new UsersRepresentation($collection), 'xml')
+ $this->halHateoas->serialize($usersRepresentation, 'xml')
);
$this->assertSame(
'{'
diff --git a/tests/Hateoas/Tests/Serializer/JsonHalSerializerTest.php b/tests/Hateoas/Tests/Serializer/JsonHalSerializerTest.php
index 09415679..f8e25784 100644
--- a/tests/Hateoas/Tests/Serializer/JsonHalSerializerTest.php
+++ b/tests/Hateoas/Tests/Serializer/JsonHalSerializerTest.php
@@ -4,6 +4,7 @@
namespace Hateoas\Tests\Serializer;
+use Doctrine\Common\Annotations\AnnotationReader;
use Hateoas\HateoasBuilder;
use Hateoas\Model\Embedded;
use Hateoas\Model\Link;
@@ -11,6 +12,7 @@
use Hateoas\Serializer\JsonHalSerializer;
use Hateoas\Serializer\Metadata\RelationPropertyMetadata;
use Hateoas\Tests\Fixtures\AdrienBrault;
+use Hateoas\Tests\Fixtures\Attribute;
use Hateoas\Tests\Fixtures\Foo1;
use Hateoas\Tests\Fixtures\Foo2;
use Hateoas\Tests\Fixtures\Foo3;
@@ -202,7 +204,11 @@ public function testSerializeCuriesWithMultipleEntriesShouldBeAnArray()
public function testSerializeAdrienBrault()
{
$hateoas = HateoasBuilder::buildHateoas();
- $adrienBrault = new AdrienBrault();
+ if (class_exists(AnnotationReader::class)) {
+ $adrienBrault = new AdrienBrault();
+ } else {
+ $adrienBrault = new Attribute\AdrienBrault();
+ }
$this->assertSame(
<<inline = $foo2;
$foo2->inline = $foo3;
@@ -284,7 +297,11 @@ public function testSerializeInlineJson()
public function testGh236()
{
- $data = new CollectionRepresentation([new Gh236Foo()]);
+ if (class_exists(AnnotationReader::class)) {
+ $data = new CollectionRepresentation([new Gh236Foo()]);
+ } else {
+ $data = new CollectionRepresentation([new Attribute\Gh236Foo()]);
+ }
$hateoas = HateoasBuilder::buildHateoas();
diff --git a/tests/Hateoas/Tests/Serializer/XmlHalSerializerTest.php b/tests/Hateoas/Tests/Serializer/XmlHalSerializerTest.php
index 7088133e..f7a4b74c 100644
--- a/tests/Hateoas/Tests/Serializer/XmlHalSerializerTest.php
+++ b/tests/Hateoas/Tests/Serializer/XmlHalSerializerTest.php
@@ -4,10 +4,12 @@
namespace Hateoas\Tests\Serializer;
+use Doctrine\Common\Annotations\AnnotationReader;
use Hateoas\HateoasBuilder;
use Hateoas\Representation\CollectionRepresentation;
use Hateoas\Serializer\XmlHalSerializer;
use Hateoas\Tests\Fixtures\AdrienBrault;
+use Hateoas\Tests\Fixtures\Attribute;
use Hateoas\Tests\Fixtures\Gh236Foo;
use Hateoas\Tests\Fixtures\LinkAttributes;
use Hateoas\Tests\TestCase;
@@ -20,7 +22,11 @@ public function testSerializeAdrienBrault()
$hateoas = HateoasBuilder::create()
->setXmlSerializer(new XmlHalSerializer())
->build();
- $adrienBrault = new AdrienBrault();
+ if (class_exists(AnnotationReader::class)) {
+ $adrienBrault = new AdrienBrault();
+ } else {
+ $adrienBrault = new Attribute\AdrienBrault();
+ }
$this->assertSame(
<<setXmlSerializer(new XmlHalSerializer())
diff --git a/tests/Hateoas/Tests/Serializer/XmlSerializerTest.php b/tests/Hateoas/Tests/Serializer/XmlSerializerTest.php
index 7d581df5..0752e886 100644
--- a/tests/Hateoas/Tests/Serializer/XmlSerializerTest.php
+++ b/tests/Hateoas/Tests/Serializer/XmlSerializerTest.php
@@ -4,6 +4,7 @@
namespace Hateoas\Tests\Serializer;
+use Doctrine\Common\Annotations\AnnotationReader;
use Hateoas\HateoasBuilder;
use Hateoas\Model\Embedded;
use Hateoas\Model\Link;
@@ -11,6 +12,7 @@
use Hateoas\Serializer\Metadata\RelationPropertyMetadata;
use Hateoas\Serializer\XmlSerializer;
use Hateoas\Tests\Fixtures\AdrienBrault;
+use Hateoas\Tests\Fixtures\Attribute;
use Hateoas\Tests\Fixtures\Gh236Foo;
use Hateoas\Tests\Fixtures\LinkAttributes;
use Hateoas\Tests\TestCase;
@@ -98,7 +100,11 @@ public function testSerializeEmbeddeds()
public function testSerializeAdrienBrault()
{
$hateoas = HateoasBuilder::buildHateoas();
- $adrienBrault = new AdrienBrault();
+ if (class_exists(AnnotationReader::class)) {
+ $adrienBrault = new AdrienBrault();
+ } else {
+ $adrienBrault = new Attribute\AdrienBrault();
+ }
$this->assertSame(
<< new \Hateoas\Tests\Fixtures\Attribute\Will(123, new \Hateoas\Tests\Fixtures\Attribute\Post(345)),
+)
+--EXPECT--
+/user_get/123
+/user_get/123
+http://example.com/user_get/123
+http://example.com/post_get/345
+http://example.com/post_get/345
+http://example.com/post_get/345