Skip to content

Commit

Permalink
feat: manage identifier and hydra collection
Browse files Browse the repository at this point in the history
  • Loading branch information
alanpoulain committed Jun 4, 2021
1 parent 364cce5 commit 54f0574
Show file tree
Hide file tree
Showing 15 changed files with 463 additions and 11 deletions.
1 change: 1 addition & 0 deletions features/main/circular_reference.feature
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@!eloquent
Feature: Circular references handling
In order to handle circular references
As a developer
Expand Down
1 change: 1 addition & 0 deletions features/main/composite.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@!mongodb
@!eloquent
Feature: Retrieve data with Composite identifiers
In order to retrieve relations with composite identifiers
As a client software developer
Expand Down
1 change: 1 addition & 0 deletions features/main/custom_identifier.feature
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@eloquent
Feature: Using custom identifier on resource
In order to use an hypermedia API
As a client software developer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,17 @@ public function create(string $resourceClass, array $options = []): PropertyName
throw new ResourceClassNotFoundException(sprintf('The resource class "%s" is not an Eloquent model.', $resourceClass));
}

/** @var Model $model */
$model = new $resourceClass();

$propertyNames[$model->getKeyName()] = $model->getKeyName();

if (null !== $propertyNameCollection) {
foreach ($propertyNameCollection as $propertyName) {
$propertyNames[$propertyName] = $propertyName;
}
}

/** @var Model $model */
$model = new $resourceClass();

$propertyNames[$model->getKeyName()] = $model->getKeyName();

return new PropertyNameCollection(array_values($propertyNames));
}
}
60 changes: 60 additions & 0 deletions src/Bridge/Eloquent/Serializer/CollectionNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Core\Bridge\Eloquent\Serializer;

use Illuminate\Database\Eloquent\Collection;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
* Collection normalizer for non-mapped data model Eloquent resources.
* It exists to be called before the JsonSerializableNormalizer.
*
* @experimental
*
* @author Alan Poulain <contact@alanpoulain.eu>
*/
final class CollectionNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface
{
private $collectionNormalizer;

public function __construct(NormalizerInterface $collectionNormalizer)
{
$this->collectionNormalizer = $collectionNormalizer;
}

/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof Collection && $this->collectionNormalizer->supportsNormalization($data, $format);
}

/**
* {@inheritdoc}
*/
public function hasCacheableSupportsMethod(): bool
{
return $this->collectionNormalizer instanceof CacheableSupportsMethodInterface && $this->collectionNormalizer->hasCacheableSupportsMethod();
}

/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = [])
{
return $this->collectionNormalizer->normalize($object, $format, $context);
}
}
7 changes: 7 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/eloquent.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@
decorates="api_platform.metadata.property.metadata_factory" decoration-priority="40" public="false">
<argument type="service" id="api_platform.eloquent.metadata.property.metadata_factory.inner" />
</service>

<service id="api_platform.eloquent.normalizer.collection.hydra"
class="ApiPlatform\Core\Bridge\Eloquent\Serializer\CollectionNormalizer">
<argument type="service" id="api_platform.hydra.normalizer.collection" />
<!-- Run before serializer.normalizer.json_serializable -->
<tag name="serializer.normalizer" priority="-885" />
</service>
</services>

</container>
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
<argument type="service" id="api_platform.resource_class_resolver" />
</service>

<service id="api_platform.metadata.property.metadata_factory.class_property" class="ApiPlatform\Core\Metadata\Property\Factory\ClassPropertyPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="20" public="false">
<argument type="service" id="api_platform.metadata.property.metadata_factory.class_property.inner" />
</service>

<service id="api_platform.metadata.property.metadata_factory.cached" class="ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="-10" public="false">
<argument type="service" id="api_platform.cache.metadata.property" />
<argument type="service" id="api_platform.metadata.property.metadata_factory.cached.inner" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Core\Metadata\Property\Factory;

use ApiPlatform\Core\Exception\PropertyNotFoundException;
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;

/**
* Creates a property metadata from apiProperties property.
*
* @author Alan Poulain <contact@alanpoulain.eu>
*/
final class ClassPropertyPropertyMetadataFactory implements PropertyMetadataFactoryInterface
{
private $decorated;

public function __construct(PropertyMetadataFactoryInterface $decorated)
{
$this->decorated = $decorated;
}

/**
* {@inheritdoc}
*/
public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
{
$parentPropertyMetadata = null;
if ($this->decorated) {
try {
$parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options);
} catch (PropertyNotFoundException $propertyNotFoundException) {
// Ignore not found exception from decorated factories
}
}

$properties = (new \ReflectionClass($resourceClass))->getDefaultProperties()['apiProperties'] ?? null;

if (null === ($properties[$property] ?? null) || !\is_array($properties[$property])) {
return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
}

$propertyMetadata = $properties[$property];

$metadata = $parentPropertyMetadata;
if (null === $metadata) {
$metadata = new PropertyMetadata();
}

if (isset($propertyMetadata['identifier'])) {
$metadata = $metadata->withIdentifier((bool) $propertyMetadata['identifier']);
}

return $metadata;
}

/**
* Returns the metadata from the decorated factory if available or throws an exception.
*
* @throws PropertyNotFoundException
*/
private function handleNotFound(?PropertyMetadata $parentPropertyMetadata, string $resourceClass, string $property): PropertyMetadata
{
if ($parentPropertyMetadata) {
return $parentPropertyMetadata;
}

throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass));
}
}
25 changes: 19 additions & 6 deletions tests/Behat/DoctrineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\User;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\UuidIdentifierDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\WithJsonDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Models\CustomMultipleIdentifierDummy as CustomMultipleIdentifierDummyModel;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Models\Foo as FooModel;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Models\PatchDummyRelation as PatchDummyRelationModel;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Models\RelatedDummy as RelatedDummyModel;
Expand Down Expand Up @@ -1899,12 +1900,20 @@ public function thereIsABook()
public function thereIsACustomMultipleIdentifierDummy()
{
$dummy = $this->buildCustomMultipleIdentifierDummy();
$dummy->setName('Orwell');
$dummy->setFirstId(1);
$dummy->setSecondId(2);
if ($this->isDoctrine()) {
$dummy->setName('Orwell');
$dummy->setFirstId(1);
$dummy->setSecondId(2);

$this->manager->persist($dummy);
$this->manager->flush();
$this->manager->persist($dummy);
$this->manager->flush();
}
if ($dummy instanceof CustomMultipleIdentifierDummyModel) {
$dummy->name = 'Orwell';
$dummy->firstId = 1;
$dummy->secondId = 2;
$dummy->save();
}
}

private function isOrm(): bool
Expand Down Expand Up @@ -2412,10 +2421,14 @@ private function buildBook()
}

/**
* @return CustomMultipleIdentifierDummy | CustomMultipleIdentifierDummyDocument
* @return CustomMultipleIdentifierDummy|CustomMultipleIdentifierDummyDocument|CustomMultipleIdentifierDummyModel
*/
private function buildCustomMultipleIdentifierDummy()
{
if ($this->isEloquent()) {
return new CustomMultipleIdentifierDummyModel();
}

return $this->isOrm() ? new CustomMultipleIdentifierDummy() : new CustomMultipleIdentifierDummyDocument();
}

Expand Down
35 changes: 35 additions & 0 deletions tests/Fixtures/TestBundle/Models/CustomIdentifierDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Core\Tests\Fixtures\TestBundle\Models;

use ApiPlatform\Core\Annotation\ApiResource;
use Illuminate\Database\Eloquent\Model;

/**
* Custom Identifier Dummy.
*
* @ApiResource
*/
class CustomIdentifierDummy extends Model
{
public $timestamps = false;

public static $snakeAttributes = false;

protected $primaryKey = 'customId';

protected $apiProperties = [
'name',
];
}
40 changes: 40 additions & 0 deletions tests/Fixtures/TestBundle/Models/CustomMultipleIdentifierDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Core\Tests\Fixtures\TestBundle\Models;

use ApiPlatform\Core\Annotation\ApiResource;
use Illuminate\Database\Eloquent\Model;

/**
* Custom Identifier Dummy.
*
* @ApiResource(compositeIdentifier=false)
*/
class CustomMultipleIdentifierDummy extends Model
{
public $timestamps = false;

public static $snakeAttributes = false;

protected $primaryKey = 'firstId';

protected $casts = [
'secondId' => 'integer',
];

protected $apiProperties = [
'secondId' => ['identifier' => true],
'name',
];
}
Loading

0 comments on commit 54f0574

Please sign in to comment.