Skip to content

Commit

Permalink
feat(laravel): laravel component (#5882)
Browse files Browse the repository at this point in the history
* feat(laravel): laravel component

* try to skip laravel

* feat(jsonapi): component

* feat(laravel): json api support (needs review)

* work on relations

* relations (needs toMany) + skolem + IRI to resource

* links handler

* ulid

* validation

* slug post

* remove deprecations

* move classes

* fix tests

* fix tests metadata

* phpstan

* missing class

* fix laravel tests

* fix stan
  • Loading branch information
soyuka authored Jun 25, 2024
1 parent 77c57fd commit f201581
Show file tree
Hide file tree
Showing 26 changed files with 2,287 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.gitignore export-ignore
/Tests export-ignore
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/composer.lock
/vendor
/.phpunit.result.cache
3 changes: 1 addition & 2 deletions JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace ApiPlatform\JsonApi\JsonSchema;

use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
use ApiPlatform\JsonSchema\DefinitionNameFactoryInterface;
use ApiPlatform\JsonSchema\ResourceMetadataTrait;
use ApiPlatform\JsonSchema\Schema;
Expand Down Expand Up @@ -106,7 +105,7 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
],
];

public function __construct(private readonly SchemaFactoryInterface $schemaFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, private readonly ?DefinitionNameFactoryInterface $definitionNameFactory = null)
public function __construct(private readonly SchemaFactoryInterface $schemaFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, private readonly ?DefinitionNameFactoryInterface $definitionNameFactory = null)
{
if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
$this->schemaFactory->setSchemaFactory($this);
Expand Down
5 changes: 2 additions & 3 deletions Serializer/CollectionNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@

namespace ApiPlatform\JsonApi\Serializer;

use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
use ApiPlatform\JsonApi\Util\IriHelper;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\ResourceClassResolverInterface;
use ApiPlatform\Serializer\AbstractCollectionNormalizer;
use ApiPlatform\Util\IriHelper;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;

/**
Expand All @@ -31,7 +30,7 @@ final class CollectionNormalizer extends AbstractCollectionNormalizer
{
public const FORMAT = 'jsonapi';

public function __construct(ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, string $pageParameterName, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory)
public function __construct(ResourceClassResolverInterface $resourceClassResolver, string $pageParameterName, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory)
{
parent::__construct($resourceClassResolver, $pageParameterName, $resourceMetadataFactory);
}
Expand Down
13 changes: 5 additions & 8 deletions Serializer/EntrypointNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@

namespace ApiPlatform\JsonApi\Serializer;

use ApiPlatform\Api\Entrypoint;
use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface;
use ApiPlatform\Api\UrlGeneratorInterface as LegacyUrlGeneratorInterface;
use ApiPlatform\Documentation\Entrypoint as DocumentationEntrypoint;
use ApiPlatform\Exception\InvalidArgumentException;
use ApiPlatform\Documentation\Entrypoint;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
Expand All @@ -37,7 +34,7 @@ final class EntrypointNormalizer implements NormalizerInterface, CacheableSuppor
{
public const FORMAT = 'jsonapi';

public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly IriConverterInterface|LegacyIriConverterInterface $iriConverter, private readonly UrlGeneratorInterface|LegacyUrlGeneratorInterface $urlGenerator)
public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly IriConverterInterface $iriConverter, private readonly UrlGeneratorInterface $urlGenerator)
{
}

Expand Down Expand Up @@ -75,12 +72,12 @@ public function normalize(mixed $object, ?string $format = null, array $context
*/
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return self::FORMAT === $format && ($data instanceof Entrypoint || $data instanceof DocumentationEntrypoint);
return self::FORMAT === $format && $data instanceof Entrypoint;
}

public function getSupportedTypes($format): array
{
return self::FORMAT === $format ? [Entrypoint::class => true, DocumentationEntrypoint::class => true] : [];
return self::FORMAT === $format ? [Entrypoint::class => true] : [];
}

public function hasCacheableSupportsMethod(): bool
Expand Down
1 change: 0 additions & 1 deletion Serializer/ErrorNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace ApiPlatform\JsonApi\Serializer;

use ApiPlatform\Problem\Serializer\ErrorNormalizerTrait;
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface as LegacyConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
Expand Down
57 changes: 57 additions & 0 deletions Serializer/ErrorNormalizerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\JsonApi\Serializer;

use ApiPlatform\Metadata\Exception\ErrorCodeSerializableInterface;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Response;

/**
* @internal
*/
trait ErrorNormalizerTrait
{
private function getErrorMessage($object, array $context, bool $debug = false): string
{
$message = $object->getMessage();

if ($debug) {
return $message;
}

if ($object instanceof FlattenException) {
$statusCode = $context['statusCode'] ?? $object->getStatusCode();
if ($statusCode >= 500 && $statusCode < 600) {
$message = Response::$statusTexts[$statusCode] ?? Response::$statusTexts[Response::HTTP_INTERNAL_SERVER_ERROR];
}
}

return $message;
}

private function getErrorCode(object $object): ?string
{
if ($object instanceof FlattenException) {
$exceptionClass = $object->getClass();
} else {
$exceptionClass = $object::class;
}

if (is_a($exceptionClass, ErrorCodeSerializableInterface::class, true)) {
return $exceptionClass::getErrorCode();
}

return null;
}
}
8 changes: 3 additions & 5 deletions Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,20 @@

namespace ApiPlatform\JsonApi\Serializer;

use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface;
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
use ApiPlatform\Exception\ItemNotFoundException;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
use ApiPlatform\Metadata\ResourceClassResolverInterface;
use ApiPlatform\Metadata\UrlGeneratorInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Serializer\AbstractItemNormalizer;
use ApiPlatform\Serializer\CacheKeyTrait;
use ApiPlatform\Serializer\ContextTrait;
use ApiPlatform\Serializer\TagCollectorInterface;
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Serializer\Exception\LogicException;
Expand Down Expand Up @@ -56,7 +54,7 @@ final class ItemNormalizer extends AbstractItemNormalizer

private array $componentsCache = [];

public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface|LegacyIriConverterInterface $iriConverter, ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, ?PropertyAccessorInterface $propertyAccessor = null, ?NameConverterInterface $nameConverter = null, ?ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ?PropertyAccessorInterface $propertyAccessor = null, ?NameConverterInterface $nameConverter = null, ?ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
{
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker, $tagCollector);
}
Expand Down
4 changes: 1 addition & 3 deletions Serializer/ObjectNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

namespace ApiPlatform\JsonApi\Serializer;

use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface;
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\ResourceClassResolverInterface;
Expand All @@ -34,7 +32,7 @@ final class ObjectNormalizer implements NormalizerInterface, CacheableSupportsMe

public const FORMAT = 'jsonapi';

public function __construct(private readonly NormalizerInterface $decorated, private readonly IriConverterInterface|LegacyIriConverterInterface $iriConverter, private readonly ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory)
public function __construct(private readonly NormalizerInterface $decorated, private readonly IriConverterInterface $iriConverter, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory)
{
}

Expand Down
20 changes: 20 additions & 0 deletions Tests/Fixtures/CircularReference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\JsonApi\Tests\Fixtures;

class CircularReference
{
public $id;
public CircularReference $parent;
}
42 changes: 42 additions & 0 deletions Tests/Fixtures/CustomConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\JsonApi\Tests\Fixtures;

use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;

/**
* Custom converter that will only convert a property named "nameConverted"
* with the same logic as Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter.
*/
class CustomConverter implements AdvancedNameConverterInterface
{
private NameConverterInterface $nameConverter;

public function __construct()
{
$this->nameConverter = new CamelCaseToSnakeCaseNameConverter();
}

public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
{
return 'nameConverted' === $propertyName ? $this->nameConverter->normalize($propertyName) : $propertyName;
}

public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
{
return 'name_converted' === $propertyName ? $this->nameConverter->denormalize($propertyName) : $propertyName;
}
}
Loading

0 comments on commit f201581

Please sign in to comment.