API Platform version(s) affected: 2.6.8
Description
I have and entity named Route contains a self-reference relation parent/children, Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity exception throws when i call deleteRoute mutation;
- When the entity has children it throws the exception after being deleted
- When the entity has no child, it works.
How to reproduce
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ExistsFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use App\Entity\Classes\ApiPlatform;
use App\Entity\Traits\HasId;
use App\Entity\Traits\HasLocale;
use App\Entity\Traits\HasParentChildren;
use App\Entity\Traits\HasSortIndex;
use App\Entity\Traits\Tree\HasMaterializedPathTree;
use App\Filter\SimpleSearchFilter;
use App\Service\Validator;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\Translatable\Entity\Translation;
use Gedmo\Tree\Entity\Repository\MaterializedPathRepository;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: MaterializedPathRepository::class)]
#[Gedmo\Tree(type: 'materializedPath')]
#[Gedmo\TranslationEntity(class: Translation::class)]
#[ApiFilter(SimpleSearchFilter::class, properties: ['title', 'routeKey'])]
#[ApiFilter(OrderFilter::class, properties: ['title', 'sortIndex', 'treeLevel'])]
#[ApiFilter(ExistsFilter::class, properties: ['parent'])]
#[ApiFilter(SearchFilter::class, properties: [
'views' => 'partial',
'parent' => 'exact',
'roles.roleKey' => 'exact',
])]
#[ApiResource(attributes: ApiPlatform::DEFAULT_ATTRIBUTES)]
class Route
{
use HasId;
use HasSortIndex;
use HasMaterializedPathTree;
use HasLocale;
use HasParentChildren;
#[Assert\NotBlank(message: Validator::NOT_BLANK)]
#[ORM\Column(unique: true)]
private string $routeKey;
#[Assert\NotBlank(message: Validator::NOT_BLANK)]
#[ORM\Column]
#[Gedmo\Translatable]
private string $title;
#[ORM\Column(nullable: true)]
private ?string $icon;
#[ORM\Column(type: 'array')]
private array $views = [];
#[ORM\JoinColumn(onDelete: 'SET NULL')]
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
#[Gedmo\TreeParent]
#[Gedmo\SortableGroup]
private ?self $parent;
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
#[ORM\OrderBy(['sortIndex' => 'ASC'])]
private Collection $children;
#[ORM\ManyToMany(targetEntity: Role::class, mappedBy: 'routes')]
private Collection $roles;
public function __construct()
{
$this->roles = new ArrayCollection;
$this->children = new ArrayCollection;
}
#[Serializer\SerializedName('hasParams')]
public function hasParams(): bool
{
return str_contains($this->getTreePath(), ':');
}
public function getRouteKey(): ?string
{
return $this->routeKey;
}
public function setRouteKey(string $routeKey): self
{
$this->routeKey = $routeKey;
return $this;
}
public function getRoles(): Collection
{
return $this->roles;
}
public function addRole(Role $role): self
{
if (!$this->roles->contains($role)) {
$this->roles[] = $role;
$role->addRoute($this);
}
return $this;
}
public function removeRole(Role $role): self
{
if ($this->roles->removeElement($role)) {
$role->removeRoute($this);
}
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getIcon(): ?string
{
return $this->icon;
}
public function setIcon(?string $icon): self
{
$this->icon = $icon;
return $this;
}
public function getViews(): ?array
{
return $this->views;
}
public function setViews(array $views): self
{
$this->views = $views;
return $this;
}
}
Possible Solution
Additional Context
Errors
[
{
"debugMessage": "Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.",
"message": "Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.",
"extensions": {
"category": "internal"
},
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"deleteRoute"
],
"trace": [
{
"file": "/var/www/AE-API/vendor/gedmo/doctrine-extensions/src/Mapping/Event/Adapter/ORM.php",
"line": 130,
"call": "Doctrine\\ORM\\UnitOfWork::recomputeSingleEntityChangeSet(instance of Doctrine\\ORM\\Mapping\\ClassMetadata, instance of Proxies\\__CG__\\App\\Entity\\Route)"
},
{
"file": "/var/www/AE-API/vendor/gedmo/doctrine-extensions/src/Mapping/MappedEventSubscriber.php",
"line": 258,
"call": "Gedmo\\Mapping\\Event\\Adapter\\ORM::recomputeSingleObjectChangeSet(instance of Doctrine\\ORM\\UnitOfWork, instance of Doctrine\\ORM\\Mapping\\ClassMetadata, instance of Proxies\\__CG__\\App\\Entity\\Route)"
},
{
"file": "/var/www/AE-API/vendor/gedmo/doctrine-extensions/src/Sortable/SortableListener.php",
"line": 266,
"call": "Gedmo\\Mapping\\MappedEventSubscriber::setFieldValue(instance of Gedmo\\Sortable\\Mapping\\Event\\Adapter\\ORM, instance of Proxies\\__CG__\\App\\Entity\\Route, 'sortIndex', 0, -1)"
},
{
"file": "/var/www/AE-API/vendor/symfony/doctrine-bridge/ContainerAwareEventManager.php",
"line": 68,
"call": "Gedmo\\Sortable\\SortableListener::postFlush(instance of Doctrine\\ORM\\Event\\PostFlushEventArgs)"
},
{
"file": "/var/www/AE-API/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php",
"line": 3479,
"call": "Symfony\\Bridge\\Doctrine\\ContainerAwareEventManager::dispatchEvent('postFlush', instance of Doctrine\\ORM\\Event\\PostFlushEventArgs)"
},
{
"file": "/var/www/AE-API/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php",
"line": 477,
"call": "Doctrine\\ORM\\UnitOfWork::dispatchPostFlushEvent()"
},
{
"file": "/var/www/AE-API/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php",
"line": 392,
"call": "Doctrine\\ORM\\UnitOfWork::commit(null)"
},
{
"file": "/var/www/AE-API/var/cache/dev/ContainerJFvMkSc/proxy-classes.php",
"line": 137,
"call": "Doctrine\\ORM\\EntityManager::flush(null)"
},
{
"file": "/var/www/AE-API/vendor/api-platform/core/src/Bridge/Doctrine/Common/DataPersister.php",
"line": 76,
"call": "ContainerJFvMkSc\\EntityManager_9a5be93::flush()"
},
{
"file": "/var/www/AE-API/vendor/api-platform/core/src/DataPersister/ChainDataPersister.php",
"line": 78,
"call": "ApiPlatform\\Core\\Bridge\\Doctrine\\Common\\DataPersister::remove(instance of App\\Entity\\Route, array(4))"
},
{
"file": "/var/www/AE-API/vendor/api-platform/core/src/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php",
"line": 68,
"call": "ApiPlatform\\Core\\DataPersister\\ChainDataPersister::remove(instance of App\\Entity\\Route, array(4))"
},
{
"file": "/var/www/AE-API/vendor/api-platform/core/src/GraphQl/Resolver/Stage/WriteStage.php",
"line": 54,
"call": "ApiPlatform\\Core\\Bridge\\Symfony\\Bundle\\DataPersister\\TraceableChainDataPersister::remove(instance of App\\Entity\\Route, array(4))"
},
{
"file": "/var/www/AE-API/vendor/api-platform/core/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php",
"line": 93,
"call": "ApiPlatform\\Core\\GraphQl\\Resolver\\Stage\\WriteStage::__invoke(instance of App\\Entity\\Route, 'App\\Entity\\Route', 'delete', array(6))"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 623,
"call": "ApiPlatform\\Core\\GraphQl\\Resolver\\Factory\\ItemMutationResolverFactory::ApiPlatform\\Core\\GraphQl\\Resolver\\Factory\\{closure}(null, array(1), null, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 550,
"call": "GraphQL\\Executor\\ReferenceExecutor::resolveFieldValueOrError(instance of GraphQL\\Type\\Definition\\FieldDefinition, instance of GraphQL\\Language\\AST\\FieldNode, instance of Closure, null, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 474,
"call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: Mutation, null, instance of ArrayObject(1), array(1))"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 857,
"call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'deleteRoute')"
},
{
"call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'deleteRoute')"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 859,
"function": "array_reduce(array(1), instance of Closure, array(0))"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 490,
"call": "GraphQL\\Executor\\ReferenceExecutor::promiseReduce(array(1), instance of Closure, array(0))"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 263,
"call": "GraphQL\\Executor\\ReferenceExecutor::executeFieldsSerially(GraphQLType: Mutation, null, array(0), instance of ArrayObject(1))"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
"line": 215,
"call": "GraphQL\\Executor\\ReferenceExecutor::executeOperation(instance of GraphQL\\Language\\AST\\OperationDefinitionNode, null)"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/Executor/Executor.php",
"line": 156,
"call": "GraphQL\\Executor\\ReferenceExecutor::doExecute()"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/GraphQL.php",
"line": 162,
"call": "GraphQL\\Executor\\Executor::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, null, array(1), 'DeleteRoute', null)"
},
{
"file": "/var/www/AE-API/vendor/webonyx/graphql-php/src/GraphQL.php",
"line": 94,
"call": "GraphQL\\GraphQL::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, 'mutation DeleteRoute($id: ID!) {\n deleteRoute(input: {id: $id}) {\n route {\n id\n __typename\n }\n __typename\n }\n}', null, null, array(1), 'DeleteRoute', null, null)"
},
{
"file": "/var/www/AE-API/vendor/api-platform/core/src/GraphQl/Executor.php",
"line": 34,
"call": "GraphQL\\GraphQL::executeQuery(instance of GraphQL\\Type\\Schema, 'mutation DeleteRoute($id: ID!) {\n deleteRoute(input: {id: $id}) {\n route {\n id\n __typename\n }\n __typename\n }\n}', null, null, array(1), 'DeleteRoute', null, null)"
},
{
"file": "/var/www/AE-API/vendor/api-platform/core/src/GraphQl/Action/EntrypointAction.php",
"line": 86,
"call": "ApiPlatform\\Core\\GraphQl\\Executor::executeQuery(instance of GraphQL\\Type\\Schema, 'mutation DeleteRoute($id: ID!) {\n deleteRoute(input: {id: $id}) {\n route {\n id\n __typename\n }\n __typename\n }\n}', null, null, array(1), 'DeleteRoute')"
},
{
"file": "/var/www/AE-API/vendor/symfony/http-kernel/HttpKernel.php",
"line": 152,
"call": "ApiPlatform\\Core\\GraphQl\\Action\\EntrypointAction::__invoke(instance of Symfony\\Component\\HttpFoundation\\Request)"
},
{
"file": "/var/www/AE-API/vendor/symfony/http-kernel/HttpKernel.php",
"line": 74,
"call": "Symfony\\Component\\HttpKernel\\HttpKernel::handleRaw(instance of Symfony\\Component\\HttpFoundation\\Request, 1)"
},
{
"file": "/var/www/AE-API/vendor/symfony/http-kernel/Kernel.php",
"line": 202,
"call": "Symfony\\Component\\HttpKernel\\HttpKernel::handle(instance of Symfony\\Component\\HttpFoundation\\Request, 1, true)"
},
{
"file": "/var/www/AE-API/public/index.php",
"line": 25,
"call": "Symfony\\Component\\HttpKernel\\Kernel::handle(instance of Symfony\\Component\\HttpFoundation\\Request)"
}
]
}
]