Skip to content

Commit

Permalink
feat: add state providers for Doctrine ORM
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelbigard committed Sep 27, 2021
1 parent f0b4b7f commit 6f75408
Show file tree
Hide file tree
Showing 18 changed files with 1,579 additions and 6 deletions.
66 changes: 66 additions & 0 deletions features/main/crud.feature
Expand Up @@ -583,3 +583,69 @@ Feature: Create-Retrieve-Update-Delete
"foo": "bar"
}
"""

@php8
Scenario: Create a resource ProviderEntity
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/provider_entities" with body:
"""
{
"foo": "bar"
}
"""
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/provider_entities/1"
And the header "Location" should be equal to "/provider_entities/1"
And the JSON should be equal to:
"""
{
"@context": "/contexts/ProviderEntity",
"@id": "/provider_entities/1",
"@type": "ProviderEntity",
"id": 1,
"foo": "bar"
}
"""

@php8
Scenario: Get a collection of Provider Entities
When I send a "GET" request to "/provider_entities"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/ProviderEntity",
"@id": "/provider_entities",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/provider_entities/1",
"@type": "ProviderEntity",
"id": 1,
"foo": "bar"
}
],
"hydra:totalItems": 1
}
"""

@php8
Scenario: Get a resource ProviderEntity
When I send a "GET" request to "/provider_entities/1"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/ProviderEntity",
"@id": "/provider_entities/1",
"@type": "ProviderEntity",
"id": 1,
"foo": "bar"
}
"""
86 changes: 86 additions & 0 deletions src/Bridge/Doctrine/Odm/State/CollectionProvider.php
@@ -0,0 +1,86 @@
<?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\Bridge\Doctrine\Odm\State;

use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface;
use ApiPlatform\Exception\OperationNotFoundException;
use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProviderInterface;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
* Collection state provider using the Doctrine ODM.
*/
final class CollectionProvider implements ProviderInterface
{
private $resourceMetadataCollectionFactory;
private $managerRegistry;
private $collectionExtensions;

/**
* @param AggregationCollectionExtensionInterface[] $collectionExtensions
*/
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, iterable $collectionExtensions = [])
{
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
$this->managerRegistry = $managerRegistry;
$this->collectionExtensions = $collectionExtensions;
}

public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = [])
{
/** @var DocumentManager $manager */
$manager = $this->managerRegistry->getManagerForClass($resourceClass);

$repository = $manager->getRepository($resourceClass);
if (!$repository instanceof DocumentRepository) {
throw new RuntimeException(sprintf('The repository for "%s" must be an instance of "%s".', $resourceClass, DocumentRepository::class));
}

$aggregationBuilder = $repository->createAggregationBuilder();
foreach ($this->collectionExtensions as $extension) {
$extension->applyToCollection($aggregationBuilder, $resourceClass, $operationName, $context);

if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context);
}
}

$resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass);
try {
$operation = $context['operation'] ?? (isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName));
$attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? [];
} catch (OperationNotFoundException $e) {
$attribute = $resourceMetadata->getOperation(null, true)->getExtraProperties()['doctrine_mongodb'] ?? [];
}
$executeOptions = $attribute['execute_options'] ?? [];

return $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions);
}

public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool
{
if (!$this->managerRegistry->getManagerForClass($resourceClass) instanceof DocumentManager) {
return false;
}

$operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName);

return $operation->isCollection() ?? false;
}
}
98 changes: 98 additions & 0 deletions src/Bridge/Doctrine/Odm/State/ItemProvider.php
@@ -0,0 +1,98 @@
<?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\Bridge\Doctrine\Odm\State;

use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface;
use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
use ApiPlatform\State\ProviderInterface;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
* Item state provider using the Doctrine ODM.
*
* @author Kévin Dunglas <kevin@dunglas.fr>
* @author Samuel ROZE <samuel.roze@gmail.com>
*/
final class ItemProvider implements ProviderInterface
{
private $resourceMetadataCollectionFactory;
private $managerRegistry;
private $itemExtensions;

/**
* @param AggregationItemExtensionInterface[] $itemExtensions
*/
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, iterable $itemExtensions = [])
{
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
$this->managerRegistry = $managerRegistry;
$this->itemExtensions = $itemExtensions;
}

public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = [])
{
/** @var DocumentManager $manager */
$manager = $this->managerRegistry->getManagerForClass($resourceClass);

$fetchData = $context['fetch_data'] ?? true;
if (!$fetchData) {
return $manager->getReference($resourceClass, $identifiers);
}

$repository = $manager->getRepository($resourceClass);
if (!$repository instanceof DocumentRepository) {
throw new RuntimeException(sprintf('The repository for "%s" must be an instance of "%s".', $resourceClass, DocumentRepository::class));
}

$aggregationBuilder = $repository->createAggregationBuilder();

foreach ($identifiers as $propertyName => $value) {
$aggregationBuilder->match()->field($propertyName)->equals($value);
}

foreach ($this->itemExtensions as $extension) {
$extension->applyToItem($aggregationBuilder, $resourceClass, $identifiers, $operationName, $context);

if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context);
}
}

$resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass);

if ($resourceMetadata instanceof ResourceMetadataCollection) {
$attribute = $resourceMetadata->getOperation()->getExtraProperties()['doctrine_mongodb'] ?? [];
}

$executeOptions = $attribute['execute_options'] ?? [];

return $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions)->current() ?: null;
}

public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool
{
if (!$this->managerRegistry->getManagerForClass($resourceClass) instanceof DocumentManager) {
return false;
}

$operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName);

return !($operation->isCollection() ?? false);
}
}
80 changes: 80 additions & 0 deletions src/Bridge/Doctrine/Orm/State/CollectionProvider.php
@@ -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\Bridge\Doctrine\Orm\State;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProviderInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;

/**
* Collection state provider using the Doctrine ORM.
*
* @author Kévin Dunglas <kevin@dunglas.fr>
* @author Samuel ROZE <samuel.roze@gmail.com>
*/
final class CollectionProvider implements ProviderInterface
{
private $resourceMetadataCollectionFactory;
private $managerRegistry;
private $collectionExtensions;

/**
* @param QueryCollectionExtensionInterface[] $collectionExtensions
*/
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, iterable $collectionExtensions = [])
{
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
$this->managerRegistry = $managerRegistry;
$this->collectionExtensions = $collectionExtensions;
}

public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = [])
{
/** @var EntityManagerInterface $manager */
$manager = $this->managerRegistry->getManagerForClass($resourceClass);

$repository = $manager->getRepository($resourceClass);
if (!method_exists($repository, 'createQueryBuilder')) {
throw new RuntimeException('The repository class must have a "createQueryBuilder" method.');
}

$queryBuilder = $repository->createQueryBuilder('o');
$queryNameGenerator = new QueryNameGenerator();
foreach ($this->collectionExtensions as $extension) {
$extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);

if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) {
return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context);
}
}

return $queryBuilder->getQuery()->getResult();
}

public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool
{
if (!$this->managerRegistry->getManagerForClass($resourceClass) instanceof EntityManagerInterface) {
return false;
}

$operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName);

return $operation->isCollection() ?? false;
}
}

0 comments on commit 6f75408

Please sign in to comment.