core icon indicating copy to clipboard operation
core copied to clipboard

Doctrine ODM Documents return inconsistent data for GET Collection and Item Operations in Symfony production environment

Open nasAtchia opened this issue 1 year ago • 1 comments

API Platform version(s) affected: 3.3.5

Description

The GET Collection and Item operations do not always return the latest data for document resources after updating a resource via the PATCH or PUT operations; sometimes the old data is returned.

Important: This happens only in the production environment docker compose -f compose.yaml -f compose.prod.yaml up --build. I don't know if this is related to the production setup.

How to reproduce

<?php

declare(strict_types=1);

namespace App\Document;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\ProductRepository;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Types\Type;

#[ApiResource]
#[ODM\Document(collection: 'products', repositoryClass: ProductRepository::class)]
class Product
{
    #[ODM\Id(type: Type::INT, strategy: 'INCREMENT')]
    private ?int $id = null;

    #[ODM\Field(type: Type::STRING, nullable: true)]
    private ?string $name = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(?string $name): static
    {
        $this->name = $name;

        return $this;
    }
}

  1. Create a new Product via the POST API.
  2. Update the Product data via the PATCH or PUT API.
  3. Hit the GET Collection of Products or the GET Single Product APIs multiple times, and you'll see that sometimes the latest data is not returned.

Use this repo for testing the above.

Possible Solution

I had to create custom state provider for the GET item operation and call the refresh method after retrieving the object from the built-in state provider.

Custom state provider:

<?php

declare(strict_types=1);

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Document\Product;
use App\Repository\ProductRepository;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

final readonly class ProductProvider implements ProviderInterface
{
    public function __construct(
        #[Autowire(service: 'api_platform.doctrine_mongodb.odm.state.item_provider')]
        private ProviderInterface $itemProvider,
        private ProductRepository $productRepository
    ) {
    }

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Product
    {
        /** @var Product|null $product */
        $product = $this->itemProvider->provide($operation, $uriVariables, $context);

        if (!$product) {
            return null;
        }

        $this->productRepository->getDocumentManager()->refresh($product);

        return $product;
    }
}

Updated document:

<?php

declare(strict_types=1);

namespace App\Document;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Repository\ProductRepository;
use App\State\ProductProvider;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Types\Type;

#[ApiResource(
    operations: [
        new GetCollection(),
        new Post(),
        new Get(provider: ProductProvider::class),
        new Patch(),
        new Put(),
        new Delete(),
    ],
)]
#[ODM\Document(collection: 'products', repositoryClass: ProductRepository::class)]
class Product
{
    #[ODM\Id(type: Type::INT, strategy: 'INCREMENT')]
    private ?int $id = null;

    #[ODM\Field(type: Type::STRING, nullable: true)]
    private ?string $name = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(?string $name): static
    {
        $this->name = $name;

        return $this;
    }
}

I haven't found a solution for the Get Collection operation yet.

Additional Context

Recording: https://www.awesomescreenshot.com/video/28637492?key=483e51522dd7bd03d915244bf090e6ff

nasAtchia avatar Jun 14 '24 10:06 nasAtchia

would you be able to patch this please?

soyuka avatar Jul 19 '24 06:07 soyuka

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Sep 17 '24 19:09 stale[bot]