Skip to content

Commit

Permalink
wip: map data model
Browse files Browse the repository at this point in the history
  • Loading branch information
alanpoulain committed May 7, 2021
1 parent 2e0e9f5 commit c43187e
Show file tree
Hide file tree
Showing 43 changed files with 2,266 additions and 157 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -607,16 +607,19 @@ jobs:
run: vendor/bin/behat --out=std --format=progress --profile=elasticsearch --no-interaction

eloquent:
name: Behat (PHP ${{ matrix.php }}) (Eloquent)
name: Behat (PHP ${{ matrix.php }}) (${{ matrix.app_env }})
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
php:
- '7.4'
app_env:
- 'eloquent'
- 'eloquent_mapped'
fail-fast: false
env:
APP_ENV: eloquent
APP_ENV: ${{ matrix.app_env }}
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion features/main/relation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ Feature: Relations support
"pattern": "^An error occurred$"
},
"hydra:description": {
"pattern": "^Expected IRI or document for resource \"ApiPlatform\\\\Core\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity|Models)\\\\RelatedDummy\", \"integer\" given.$"
"pattern": "^Expected IRI or document for resource \"ApiPlatform\\\\Core\\\\Tests\\\\Fixtures\\\\TestBundle\\\\(Document|Entity|Models|Resource)\\\\RelatedDummy\", \"integer\" given.$"
}
},
"required": [
Expand Down
7 changes: 5 additions & 2 deletions src/Annotation/ApiProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
* @Attribute("push", type="bool"),
* @Attribute("security", type="string"),
* @Attribute("securityPostDenormalize", type="string"),
* @Attribute("swaggerContext", type="array")
* @Attribute("swaggerContext", type="array"),
* @Attribute("virtual", type="bool")
* )
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)]
Expand Down Expand Up @@ -114,6 +115,7 @@ final class ApiProperty
* @param string $security
* @param array $swaggerContext
* @param string $securityPostDenormalize
* @param bool $virtual
*
* @throws InvalidArgumentException
*/
Expand All @@ -139,7 +141,8 @@ public function __construct(
?bool $push = null,
?string $security = null,
?array $swaggerContext = null,
?string $securityPostDenormalize = null
?string $securityPostDenormalize = null,
?bool $virtual = null
) {
if (!\is_array($description)) { // @phpstan-ignore-line Doctrine annotations support
[$publicProperties, $configurableAttributes] = self::getConfigMetadata();
Expand Down
6 changes: 3 additions & 3 deletions src/Annotation/ApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* @Attribute("cacheHeaders", type="array"),
* @Attribute("collectionOperations", type="array"),
* @Attribute("compositeIdentifier", type="bool"),
* @Attribute("dataModel", type="string"),
* @Attribute("denormalizationContext", type="array"),
* @Attribute("deprecationReason", type="string"),
* @Attribute("description", type="string"),
Expand Down Expand Up @@ -58,7 +59,6 @@
* @Attribute("paginationMaximumItemsPerPage", type="int"),
* @Attribute("paginationPartial", type="bool"),
* @Attribute("paginationViaCursor", type="array"),
* @Attribute("properties", type="array"),
* @Attribute("routePrefix", type="string"),
* @Attribute("security", type="string"),
* @Attribute("securityMessage", type="string"),
Expand Down Expand Up @@ -139,6 +139,7 @@ final class ApiResource
* @param array $itemOperations https://api-platform.com/docs/core/operations
* @param array $subresourceOperations https://api-platform.com/docs/core/subresources
* @param array $cacheHeaders https://api-platform.com/docs/core/performance/#setting-custom-http-cache-headers
* @param string $dataModel
* @param array $denormalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups
* @param string $deprecationReason https://api-platform.com/docs/core/deprecations/#deprecating-resource-classes-operations-and-properties
* @param bool $elasticsearch https://api-platform.com/docs/core/elasticsearch/
Expand All @@ -163,7 +164,6 @@ final class ApiResource
* @param int $paginationItemsPerPage https://api-platform.com/docs/core/pagination/#changing-the-number-of-items-per-page
* @param int $paginationMaximumItemsPerPage https://api-platform.com/docs/core/pagination/#changing-maximum-items-per-page
* @param bool $paginationPartial https://api-platform.com/docs/core/performance/#partial-pagination
* @param array $properties
* @param string $routePrefix https://api-platform.com/docs/core/operations/#prefixing-all-routes-of-all-operations
* @param string $security https://api-platform.com/docs/core/security
* @param string $securityMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message
Expand Down Expand Up @@ -191,6 +191,7 @@ public function __construct(
// attributes
?array $attributes = null,
?array $cacheHeaders = null,
?string $dataModel = null,
?array $denormalizationContext = null,
?string $deprecationReason = null,
?bool $elasticsearch = null,
Expand All @@ -215,7 +216,6 @@ public function __construct(
?int $paginationItemsPerPage = null,
?int $paginationMaximumItemsPerPage = null,
?bool $paginationPartial = null,
?array $properties = null,
?string $routePrefix = null,
?string $security = null,
?string $securityMessage = null,
Expand Down
24 changes: 17 additions & 7 deletions src/Bridge/Eloquent/DataPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

/**
* Data persister for Eloquent.
Expand All @@ -43,7 +44,7 @@ public function __construct(DatabaseManager $databaseManager)
*/
public function supports($data, array $context = []): bool
{
return \is_object($data) ? is_subclass_of($this->getObjectClass($data), Model::class, true) : false;
return \is_object($data) && is_subclass_of($this->getObjectClass($data), Model::class, true);
}

/**
Expand All @@ -67,7 +68,7 @@ public function persist($data, array $context = [])
*
* @param Model $data
*/
public function remove($data, array $context = [])
public function remove($data, array $context = []): void
{
$data->delete();
}
Expand All @@ -77,6 +78,19 @@ private function save(Model $data): void
$data->save();

foreach ($data->getRelations() as $relationName => $relation) {
if (null === $relation) {
continue;
}

$related = $data->{$relationName}();

if (is_a($related, HasOne::class)) {
$related->save($relation);
}
if (is_iterable($relation) && is_a($related, HasMany::class)) {
$related->saveMany($relation);
}

if (is_iterable($relation)) {
foreach ($relation as $relationItem) {
$this->save($relationItem);
Expand All @@ -85,13 +99,9 @@ private function save(Model $data): void
$this->save($relation);
}

$related = $data->{$relationName}();
if (is_a($related, BelongsTo::class)) {
$related->associate($relation);
}
if (is_iterable($relation) && is_a($related, HasOneOrMany::class)) {
$related->saveMany($relation);
}
}

$data->push();
Expand Down
17 changes: 2 additions & 15 deletions src/Bridge/Eloquent/PropertyInfo/EloquentExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

namespace ApiPlatform\Core\Bridge\Eloquent\PropertyInfo;

use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
Expand All @@ -30,13 +28,6 @@
*/
final class EloquentExtractor implements PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
{
private $resourceMetadataFactory;

public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory)
{
$this->resourceMetadataFactory = $resourceMetadataFactory;
}

/**
* {@inheritdoc}
*/
Expand All @@ -46,13 +37,9 @@ public function getTypes($class, $property, array $context = []): ?array
return null;
}

try {
$resourceMetadata = $this->resourceMetadataFactory->create($class);
} catch (ResourceClassNotFoundException $e) {
return null;
}
$properties = (new \ReflectionClass($class))->getDefaultProperties()['apiProperties'] ?? null;

if (null === $properties = $resourceMetadata->getAttribute('properties')) {
if (null === $properties) {
return null;
}

Expand Down
38 changes: 38 additions & 0 deletions src/Bridge/Eloquent/Serializer/EloquentNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?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\Model;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

/**
* Normalizer for Eloquent model (when the model is not a resource and a resource is mapped to the model).
*
* @author Alan Poulain <contact@alanpoulain.eu>
*/
final class EloquentNormalizer extends ObjectNormalizer
{
public function supportsNormalization($data, string $format = null, array $context = []): bool
{
return false;
}

/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return is_subclass_of($type, Model::class, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

/**
* Add resource metadata loader to Serializer chain loader.
Expand All @@ -37,7 +36,7 @@ public function process(ContainerBuilder $container)

$serializerLoaders = $chainLoader->getArgument(0);

$definition = new Definition(ResourceMetadataLoader::class, [new Reference('api_platform.metadata.resource.metadata_factory')]);
$definition = new Definition(ResourceMetadataLoader::class);
$definition->setPublic(false);
$serializerLoaders[] = $definition;

Expand Down
18 changes: 18 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,24 @@
<tag name="serializer.normalizer" priority="-895" />
</service>

<service id="api_platform.serializer.normalizer.mapped_data_model" class="ApiPlatform\Core\Serializer\MappedDataModelNormalizer" parent="serializer.normalizer.object" public="false">
<argument type="service" id="api_platform.resource_class_resolver" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<!-- Run before api_platform.serializer.normalizer.item -->
<tag name="serializer.normalizer" priority="-894" />
</service>

<service id="api_platform.serializer.normalizer.data_model" class="ApiPlatform\Core\Serializer\DataModelNormalizer" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.identifiers_extractor" />
<argument type="service" id="api_platform.item_data_provider" />
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
<argument type="service" id="api_platform.property_accessor" />
<!-- Run before serializer.normalizer.object -->
<tag name="serializer.normalizer" priority="-997" />
</service>

<!-- Resources Operations path resolver -->

<service id="api_platform.operation_path_resolver" alias="api_platform.operation_path_resolver.router" public="false" />
Expand Down
10 changes: 10 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
<argument type="tagged" tag="api_platform.data_persister" />
</service>
<service id="ApiPlatform\Core\DataPersister\DataPersisterInterface" alias="api_platform.data_persister" />

<service id="api_platform.mapped_data_model_data_persister" class="ApiPlatform\Core\DataPersister\MappedDataModelDataPersister" public="false">
<argument type="service" id="api_platform.data_persister" />
<argument type="service" id="api_platform.item_data_provider" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.serializer" />
<argument type="service" id="api_platform.identifiers_extractor" />
<argument type="service" id="api_platform.property_accessor" />
<tag name="api_platform.data_persister" priority="-1000" />
</service>
</services>

</container>
14 changes: 14 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,25 @@
</service>
<service id="ApiPlatform\Core\DataProvider\ItemDataProviderInterface" alias="api_platform.item_data_provider" />

<service id="api_platform.mapped_data_model_item_data_provider" class="ApiPlatform\Core\DataProvider\MappedDataModelItemDataProvider" public="false">
<argument type="service" id="api_platform.item_data_provider" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.serializer" />
<tag name="api_platform.item_data_provider" />
</service>

<service id="api_platform.collection_data_provider" class="ApiPlatform\Core\DataProvider\ChainCollectionDataProvider">
<argument type="tagged" tag="api_platform.collection_data_provider" />
</service>
<service id="ApiPlatform\Core\DataProvider\CollectionDataProviderInterface" alias="api_platform.collection_data_provider" />

<service id="api_platform.mapped_data_model_collection_data_provider" class="ApiPlatform\Core\DataProvider\MappedDataModelCollectionDataProvider" public="false">
<argument type="service" id="api_platform.collection_data_provider" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.serializer" />
<tag name="api_platform.collection_data_provider" />
</service>

<service id="api_platform.subresource_data_provider" class="ApiPlatform\Core\DataProvider\ChainSubresourceDataProvider">
<argument type="tagged" tag="api_platform.subresource_data_provider" />
</service>
Expand Down
20 changes: 14 additions & 6 deletions src/Bridge/Symfony/Bundle/Resources/config/eloquent.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="api_platform.eloquent.builder_factory"
class="ApiPlatform\Core\Bridge\Eloquent\BuilderFactory">
<service id="api_platform.eloquent.builder_factory" class="ApiPlatform\Core\Bridge\Eloquent\BuilderFactory" public="false">
</service>

<service id="api_platform.eloquent.data_persister" class="ApiPlatform\Core\Bridge\Eloquent\DataPersister" public="false">
Expand All @@ -25,28 +24,37 @@
<service id="api_platform.eloquent.default.collection_data_provider"
parent="api_platform.eloquent.collection_data_provider"
class="ApiPlatform\Core\Bridge\Eloquent\CollectionDataProvider">
<tag name="api_platform.collection_data_provider"/>
<tag name="api_platform.collection_data_provider" />
</service>

<service id="api_platform.eloquent.default.item_data_provider"
parent="api_platform.eloquent.item_data_provider"
class="ApiPlatform\Core\Bridge\Eloquent\ItemDataProvider">
<tag name="api_platform.item_data_provider"/>
<tag name="api_platform.item_data_provider" />
</service>

<service id="api_platform.eloquent.property_accessor"
class="ApiPlatform\Core\Bridge\Eloquent\PropertyAccess\EloquentPropertyAccessor"
decorates="api_platform.property_accessor"
public="false"
>
<argument type="service" id="api_platform.eloquent.property_accessor.inner" />
</service>

<service id="api_platform.eloquent.property_info_extractor" class="ApiPlatform\Core\Bridge\Eloquent\PropertyInfo\EloquentExtractor">
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<service id="api_platform.eloquent.property_info_extractor" class="ApiPlatform\Core\Bridge\Eloquent\PropertyInfo\EloquentExtractor" public="false">
<tag name="property_info.type_extractor" priority="-999" />
<tag name="property_info.access_extractor" priority="-999" />
</service>

<!--<service id="api_platform.serializer.normalizer.eloquent" class="ApiPlatform\Core\Bridge\Eloquent\Serializer\EloquentNormalizer" public="false">
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
<argument type="service" id="api_platform.eloquent.property_accessor" />-->

<!-- Run before serializer.normalizer.object -->
<!--<tag name="serializer.normalizer" priority="-999" />
</service>-->

<!-- Metadata loader -->

<service id="api_platform.eloquent.metadata.property.name_collection_factory"
Expand Down
Loading

0 comments on commit c43187e

Please sign in to comment.