diff --git a/psalm.xml b/psalm.xml
index 2defa899f..2f64f6e27 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -46,11 +46,20 @@
+
+
+
+
+
+
+
+
+
@@ -107,6 +116,13 @@
+
+
+
+
+
+
+
@@ -170,6 +186,13 @@
+
+
+
+
+
+
+
@@ -311,6 +334,13 @@
+
+
+
+
+
+
+
diff --git a/src/Bundle/DependencyInjection/Configuration.php b/src/Bundle/DependencyInjection/Configuration.php
index 6de235407..7da7477d5 100644
--- a/src/Bundle/DependencyInjection/Configuration.php
+++ b/src/Bundle/DependencyInjection/Configuration.php
@@ -39,6 +39,9 @@ public function getConfigTreeBuilder(): TreeBuilder
->arrayNode('mapping')
->addDefaultsIfNotSet()
->children()
+ ->arrayNode('imports')
+ ->prototype('scalar')->end()
+ ->end()
->arrayNode('paths')
->prototype('scalar')->end()
->end()
diff --git a/src/Bundle/Resources/config/services/integrations/doctrine/orm.xml b/src/Bundle/Resources/config/services/integrations/doctrine/orm.xml
index 0ca399cfe..5f19df807 100644
--- a/src/Bundle/Resources/config/services/integrations/doctrine/orm.xml
+++ b/src/Bundle/Resources/config/services/integrations/doctrine/orm.xml
@@ -20,6 +20,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Bundle/Resources/config/services/metadata.xml b/src/Bundle/Resources/config/services/metadata.xml
index 18fea2a9b..55b54b4bd 100644
--- a/src/Bundle/Resources/config/services/metadata.xml
+++ b/src/Bundle/Resources/config/services/metadata.xml
@@ -12,35 +12,61 @@
-->
+
+
+
+
-
+
+
+
+ %sylius.resource.mapping%
+
+
+
+
+
+
+
+
-
-
-
+
- %sylius.state_machine_component.default%
-
+
+
+
+
+
+ %sylius.state_machine_component.default%
%sylius.resource.settings%
@@ -91,8 +117,8 @@
diff --git a/src/Bundle/Resources/config/services/metadata/resource_name.xml b/src/Bundle/Resources/config/services/metadata/resource_name.xml
new file mode 100644
index 000000000..d528bb2dd
--- /dev/null
+++ b/src/Bundle/Resources/config/services/metadata/resource_name.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+ %sylius.resource.mapping%
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Bundle/Resources/config/services/routing.xml b/src/Bundle/Resources/config/services/routing.xml
index 567598b92..7a7c17dd5 100644
--- a/src/Bundle/Resources/config/services/routing.xml
+++ b/src/Bundle/Resources/config/services/routing.xml
@@ -12,6 +12,10 @@
-->
+
+
+
+
@@ -102,7 +106,7 @@
-
+
diff --git a/src/Bundle/Resources/config/services/routing/loader.xml b/src/Bundle/Resources/config/services/routing/loader.xml
new file mode 100644
index 000000000..12b09fdbd
--- /dev/null
+++ b/src/Bundle/Resources/config/services/routing/loader.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Bundle/Resources/config/services/routing/resource.xml b/src/Bundle/Resources/config/services/routing/resource.xml
new file mode 100644
index 000000000..ac8fa84d8
--- /dev/null
+++ b/src/Bundle/Resources/config/services/routing/resource.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Bundle/Resources/config/services/state.xml b/src/Bundle/Resources/config/services/state.xml
index a6af7a29b..597454bc2 100644
--- a/src/Bundle/Resources/config/services/state.xml
+++ b/src/Bundle/Resources/config/services/state.xml
@@ -37,13 +37,6 @@
-
-
-
-
-
-
-
@@ -71,7 +64,7 @@
-
+
diff --git a/src/Component/composer.json b/src/Component/composer.json
index 4cc4d95f5..3375a24de 100644
--- a/src/Component/composer.json
+++ b/src/Component/composer.json
@@ -34,6 +34,7 @@
"pagerfanta/core": "^3.7 || ^4.0",
"symfony/event-dispatcher": "^6.4 || ^7.1",
"symfony/form": "^6.4 || ^7.1",
+ "symfony/framework-bundle": "^6.4 || ^7.1",
"symfony/http-foundation": "^6.4 || ^7.1",
"symfony/http-kernel": "^6.4 || ^7.1",
"symfony/property-access": "^6.4 || ^7.1",
diff --git a/src/Component/spec/Symfony/Request/State/ProviderSpec.php b/src/Component/spec/Symfony/Request/State/ProviderSpec.php
deleted file mode 100644
index 05d3fbb89..000000000
--- a/src/Component/spec/Symfony/Request/State/ProviderSpec.php
+++ /dev/null
@@ -1,175 +0,0 @@
-beConstructedWith($locator, new RepositoryArgumentResolver(), $argumentParser);
- }
-
- function it_is_initializable(): void
- {
- $this->shouldHaveType(Provider::class);
- }
-
- function it_calls_repository_as_callable(
- Operation $operation,
- Request $request,
- ): void {
- $operation->getRepository()->willReturn([RepositoryWithCallables::class, 'find']);
- $operation->getRepositoryArguments()->willReturn(null);
-
- $request->attributes = new ParameterBag(['_route_params' => ['id' => 'my_id']]);
- $request->query = new InputBag([]);
- $request->request = new InputBag();
-
- $response = $this->provide($operation, new Context(new RequestOption($request->getWrappedObject())));
- $response->shouldHaveType(\stdClass::class);
- $response->id->shouldReturn('my_id');
- }
-
- function it_calls_repository_as_string(
- Operation $operation,
- Request $request,
- ContainerInterface $locator,
- RepositoryInterface $repository,
- \stdClass $stdClass,
- ): void {
- $operation->getRepository()->willReturn('App\Repository');
- $operation->getRepositoryMethod()->willReturn(null);
- $operation->getRepositoryArguments()->willReturn(null);
-
- $request->attributes = new ParameterBag(['_route_params' => ['id' => 'my_id', '_sylius' => ['resource' => 'app.dummy']]]);
- $request->query = new InputBag([]);
- $request->request = new InputBag();
-
- $locator->has('App\Repository')->willReturn(true);
- $locator->get('App\Repository')->willReturn($repository);
-
- $repository->findOneBy(['id' => 'my_id'])->willReturn($stdClass);
-
- $response = $this->provide($operation, new Context(new RequestOption($request->getWrappedObject())));
- $response->shouldReturn($stdClass);
- }
-
- function it_calls_create_paginator_by_default_on_collection_operations(
- Request $request,
- ContainerInterface $locator,
- RepositoryInterface $repository,
- Pagerfanta $pagerfanta,
- ): void {
- $operation = new Index(repository: 'App\Repository');
-
- $request->attributes = new ParameterBag(['_route_params' => ['id' => 'my_id', '_sylius' => ['resource' => 'app.dummy']]]);
- $request->query = new InputBag([]);
- $request->request = new InputBag();
-
- $locator->has('App\Repository')->willReturn(true);
- $locator->get('App\Repository')->willReturn($repository);
-
- $repository->createPaginator()->willReturn($pagerfanta)->shouldBeCalled();
- $pagerfanta->setCurrentPage(1)->willReturn($pagerfanta)->shouldBeCalled();
-
- $response = $this->provide($operation, new Context(new RequestOption($request->getWrappedObject())));
- $response->shouldReturn($pagerfanta);
- }
-
- function it_sets_current_page_from_request_when_data_is_a_paginator(
- Request $request,
- ContainerInterface $locator,
- RepositoryInterface $repository,
- Pagerfanta $pagerfanta,
- ): void {
- $operation = new Index(repository: 'App\Repository');
-
- $request->attributes = new ParameterBag(['_route_params' => ['id' => 'my_id', '_sylius' => ['resource' => 'app.dummy']]]);
- $request->query = new InputBag(['page' => 42]);
- $request->request = new InputBag();
-
- $locator->has('App\Repository')->willReturn(true);
- $locator->get('App\Repository')->willReturn($repository);
-
- $repository->createPaginator()->willReturn($pagerfanta)->shouldBeCalled();
- $pagerfanta->setCurrentPage(42)->willReturn($pagerfanta)->shouldBeCalled();
-
- $response = $this->provide($operation, new Context(new RequestOption($request->getWrappedObject())));
- $response->shouldReturn($pagerfanta);
- $pagerfanta->getCurrentPage()->willReturn(42);
- }
-
- function it_calls_repository_as_string_with_specific_repository_method(
- Operation $operation,
- Request $request,
- ContainerInterface $locator,
- RepositoryInterface $repository,
- \stdClass $stdClass,
- ): void {
- $operation->getRepository()->willReturn('App\Repository');
- $operation->getRepositoryMethod()->willReturn('find');
- $operation->getRepositoryArguments()->willReturn(null);
-
- $request->attributes = new ParameterBag(['_route_params' => ['id' => 'my_id', '_sylius' => ['resource' => 'app.dummy']]]);
- $request->query = new InputBag([]);
- $request->request = new InputBag();
-
- $locator->has('App\Repository')->willReturn(true);
- $locator->get('App\Repository')->willReturn($repository);
-
- $repository->find('my_id')->willReturn($stdClass);
-
- $response = $this->provide($operation, new Context(new RequestOption($request->getWrappedObject())));
- $response->shouldReturn($stdClass);
- }
-
- function it_calls_repository_as_string_with_specific_repository_method_an_arguments(
- Operation $operation,
- Request $request,
- ContainerInterface $locator,
- RepositoryInterface $repository,
- ArgumentParserInterface $argumentParser,
- \stdClass $stdClass,
- ): void {
- $operation->getRepository()->willReturn('App\Repository');
- $operation->getRepositoryMethod()->willReturn('find');
- $operation->getRepositoryArguments()->willReturn(['id' => "request.attributes.get('id')"]);
-
- $argumentParser->parseExpression("request.attributes.get('id')")->willReturn('my_id');
-
- $locator->has('App\Repository')->willReturn(true);
- $locator->get('App\Repository')->willReturn($repository);
-
- $repository->find('my_id')->willReturn($stdClass);
-
- $response = $this->provide($operation, new Context(new RequestOption($request->getWrappedObject())));
- $response->shouldReturn($stdClass);
- }
-}
diff --git a/src/Component/spec/Symfony/Routing/Factory/AttributesOperationRouteFactorySpec.php b/src/Component/spec/Symfony/Routing/Factory/AttributesOperationRouteFactorySpec.php
index a55bc8421..2983df629 100644
--- a/src/Component/spec/Symfony/Routing/Factory/AttributesOperationRouteFactorySpec.php
+++ b/src/Component/spec/Symfony/Routing/Factory/AttributesOperationRouteFactorySpec.php
@@ -42,7 +42,6 @@ function let(
new AttributesResourceMetadataCollectionFactory(
$resourceRegistry->getWrappedObject(),
new OperationRouteNameFactory(),
- 'symfony',
),
);
}
diff --git a/src/Component/spec/Symfony/Routing/Factory/OperationRouteFactorySpec.php b/src/Component/spec/Symfony/Routing/Factory/OperationRouteFactorySpec.php
index edc46a93f..4c2c09640 100644
--- a/src/Component/spec/Symfony/Routing/Factory/OperationRouteFactorySpec.php
+++ b/src/Component/spec/Symfony/Routing/Factory/OperationRouteFactorySpec.php
@@ -289,4 +289,22 @@ function it_generates_routes_with_vars(
],
]);
}
+
+ function it_generates_routes_with_requirements(
+ OperationRoutePathFactoryInterface $routePathFactory,
+ ): void {
+ $operation = new Index(routeRequirements: ['type' => 'country|province|zone']);
+
+ $metadata = Metadata::fromAliasAndConfiguration('app.dummy', ['driver' => 'dummy_driver']);
+
+ $routePathFactory->createRoutePath($operation, 'dummies')->willReturn('/dummies')->shouldBeCalled();
+
+ $route = $this->create(
+ $metadata,
+ new ResourceMetadata(alias: 'app.dummy'),
+ $operation,
+ );
+
+ $route->getRequirements()->shouldReturn(['type' => 'country|province|zone']);
+ }
}
diff --git a/src/Component/spec/Symfony/Session/Flash/FlashHelperSpec.php b/src/Component/spec/Symfony/Session/Flash/FlashHelperSpec.php
index bd37c37fb..8ef6be2d1 100644
--- a/src/Component/spec/Symfony/Session/Flash/FlashHelperSpec.php
+++ b/src/Component/spec/Symfony/Session/Flash/FlashHelperSpec.php
@@ -91,6 +91,31 @@ function it_adds_success_flashes_with_fallback_message(
$this->addSuccessFlash($operation, $context);
}
+ function it_adds_success_flashes_with_custom_message(
+ Request $request,
+ SessionInterface $session,
+ FlashBagInterface $flashBag,
+ TranslatorBagInterface $translator,
+ MessageCatalogueInterface $messageCatalogue,
+ ): void {
+ $operation = (new Create(notificationMessage: 'app.dummy.shipped'))->withResource(new ResourceMetadata(alias: 'app.dummy', name: 'dummy', applicationName: 'app'));
+ $context = new Context(new RequestOption($request->getWrappedObject()));
+
+ $request->getSession()->willReturn($session);
+
+ $session->getBag('flashes')->willReturn($flashBag);
+
+ $translator->getCatalogue()->willReturn($messageCatalogue);
+
+ $messageCatalogue->has('app.dummy.shipped', 'flashes')->willReturn(true)->shouldBeCalled();
+
+ $translator->trans('app.dummy.shipped', ['%resource%' => 'Dummy'], 'flashes')->willReturn('Dummy was shipped successfully.')->shouldBeCalled();
+
+ $flashBag->add('success', 'Dummy was shipped successfully.')->shouldBeCalled();
+
+ $this->addSuccessFlash($operation, $context);
+ }
+
function it_adds_success_flashes_with_default_message_when_translator_is_not_a_bag(
Request $request,
SessionInterface $session,
diff --git a/src/Component/src/Doctrine/Common/Metadata/Resource/Factory/DoctrineResourceMetadataCollectionFactory.php b/src/Component/src/Doctrine/Common/Metadata/Resource/Factory/DoctrineResourceMetadataCollectionFactory.php
index b6141ac35..f4ae8daac 100644
--- a/src/Component/src/Doctrine/Common/Metadata/Resource/Factory/DoctrineResourceMetadataCollectionFactory.php
+++ b/src/Component/src/Doctrine/Common/Metadata/Resource/Factory/DoctrineResourceMetadataCollectionFactory.php
@@ -15,6 +15,7 @@
use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
use Sylius\Resource\Doctrine\Common\State\RemoveProcessor;
+use Sylius\Resource\Doctrine\ORM\Metadata\Resource\Factory\DoctrineORMResourceMetadataCollectionFactory;
use Sylius\Resource\Metadata\DeleteOperationInterface;
use Sylius\Resource\Metadata\Operation;
use Sylius\Resource\Metadata\Operations;
@@ -29,6 +30,13 @@ public function __construct(
private RegistryInterface $resourceRegistry,
private ResourceMetadataCollectionFactoryInterface $decorated,
) {
+ trigger_deprecation(
+ 'sylius/resource',
+ '1.13',
+ 'The "%s" class is deprecated, use "%s instead. It will be removed in 2.0.',
+ self::class,
+ DoctrineORMResourceMetadataCollectionFactory::class,
+ );
}
public function create(string $resourceClass): ResourceMetadataCollection
diff --git a/src/Component/src/Doctrine/ORM/Metadata/Resource/Factory/DoctrineORMResourceMetadataCollectionFactory.php b/src/Component/src/Doctrine/ORM/Metadata/Resource/Factory/DoctrineORMResourceMetadataCollectionFactory.php
new file mode 100644
index 000000000..93293d7a3
--- /dev/null
+++ b/src/Component/src/Doctrine/ORM/Metadata/Resource/Factory/DoctrineORMResourceMetadataCollectionFactory.php
@@ -0,0 +1,103 @@
+decorated->create($resourceClass);
+
+ /** @var ResourceMetadata $resource */
+ foreach ($resourceCollectionMetadata->getIterator() as $i => $resource) {
+ $operations = $resource->getOperations() ?? new Operations();
+ $entityClass = $resource->getClass();
+
+ if (null === $entityClass) {
+ continue;
+ }
+
+ /** @var Operation $operation */
+ foreach ($operations as $operation) {
+ /** @var string $key */
+ $key = $operation->getName();
+ $entityManager = $this->managerRegistry->getManagerForClass($entityClass);
+
+ if (!$entityManager instanceof EntityManagerInterface) {
+ $operations->add($key, $operation);
+
+ continue;
+ }
+
+ $operations->add($key, $this->addDefaults($operation));
+ }
+
+ $resource = $resource->withOperations($operations);
+ $resourceCollectionMetadata[$i] = $resource;
+ }
+
+ return $resourceCollectionMetadata;
+ }
+
+ private function addDefaults(Operation $operation): Operation
+ {
+ $operation = $operation->withProvider($this->getProvider($operation));
+
+ return $operation->withProcessor($this->getProcessor($operation));
+ }
+
+ private function getProvider(Operation $operation): callable|string|null
+ {
+ if (null !== $provider = $operation->getProvider()) {
+ return $provider;
+ }
+
+ if ($operation instanceof GridAwareOperationInterface && null !== $operation->getGrid()) {
+ return null;
+ }
+
+ return 'sylius.state_provider.doctrine.orm.state.provider';
+ }
+
+ private function getProcessor(Operation $operation): callable|string
+ {
+ if (null !== $processor = $operation->getProcessor()) {
+ return $processor;
+ }
+
+ if ($operation instanceof DeleteOperationInterface) {
+ return RemoveProcessor::class;
+ }
+
+ return PersistProcessor::class;
+ }
+}
diff --git a/src/Component/src/Symfony/Request/State/Provider.php b/src/Component/src/Doctrine/ORM/State/Provider.php
similarity index 52%
rename from src/Component/src/Symfony/Request/State/Provider.php
rename to src/Component/src/Doctrine/ORM/State/Provider.php
index cea4eda91..cac55efa3 100644
--- a/src/Component/src/Symfony/Request/State/Provider.php
+++ b/src/Component/src/Doctrine/ORM/State/Provider.php
@@ -11,9 +11,11 @@
declare(strict_types=1);
-namespace Sylius\Resource\Symfony\Request\State;
+namespace Sylius\Resource\Doctrine\ORM\State;
-use Pagerfanta\Pagerfanta;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\Persistence\ManagerRegistry;
+use Pagerfanta\PagerfantaInterface;
use Psr\Container\ContainerInterface;
use Sylius\Resource\Context\Context;
use Sylius\Resource\Context\Option\RequestOption;
@@ -31,49 +33,33 @@
final class Provider implements ProviderInterface
{
public function __construct(
- private ContainerInterface $locator,
+ private ManagerRegistry $managerRegistry,
private RepositoryArgumentResolver $argumentResolver,
private ArgumentParserInterface $argumentParser,
+ private ContainerInterface $locator,
) {
}
public function provide(Operation $operation, Context $context): object|array|null
{
$request = $context->get(RequestOption::class)?->request();
- $repository = $operation->getRepository();
- if (
- null === $request ||
- null === $repository
- ) {
+ if (null === $request) {
return null;
}
+ $repository = $operation->getRepository();
$repositoryInstance = null;
- $arguments = $this->parseArgumentValues($operation->getRepositoryArguments() ?? []);
-
- if (\is_string($repository)) {
- $defaultMethod = $operation instanceof CollectionOperationInterface ? 'createPaginator' : 'findOneBy';
-
- if ($operation instanceof BulkOperationInterface) {
- $defaultMethod = 'findById';
- }
-
- $method = $operation->getRepositoryMethod() ?? $defaultMethod;
- if (!$this->locator->has($repository)) {
- throw new \RuntimeException(sprintf('Repository "%s" not found on operation "%s"', $repository, $operation->getName() ?? ''));
- }
-
- $repositoryInstance = $this->locator->get($repository);
-
- // make it as callable
- /** @var callable $repository */
- $repository = [$repositoryInstance, $method];
+ if (\is_callable($repository)) {
+ $callableRepository = $repository;
+ } else {
+ $repositoryInstance = $this->getRepositoryInstance($operation);
+ $callableRepository = $this->createCallableRepository($operation, $repositoryInstance);
}
try {
- $reflector = CallableReflection::from($repository);
+ $reflector = CallableReflection::from($callableRepository);
} catch (\ReflectionException $exception) {
if (null === $repositoryInstance) {
throw $exception;
@@ -85,13 +71,15 @@ public function provide(Operation $operation, Context $context): object|array|nu
$reflector = CallableReflection::from($callable);
}
+ $arguments = $this->parseArgumentValues($operation->getRepositoryArguments() ?? []);
+
if ([] === $arguments) {
$arguments = $this->argumentResolver->getArguments($request, $reflector);
}
- $data = $repository(...$arguments);
+ $data = $callableRepository(...$arguments);
- if ($data instanceof Pagerfanta) {
+ if ($data instanceof PagerfantaInterface) {
$currentPage = $request->query->getInt('page', 1);
$data->setCurrentPage($currentPage);
}
@@ -107,4 +95,43 @@ private function parseArgumentValues(array $arguments): array
return $arguments;
}
+
+ private function createCallableRepository(Operation $operation, mixed $repositoryInstance): callable
+ {
+ $defaultMethod = $operation instanceof CollectionOperationInterface ? 'createPaginator' : 'findOneBy';
+
+ if ($operation instanceof BulkOperationInterface) {
+ $defaultMethod = 'findById';
+ }
+
+ $method = $operation->getRepositoryMethod() ?? $defaultMethod;
+
+ // make it as callable
+ /** @var callable $repository */
+ $repository = [$repositoryInstance, $method];
+
+ return $repository;
+ }
+
+ private function getRepositoryInstance(Operation $operation): mixed
+ {
+ /** @var string|null $repository */
+ $repository = $operation->getRepository();
+
+ if (null === $repository) {
+ /** @var class-string $entityClass */
+ $entityClass = $operation->getResource()?->getClass();
+
+ /** @var EntityManagerInterface $manager */
+ $manager = $this->managerRegistry->getManagerForClass($entityClass);
+
+ return $manager->getRepository($entityClass);
+ }
+
+ if (!$this->locator->has($repository)) {
+ throw new \RuntimeException(sprintf('Repository "%s" not found on operation "%s"', $repository, $operation->getName() ?? ''));
+ }
+
+ return $this->locator->get($repository);
+ }
}
diff --git a/src/Component/src/Grid/State/RequestGridProvider.php b/src/Component/src/Grid/State/RequestGridProvider.php
index 8b5311840..79237872f 100644
--- a/src/Component/src/Grid/State/RequestGridProvider.php
+++ b/src/Component/src/Grid/State/RequestGridProvider.php
@@ -13,7 +13,7 @@
namespace Sylius\Resource\Grid\State;
-use Pagerfanta\Pagerfanta;
+use Pagerfanta\PagerfantaInterface;
use Sylius\Component\Grid\Parameters;
use Sylius\Component\Grid\Provider\GridProviderInterface;
use Sylius\Resource\Context\Context;
@@ -63,7 +63,7 @@ public function provide(Operation $operation, Context $context): object|array|nu
$data = $gridView->getData();
- if ($data instanceof Pagerfanta) {
+ if ($data instanceof PagerfantaInterface) {
$currentPage = $request->query->getInt('page', 1);
$data->setCurrentPage($currentPage);
diff --git a/src/Component/src/Metadata/Api/Delete.php b/src/Component/src/Metadata/Api/Delete.php
index edc92537f..63447dcab 100644
--- a/src/Component/src/Metadata/Api/Delete.php
+++ b/src/Component/src/Metadata/Api/Delete.php
@@ -24,7 +24,9 @@ final class Delete extends HttpOperation implements DeleteOperationInterface, Ap
{
public function __construct(
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -45,12 +47,15 @@ public function __construct(
?array $denormalizationContext = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
) {
parent::__construct(
methods: ['DELETE'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'delete',
name: $name,
@@ -71,6 +76,7 @@ public function __construct(
denormalizationContext: $denormalizationContext,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
);
}
diff --git a/src/Component/src/Metadata/Api/Get.php b/src/Component/src/Metadata/Api/Get.php
index ae77d6303..bba4e7196 100644
--- a/src/Component/src/Metadata/Api/Get.php
+++ b/src/Component/src/Metadata/Api/Get.php
@@ -24,7 +24,9 @@ final class Get extends HttpOperation implements ShowOperationInterface, ApiOper
{
public function __construct(
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -45,12 +47,15 @@ public function __construct(
?array $denormalizationContext = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
) {
parent::__construct(
methods: ['GET'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'get',
name: $name,
@@ -71,6 +76,7 @@ public function __construct(
denormalizationContext: $denormalizationContext,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
);
}
diff --git a/src/Component/src/Metadata/Api/GetCollection.php b/src/Component/src/Metadata/Api/GetCollection.php
index b4cbe0ad1..372bf7bce 100644
--- a/src/Component/src/Metadata/Api/GetCollection.php
+++ b/src/Component/src/Metadata/Api/GetCollection.php
@@ -24,7 +24,9 @@ final class GetCollection extends HttpOperation implements CollectionOperationIn
{
public function __construct(
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -45,12 +47,15 @@ public function __construct(
?array $denormalizationContext = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
) {
parent::__construct(
methods: ['GET'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'get_collection',
name: $name,
@@ -71,6 +76,7 @@ public function __construct(
denormalizationContext: $denormalizationContext,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
);
}
diff --git a/src/Component/src/Metadata/Api/Patch.php b/src/Component/src/Metadata/Api/Patch.php
index 803418410..a0e8f3576 100644
--- a/src/Component/src/Metadata/Api/Patch.php
+++ b/src/Component/src/Metadata/Api/Patch.php
@@ -24,7 +24,9 @@ final class Patch extends HttpOperation implements UpdateOperationInterface, Api
{
public function __construct(
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -45,12 +47,15 @@ public function __construct(
?array $denormalizationContext = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
) {
parent::__construct(
methods: ['PATCH'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'patch',
name: $name,
@@ -71,6 +76,7 @@ public function __construct(
denormalizationContext: $denormalizationContext,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
);
}
diff --git a/src/Component/src/Metadata/Api/Post.php b/src/Component/src/Metadata/Api/Post.php
index 4a0e96eb3..2a87cbdd6 100644
--- a/src/Component/src/Metadata/Api/Post.php
+++ b/src/Component/src/Metadata/Api/Post.php
@@ -24,7 +24,9 @@ final class Post extends HttpOperation implements CreateOperationInterface, ApiO
{
public function __construct(
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -45,12 +47,15 @@ public function __construct(
?array $denormalizationContext = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
) {
parent::__construct(
methods: ['POST'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'post',
name: $name,
@@ -71,6 +76,7 @@ public function __construct(
denormalizationContext: $denormalizationContext,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
);
}
diff --git a/src/Component/src/Metadata/Api/Put.php b/src/Component/src/Metadata/Api/Put.php
index d4a954679..b8a9f4ed9 100644
--- a/src/Component/src/Metadata/Api/Put.php
+++ b/src/Component/src/Metadata/Api/Put.php
@@ -24,7 +24,9 @@ final class Put extends HttpOperation implements UpdateOperationInterface, ApiOp
{
public function __construct(
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -45,12 +47,15 @@ public function __construct(
?array $denormalizationContext = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
) {
parent::__construct(
methods: ['PUT'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'put',
name: $name,
@@ -71,6 +76,7 @@ public function __construct(
denormalizationContext: $denormalizationContext,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
);
}
diff --git a/src/Component/src/Metadata/ApplyStateMachineTransition.php b/src/Component/src/Metadata/ApplyStateMachineTransition.php
index 10488d6c5..8318d4997 100644
--- a/src/Component/src/Metadata/ApplyStateMachineTransition.php
+++ b/src/Component/src/Metadata/ApplyStateMachineTransition.php
@@ -22,6 +22,7 @@ final class ApplyStateMachineTransition extends HttpOperation implements UpdateO
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
?string $template = null,
?string $shortName = null,
@@ -36,6 +37,7 @@ public function __construct(
?bool $validate = null,
?string $formType = null,
?array $formOptions = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
private ?string $stateMachineComponent = null,
private ?string $stateMachineTransition = null,
@@ -44,6 +46,7 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['PUT', 'PATCH', 'POST'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
template: $template,
shortName: $shortName ?? $stateMachineTransition ?? 'apply_state_machine_transition',
@@ -58,6 +61,7 @@ public function __construct(
validate: $validate ?? false,
formType: $formType,
formOptions: $formOptions,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
);
}
diff --git a/src/Component/src/Metadata/AsResource.php b/src/Component/src/Metadata/AsResource.php
index f2eb89938..df0925b2e 100644
--- a/src/Component/src/Metadata/AsResource.php
+++ b/src/Component/src/Metadata/AsResource.php
@@ -16,6 +16,9 @@
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
final class AsResource
{
+ /**
+ * @param class-string|null $class
+ */
public function __construct(
private ?string $alias = null,
private ?string $section = null,
diff --git a/src/Component/src/Metadata/BulkDelete.php b/src/Component/src/Metadata/BulkDelete.php
index b0798d492..b22724041 100644
--- a/src/Component/src/Metadata/BulkDelete.php
+++ b/src/Component/src/Metadata/BulkDelete.php
@@ -22,7 +22,9 @@ final class BulkDelete extends HttpOperation implements DeleteOperationInterface
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -37,6 +39,7 @@ public function __construct(
?string $formType = null,
?array $formOptions = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
?array $redirectArguments = null,
?array $vars = null,
@@ -44,7 +47,9 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['DELETE', 'POST'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'bulk_delete',
name: $name,
@@ -59,6 +64,7 @@ public function __construct(
formType: $formType,
formOptions: $formOptions,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
redirectArguments: $redirectArguments,
vars: $vars,
diff --git a/src/Component/src/Metadata/BulkUpdate.php b/src/Component/src/Metadata/BulkUpdate.php
index 9b5c1f9b9..3ddccfb70 100644
--- a/src/Component/src/Metadata/BulkUpdate.php
+++ b/src/Component/src/Metadata/BulkUpdate.php
@@ -22,7 +22,9 @@ final class BulkUpdate extends HttpOperation implements UpdateOperationInterface
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -41,6 +43,7 @@ public function __construct(
?array $formOptions = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?string $redirectToRoute = null,
?array $redirectArguments = null,
?array $vars = null,
@@ -51,7 +54,9 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['PUT', 'PATCH'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'bulk_update',
name: $name,
@@ -70,6 +75,7 @@ public function __construct(
formOptions: $formOptions,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
redirectToRoute: $redirectToRoute,
redirectArguments: $redirectArguments,
vars: $vars,
diff --git a/src/Component/src/Metadata/Create.php b/src/Component/src/Metadata/Create.php
index cf43df572..90d2ce372 100644
--- a/src/Component/src/Metadata/Create.php
+++ b/src/Component/src/Metadata/Create.php
@@ -25,7 +25,9 @@ final class Create extends HttpOperation implements CreateOperationInterface, St
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -47,6 +49,7 @@ public function __construct(
?array $formOptions = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
string|callable|null $twigContextFactory = null,
?string $redirectToRoute = null,
?array $redirectArguments = null,
@@ -58,7 +61,9 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['GET', 'POST'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'create',
name: $name,
@@ -77,6 +82,7 @@ public function __construct(
formOptions: $formOptions,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
twigContextFactory: $twigContextFactory,
redirectToRoute: $redirectToRoute,
redirectArguments: $redirectArguments,
diff --git a/src/Component/src/Metadata/Delete.php b/src/Component/src/Metadata/Delete.php
index 21eced536..91929c5d3 100644
--- a/src/Component/src/Metadata/Delete.php
+++ b/src/Component/src/Metadata/Delete.php
@@ -22,7 +22,9 @@ final class Delete extends HttpOperation implements DeleteOperationInterface
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -41,6 +43,7 @@ public function __construct(
?array $formOptions = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
string|callable|null $twigContextFactory = null,
?string $redirectToRoute = null,
?array $redirectArguments = null,
@@ -49,7 +52,9 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['DELETE', 'POST'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'delete',
name: $name,
@@ -68,6 +73,7 @@ public function __construct(
formOptions: $formOptions,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
twigContextFactory: $twigContextFactory,
redirectToRoute: $redirectToRoute,
redirectArguments: $redirectArguments,
diff --git a/src/Component/src/Metadata/Extractor/MetadataExtractorInterface.php b/src/Component/src/Metadata/Extractor/MetadataExtractorInterface.php
new file mode 100644
index 000000000..4c8dd48b2
--- /dev/null
+++ b/src/Component/src/Metadata/Extractor/MetadataExtractorInterface.php
@@ -0,0 +1,24 @@
+getResourceFilePaths() as $filePath) {
+ if (!is_readable($filePath)) {
+ continue;
+ }
+
+ $resource = $this->getPHPFileClosure($filePath)();
+
+ if (!$resource instanceof ResourceMetadata) {
+ continue;
+ }
+
+ $resourceReflection = new \ReflectionClass($resource);
+
+ foreach ($resourceReflection->getProperties() as $property) {
+ $property->setAccessible(true);
+ $resolvedValue = $this->resolve($property->getValue($resource));
+ $property->setValue($resource, $resolvedValue);
+ }
+
+ $metadata[] = $resource;
+ }
+
+ return $metadata;
+ }
+
+ private function getResourceFilePaths(): iterable
+ {
+ foreach ($this->createFinder() as $file) {
+ yield $file->getPathname();
+ }
+ }
+
+ private function createFinder(): Finder
+ {
+ $finder = (new Finder())->files();
+
+ foreach ($this->resourceMapping['imports'] ?? [] as $path) {
+ $finder->in($path);
+ }
+
+ return $finder->files();
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+ private function getPHPFileClosure(string $filePath): \Closure
+ {
+ return \Closure::bind(function () use ($filePath): mixed {
+ return require $filePath;
+ }, null, null);
+ }
+
+ /**
+ * Recursively replaces placeholders with the service container parameters.
+ *
+ * @see https://github.com/symfony/symfony/blob/6fec32c/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php
+ *
+ * @param mixed $value The source which might contain "%placeholders%"
+ *
+ * @throws \RuntimeException When a container value is not a string or a numeric value
+ *
+ * @return mixed The source with the placeholders replaced by the container
+ * parameters. Arrays are resolved recursively.
+ */
+ private function resolve(mixed $value): mixed
+ {
+ $container = $this->container;
+
+ if (null === $container) {
+ return $value;
+ }
+
+ if (\is_array($value)) {
+ foreach ($value as $key => $val) {
+ $value[$key] = $this->resolve($val);
+ }
+
+ return $value;
+ }
+
+ if (!\is_string($value)) {
+ return $value;
+ }
+
+ $escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($value, $container) {
+ $parameter = $match[1] ?? null;
+
+ // skip %%
+ if (!isset($parameter)) {
+ return '%%';
+ }
+
+ if (preg_match('/^env\(\w+\)$/', $parameter)) {
+ throw new \RuntimeException(\sprintf('Using "%%%s%%" is not allowed in routing configuration.', $parameter));
+ }
+
+ if (\array_key_exists($parameter, $this->collectedParameters)) {
+ return $this->collectedParameters[$parameter];
+ }
+
+ if ($container instanceof SymfonyContainerInterface) {
+ $resolved = $container->getParameter($parameter);
+ } else {
+ $resolved = $container->get($parameter);
+ }
+
+ if (\is_string($resolved) || is_numeric($resolved)) {
+ $this->collectedParameters[$parameter] = $resolved;
+
+ return (string) $resolved;
+ }
+
+ throw new \RuntimeException(\sprintf('The container parameter "%s", used in the resource configuration value "%s", must be a string or numeric, but it is of type %s.', $parameter, $value, \gettype($resolved)));
+ }, $value);
+
+ return str_replace('%%', '%', $escapedValue ?? '');
+ }
+}
diff --git a/src/Component/src/Metadata/HttpOperation.php b/src/Component/src/Metadata/HttpOperation.php
index 194166b67..cb16b5be9 100644
--- a/src/Component/src/Metadata/HttpOperation.php
+++ b/src/Component/src/Metadata/HttpOperation.php
@@ -26,6 +26,7 @@ public function __construct(
protected ?string $path = null,
protected ?string $routeName = null,
protected ?string $routePrefix = null,
+ protected ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -46,6 +47,7 @@ public function __construct(
?array $denormalizationContext = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
string|callable|null $twigContextFactory = null,
protected ?string $redirectToRoute = null,
protected ?array $redirectArguments = null,
@@ -72,6 +74,7 @@ public function __construct(
denormalizationContext: $denormalizationContext,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
);
$this->twigContextFactory = $twigContextFactory;
@@ -129,6 +132,19 @@ public function withRoutePrefix(?string $routePrefix): self
return $self;
}
+ public function getRouteRequirements(): ?array
+ {
+ return $this->routeRequirements;
+ }
+
+ public function withRouteRequirements(array $routeRequirements): self
+ {
+ $self = clone $this;
+ $self->routeRequirements = $routeRequirements;
+
+ return $self;
+ }
+
public function getTwigContextFactory(): callable|string|null
{
return $this->twigContextFactory;
diff --git a/src/Component/src/Metadata/Index.php b/src/Component/src/Metadata/Index.php
index ae64fe889..7e1cadbd3 100644
--- a/src/Component/src/Metadata/Index.php
+++ b/src/Component/src/Metadata/Index.php
@@ -22,7 +22,9 @@ final class Index extends HttpOperation implements CollectionOperationInterface,
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -40,6 +42,7 @@ public function __construct(
?string $formType = null,
?array $formOptions = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
?array $validationContext = null,
string|callable|null $twigContextFactory = null,
?string $redirectToRoute = null,
@@ -49,7 +52,9 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['GET'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'index',
name: $name,
@@ -68,6 +73,7 @@ public function __construct(
formOptions: $formOptions,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
twigContextFactory: $twigContextFactory,
redirectToRoute: $redirectToRoute,
vars: $vars,
diff --git a/src/Component/src/Metadata/Operation.php b/src/Component/src/Metadata/Operation.php
index e47e89fc2..d1b70282a 100644
--- a/src/Component/src/Metadata/Operation.php
+++ b/src/Component/src/Metadata/Operation.php
@@ -53,6 +53,7 @@ public function __construct(
protected ?array $denormalizationContext = null,
protected ?array $validationContext = null,
protected ?string $eventShortName = null,
+ protected ?string $notificationMessage = null,
) {
$this->provider = $provider;
$this->processor = $processor;
@@ -332,4 +333,17 @@ public function withEventShortName(string $eventShortName): self
return $self;
}
+
+ public function getNotificationMessage(): ?string
+ {
+ return $this->notificationMessage;
+ }
+
+ public function withNotificationMessage(string $notificationMessage): self
+ {
+ $self = clone $this;
+ $self->notificationMessage = $notificationMessage;
+
+ return $self;
+ }
}
diff --git a/src/Component/src/Metadata/Operation/CustomPhpFileOperationUpdater.php b/src/Component/src/Metadata/Operation/CustomPhpFileOperationUpdater.php
new file mode 100644
index 000000000..faf8df833
--- /dev/null
+++ b/src/Component/src/Metadata/Operation/CustomPhpFileOperationUpdater.php
@@ -0,0 +1,94 @@
+getResourceFilePaths() as $filePath) {
+ if (!is_readable($filePath)) {
+ continue;
+ }
+
+ $resource = $this->getPHPFileClosure($filePath)();
+
+ if (!$resource instanceof \Closure) {
+ continue;
+ }
+
+ $resourceReflection = new \ReflectionFunction($resource);
+
+ if (1 !== $resourceReflection->getNumberOfParameters()) {
+ continue;
+ }
+
+ $firstParameterType = ($resourceReflection->getParameters()[0] ?? null)?->getType();
+
+ if (!$firstParameterType instanceof \ReflectionNamedType) {
+ continue;
+ }
+
+ // Check if the closure parameter is an operation
+ if (!is_a($firstParameterType->getName(), Operation::class, true)) {
+ continue;
+ }
+
+ $operation = $resource($operation);
+ }
+
+ return $operation;
+ }
+
+ private function getResourceFilePaths(): iterable
+ {
+ foreach ($this->createFinder() as $file) {
+ yield $file->getPathname();
+ }
+ }
+
+ private function createFinder(): Finder
+ {
+ $finder = (new Finder())->files();
+
+ foreach ($this->resourceMapping['imports'] ?? [] as $path) {
+ $finder->in($path);
+ }
+
+ return $finder->files();
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+ private function getPHPFileClosure(string $filePath): \Closure
+ {
+ return \Closure::bind(function () use ($filePath): mixed {
+ return require $filePath;
+ }, null, null);
+ }
+}
diff --git a/src/Component/src/Metadata/Operation/OperationUpdaterInterface.php b/src/Component/src/Metadata/Operation/OperationUpdaterInterface.php
new file mode 100644
index 000000000..1855c7cd0
--- /dev/null
+++ b/src/Component/src/Metadata/Operation/OperationUpdaterInterface.php
@@ -0,0 +1,21 @@
+decorated) {
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
+ }
$attributes = ClassReflection::getClassAttributes($resourceClass);
@@ -48,6 +52,7 @@ public function create(string $resourceClass): ResourceMetadataCollection
/**
* @param \ReflectionAttribute[] $attributes
+ * @param class-string $resourceClass
*
* @return ResourceMetadata[]
*/
@@ -118,6 +123,9 @@ private function buildResourceOperations(array $attributes, string $resourceClas
return $resources;
}
+ /**
+ * @param class-string $resourceClass
+ */
private function getResourceWithDefaults(string $resourceClass, ResourceMetadata $resource, MetadataInterface $resourceConfiguration): ResourceMetadata
{
$resource = $resource->withClass($resourceClass);
@@ -171,10 +179,6 @@ private function getOperationWithDefaults(ResourceMetadata $resource, Operation
$operation = $operation->withResource($resource);
- if (null === $operation->getRepository()) {
- $operation = $operation->withRepository($resourceConfiguration->getServiceId('repository'));
- }
-
if (null === $operation->getFormType()) {
$formType = $resource->getFormType() ?? $resourceConfiguration->getClass('form');
$operation = $operation->withFormType($formType);
diff --git a/src/Component/src/Metadata/Resource/Factory/AttributesResourceNameCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/AttributesResourceNameCollectionFactory.php
new file mode 100644
index 000000000..859070653
--- /dev/null
+++ b/src/Component/src/Metadata/Resource/Factory/AttributesResourceNameCollectionFactory.php
@@ -0,0 +1,61 @@
+decorated) {
+ foreach ($this->decorated->create() as $resourceClass) {
+ $classes[$resourceClass] = true;
+ }
+ }
+
+ $paths = $this->mapping['paths'] ?? [];
+
+ foreach (ClassReflection::getResourcesByPaths($paths) as $resourceClass) {
+ if ([] === ClassReflection::getClassAttributes($resourceClass, AsResource::class)) {
+ continue;
+ }
+
+ $classes[$resourceClass] = true;
+ }
+
+ return new ResourceNameCollection(array_keys($classes));
+ }
+}
diff --git a/src/Component/src/Metadata/Resource/Factory/CustomResourceMetadataCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/CustomResourceMetadataCollectionFactory.php
new file mode 100644
index 000000000..52feaeac1
--- /dev/null
+++ b/src/Component/src/Metadata/Resource/Factory/CustomResourceMetadataCollectionFactory.php
@@ -0,0 +1,58 @@
+decorated) {
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
+ }
+
+ $newMetadataCollection = new ResourceMetadataCollection();
+
+ /** @var ResourceMetadata $resourceMetadata */
+ foreach ($resourceMetadataCollection as $resourceMetadata) {
+ $operations = $resourceMetadata->getOperations() ?? new Operations();
+
+ /** @var Operation $operation */
+ foreach ($operations as $operation) {
+ $operationName = $operation->getName();
+ Assert::notNull($operationName);
+ $operations->add($operationName, $this->metadataUpdater->update($operation));
+ }
+
+ $newMetadataCollection[] = $resourceMetadata->withOperations($operations);
+ }
+
+ return $newMetadataCollection;
+ }
+}
diff --git a/src/Component/src/Metadata/Resource/Factory/EventShortNameResourceMetadataCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/EventShortNameResourceMetadataCollectionFactory.php
index 7311c551d..23bf61d29 100644
--- a/src/Component/src/Metadata/Resource/Factory/EventShortNameResourceMetadataCollectionFactory.php
+++ b/src/Component/src/Metadata/Resource/Factory/EventShortNameResourceMetadataCollectionFactory.php
@@ -29,10 +29,10 @@ public function __construct(
public function create(string $resourceClass): ResourceMetadataCollection
{
- $resourceCollectionMetadata = $this->decorated->create($resourceClass);
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
/** @var ResourceMetadata $resource */
- foreach ($resourceCollectionMetadata->getIterator() as $i => $resource) {
+ foreach ($resourceMetadataCollection->getIterator() as $i => $resource) {
$operations = $resource->getOperations() ?? new Operations();
/** @var Operation $operation */
@@ -45,10 +45,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
$resource = $resource->withOperations($operations);
- $resourceCollectionMetadata[$i] = $resource;
+ $resourceMetadataCollection[$i] = $resource;
}
- return $resourceCollectionMetadata;
+ return $resourceMetadataCollection;
}
private function addDefaults(ResourceMetadata $resource, Operation $operation): Operation
diff --git a/src/Component/src/Metadata/Resource/Factory/FactoryResourceMetadataCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/FactoryResourceMetadataCollectionFactory.php
index 26eb23c8e..bc6c10c4e 100644
--- a/src/Component/src/Metadata/Resource/Factory/FactoryResourceMetadataCollectionFactory.php
+++ b/src/Component/src/Metadata/Resource/Factory/FactoryResourceMetadataCollectionFactory.php
@@ -31,10 +31,10 @@ public function __construct(
public function create(string $resourceClass): ResourceMetadataCollection
{
- $resourceCollectionMetadata = $this->decorated->create($resourceClass);
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
/** @var ResourceMetadata $resource */
- foreach ($resourceCollectionMetadata->getIterator() as $i => $resource) {
+ foreach ($resourceMetadataCollection->getIterator() as $i => $resource) {
$resourceConfiguration = $this->resourceRegistry->get($resource->getAlias() ?? '');
$operations = $resource->getOperations() ?? new Operations();
@@ -55,10 +55,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
$resource = $resource->withOperations($operations);
- $resourceCollectionMetadata[$i] = $resource;
+ $resourceMetadataCollection[$i] = $resource;
}
- return $resourceCollectionMetadata;
+ return $resourceMetadataCollection;
}
private function addDefaults(MetadataInterface $resourceConfiguration, ResourceMetadata $resource, FactoryAwareOperationInterface $operation): FactoryAwareOperationInterface
diff --git a/src/Component/src/Metadata/Resource/Factory/OperationDefaultsTrait.php b/src/Component/src/Metadata/Resource/Factory/OperationDefaultsTrait.php
new file mode 100644
index 000000000..3641f209b
--- /dev/null
+++ b/src/Component/src/Metadata/Resource/Factory/OperationDefaultsTrait.php
@@ -0,0 +1,134 @@
+withClass($resourceClass);
+
+ if (null === $resource->getAlias()) {
+ $resource = $resource->withAlias($resourceConfiguration->getAlias());
+ }
+
+ if (null === $resource->getApplicationName()) {
+ $resource = $resource->withApplicationName($resourceConfiguration->getApplicationName());
+ }
+
+ if (null === $resource->getName()) {
+ $resource = $resource->withName($resourceConfiguration->getName());
+ }
+
+ return $resource;
+ }
+
+ private function getOperationWithDefaults(OperationRouteNameFactory $operationRouteNameFactory, RegistryInterface $resourceRegistry, ResourceMetadata $resource, Operation $operation): array
+ {
+ $resourceConfiguration = $resourceRegistry->get($resource->getAlias() ?? '');
+
+ $operation = $operation->withResource($resource);
+
+ if (null === $resource->getName()) {
+ $resourceName = $resourceConfiguration->getName();
+
+ $resource = $resource->withName($resourceName);
+ $operation = $operation->withResource($resource);
+ }
+
+ if (null === $resource->getPluralName()) {
+ $resourcePluralName = $resourceConfiguration->getPluralName();
+
+ $resource = $resource->withPluralName($resourcePluralName);
+ $operation = $operation->withResource($resource);
+ }
+
+ if (null === $operation->getNormalizationContext()) {
+ $operation = $operation->withNormalizationContext($resource->getNormalizationContext());
+ }
+
+ if (null === $operation->getDenormalizationContext()) {
+ $operation = $operation->withDenormalizationContext($resource->getDenormalizationContext());
+ }
+
+ if (null === $operation->getValidationContext()) {
+ $operation = $operation->withValidationContext($resource->getValidationContext());
+ }
+
+ $operation = $operation->withResource($resource);
+
+ if (null === $operation->getFormType()) {
+ $formType = $resource->getFormType() ?? $resourceConfiguration->getClass('form');
+ $operation = $operation->withFormType($formType);
+ }
+
+ $formOptions = $this->buildFormOptions($operation, $resourceConfiguration);
+ $operation = $operation->withFormOptions($formOptions);
+
+ if ($operation instanceof HttpOperation) {
+ if (null === $operation->getRoutePrefix()) {
+ $operation = $operation->withRoutePrefix($resource->getRoutePrefix() ?? null);
+ }
+
+ if (null === $operation->getTwigContextFactory()) {
+ $operation = $operation->withTwigContextFactory('sylius.twig.context.factory.default');
+ }
+
+ if (null === $routeName = $operation->getRouteName()) {
+ $routeName = $operationRouteNameFactory->createRouteName($operation);
+ $operation = $operation->withRouteName($routeName);
+ }
+
+ if (null === $operation->getResponder()) {
+ $operation = $operation->withResponder(Responder::class);
+ }
+
+ $operation = $operation->withName($routeName);
+ }
+
+ $operationName = $operation->getName();
+
+ return [$operationName, $operation];
+ }
+
+ private function buildFormOptions(Operation $operation, MetadataInterface $resourceConfiguration): array
+ {
+ $formOptions = array_merge(
+ ['data_class' => $resourceConfiguration->getClass('model')],
+ $operation->getFormOptions() ?? [],
+ );
+
+ $validationGroups = $operation->getValidationContext()['groups'] ?? null;
+
+ if (null !== $validationGroups) {
+ $formOptions = array_merge(['validation_groups' => $validationGroups], $formOptions);
+ }
+
+ return $formOptions;
+ }
+}
diff --git a/src/Component/src/Metadata/Resource/Factory/PhpFileResourceMetadataCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/PhpFileResourceMetadataCollectionFactory.php
new file mode 100644
index 000000000..97acb87f7
--- /dev/null
+++ b/src/Component/src/Metadata/Resource/Factory/PhpFileResourceMetadataCollectionFactory.php
@@ -0,0 +1,73 @@
+decorated) {
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
+ }
+
+ foreach ($this->metadataExtractor->extract() as $resource) {
+ if ($resourceClass !== $resource->getClass()) {
+ continue;
+ }
+
+ $resourceAlias = $resource->getAlias();
+
+ if (null !== $resourceAlias) {
+ $resourceConfiguration = $this->resourceRegistry->get($resource->getAlias() ?? '');
+ } else {
+ $resourceConfiguration = $this->resourceRegistry->getByClass($resourceClass);
+ }
+
+ $resource = $this->getResourceWithDefaults($resourceClass, $resource, $resourceConfiguration);
+
+ $operations = [];
+ /** @var Operation $operation */
+ foreach ($resource->getOperations() ?? new Operations() as $operation) {
+ [$key, $operation] = $this->getOperationWithDefaults($this->operationRouteNameFactory, $this->resourceRegistry, $resource, $operation);
+ $operations[$key] = $operation;
+ }
+
+ if ($operations) {
+ $resource = $resource->withOperations(new Operations($operations));
+ }
+
+ $resourceMetadataCollection[] = $resource;
+ }
+
+ return $resourceMetadataCollection;
+ }
+}
diff --git a/src/Component/src/Metadata/Resource/Factory/PhpFileResourceNameCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/PhpFileResourceNameCollectionFactory.php
new file mode 100644
index 000000000..401365050
--- /dev/null
+++ b/src/Component/src/Metadata/Resource/Factory/PhpFileResourceNameCollectionFactory.php
@@ -0,0 +1,51 @@
+decorated) {
+ foreach ($this->decorated->create() as $resourceClass) {
+ $classes[$resourceClass] = true;
+ }
+ }
+
+ foreach ($this->metadataExtractor->extract() as $resource) {
+ $resourceClass = $resource->getClass();
+
+ if (null === $resourceClass) {
+ continue;
+ }
+
+ $classes[$resourceClass] = true;
+ }
+
+ return new ResourceNameCollection(array_keys($classes));
+ }
+}
diff --git a/src/Component/src/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactory.php
index f5f57d1fe..13045c05f 100644
--- a/src/Component/src/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactory.php
+++ b/src/Component/src/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactory.php
@@ -19,7 +19,6 @@
use Sylius\Resource\Metadata\Operations;
use Sylius\Resource\Metadata\Resource\ResourceMetadataCollection;
use Sylius\Resource\Metadata\ResourceMetadata;
-use Sylius\Resource\Symfony\Request\State\Provider;
final class ProviderResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
@@ -30,10 +29,10 @@ public function __construct(
public function create(string $resourceClass): ResourceMetadataCollection
{
- $resourceCollectionMetadata = $this->decorated->create($resourceClass);
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
/** @var ResourceMetadata $resource */
- foreach ($resourceCollectionMetadata->getIterator() as $i => $resource) {
+ foreach ($resourceMetadataCollection->getIterator() as $i => $resource) {
$operations = $resource->getOperations() ?? new Operations();
/** @var Operation $operation */
@@ -46,10 +45,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
$resource = $resource->withOperations($operations);
- $resourceCollectionMetadata[$i] = $resource;
+ $resourceMetadataCollection[$i] = $resource;
}
- return $resourceCollectionMetadata;
+ return $resourceMetadataCollection;
}
private function addDefaults(Operation $operation): Operation
@@ -62,10 +61,6 @@ private function addDefaults(Operation $operation): Operation
$operation = $operation->withProvider(RequestGridProvider::class);
}
- if (null === $operation->getProvider()) {
- $operation = $operation->withProvider(Provider::class);
- }
-
return $operation;
}
}
diff --git a/src/Component/src/Metadata/Resource/Factory/ResourceNameCollectionFactoryInterface.php b/src/Component/src/Metadata/Resource/Factory/ResourceNameCollectionFactoryInterface.php
new file mode 100644
index 000000000..c35d9c2a5
--- /dev/null
+++ b/src/Component/src/Metadata/Resource/Factory/ResourceNameCollectionFactoryInterface.php
@@ -0,0 +1,29 @@
+decorated->create($resourceClass);
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
/** @var ResourceMetadata $resource */
- foreach ($resourceCollectionMetadata->getIterator() as $i => $resource) {
+ foreach ($resourceMetadataCollection->getIterator() as $i => $resource) {
$resourceConfiguration = $this->resourceRegistry->get($resource->getAlias() ?? '');
$operations = $resource->getOperations() ?? new Operations();
@@ -50,10 +50,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
$resource = $resource->withOperations($operations);
- $resourceCollectionMetadata[$i] = $resource;
+ $resourceMetadataCollection[$i] = $resource;
}
- return $resourceCollectionMetadata;
+ return $resourceMetadataCollection;
}
private function addDefaults(MetadataInterface $resourceConfiguration, Operation $operation): Operation
diff --git a/src/Component/src/Metadata/Resource/Factory/TemplatesDirResourceMetadataCollectionFactory.php b/src/Component/src/Metadata/Resource/Factory/TemplatesDirResourceMetadataCollectionFactory.php
index 7e1608cda..d112e3f58 100644
--- a/src/Component/src/Metadata/Resource/Factory/TemplatesDirResourceMetadataCollectionFactory.php
+++ b/src/Component/src/Metadata/Resource/Factory/TemplatesDirResourceMetadataCollectionFactory.php
@@ -28,10 +28,10 @@ public function __construct(
public function create(string $resourceClass): ResourceMetadataCollection
{
- $resourceCollectionMetadata = $this->decorated->create($resourceClass);
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
/** @var ResourceMetadata $resource */
- foreach ($resourceCollectionMetadata->getIterator() as $i => $resource) {
+ foreach ($resourceMetadataCollection->getIterator() as $i => $resource) {
$operations = $resource->getOperations() ?? new Operations();
/** @var Operation $operation */
@@ -44,10 +44,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
$resource = $resource->withOperations($operations);
- $resourceCollectionMetadata[$i] = $resource;
+ $resourceMetadataCollection[$i] = $resource;
}
- return $resourceCollectionMetadata;
+ return $resourceMetadataCollection;
}
private function addDefaults(ResourceMetadata $resource, Operation $operation): Operation
diff --git a/src/Component/src/Metadata/Resource/ResourceNameCollection.php b/src/Component/src/Metadata/Resource/ResourceNameCollection.php
new file mode 100644
index 000000000..cec732846
--- /dev/null
+++ b/src/Component/src/Metadata/Resource/ResourceNameCollection.php
@@ -0,0 +1,42 @@
+
+ */
+ public function getIterator(): \Traversable
+ {
+ return new \ArrayIterator($this->names);
+ }
+
+ public function count(): int
+ {
+ return \count($this->names);
+ }
+}
diff --git a/src/Component/src/Metadata/ResourceMetadata.php b/src/Component/src/Metadata/ResourceMetadata.php
index c2ee0a9c3..404618c3f 100644
--- a/src/Component/src/Metadata/ResourceMetadata.php
+++ b/src/Component/src/Metadata/ResourceMetadata.php
@@ -17,6 +17,9 @@ final class ResourceMetadata
{
private ?Operations $operations;
+ /**
+ * @param class-string|null $class
+ */
public function __construct(
private ?string $alias = null,
private ?string $section = null,
@@ -36,13 +39,27 @@ public function __construct(
?array $operations = null,
) {
$this->operations = null === $operations ? null : new Operations($operations);
+
+ if (null !== $driver && false !== $driver) {
+ trigger_deprecation(
+ 'sylius/resource',
+ '1.13',
+ 'Using driver is deprecated. If your resource is managed by Doctrine you have nothing to do, otherwise use a custom provider.',
+ );
+ }
}
+ /**
+ * @return class-string|null
+ */
public function getClass(): ?string
{
return $this->class;
}
+ /**
+ * @param class-string $class
+ */
public function withClass(string $class): self
{
$self = clone $this;
diff --git a/src/Component/src/Metadata/Show.php b/src/Component/src/Metadata/Show.php
index 72bbe12e2..79351610c 100644
--- a/src/Component/src/Metadata/Show.php
+++ b/src/Component/src/Metadata/Show.php
@@ -22,7 +22,9 @@ final class Show extends HttpOperation implements ShowOperationInterface
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -41,6 +43,7 @@ public function __construct(
?array $formOptions = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
string|callable|null $twigContextFactory = null,
?string $redirectToRoute = null,
?array $vars = null,
@@ -48,7 +51,9 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['GET'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'show',
name: $name,
@@ -67,6 +72,7 @@ public function __construct(
formOptions: $formOptions,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
twigContextFactory: $twigContextFactory,
redirectToRoute: $redirectToRoute,
vars: $vars,
diff --git a/src/Component/src/Metadata/Update.php b/src/Component/src/Metadata/Update.php
index 85e393690..28a09f04f 100644
--- a/src/Component/src/Metadata/Update.php
+++ b/src/Component/src/Metadata/Update.php
@@ -22,7 +22,9 @@ final class Update extends HttpOperation implements UpdateOperationInterface, St
public function __construct(
?array $methods = null,
?string $path = null,
+ ?string $routeName = null,
?string $routePrefix = null,
+ ?array $routeRequirements = null,
?string $template = null,
?string $shortName = null,
?string $name = null,
@@ -41,6 +43,7 @@ public function __construct(
?array $formOptions = null,
?array $validationContext = null,
?string $eventShortName = null,
+ ?string $notificationMessage = null,
string|callable|null $twigContextFactory = null,
?string $redirectToRoute = null,
?array $redirectArguments = null,
@@ -52,7 +55,9 @@ public function __construct(
parent::__construct(
methods: $methods ?? ['GET', 'PUT', 'POST'],
path: $path,
+ routeName: $routeName,
routePrefix: $routePrefix,
+ routeRequirements: $routeRequirements,
template: $template,
shortName: $shortName ?? 'update',
name: $name,
@@ -71,6 +76,7 @@ public function __construct(
formOptions: $formOptions,
validationContext: $validationContext,
eventShortName: $eventShortName,
+ notificationMessage: $notificationMessage,
twigContextFactory: $twigContextFactory,
redirectToRoute: $redirectToRoute,
redirectArguments: $redirectArguments,
diff --git a/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactory.php b/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactory.php
index 84ad9a2bd..9b55660e9 100644
--- a/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactory.php
+++ b/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactory.php
@@ -19,10 +19,16 @@
use Sylius\Resource\Metadata\RegistryInterface;
use Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use Sylius\Resource\Metadata\ResourceMetadata;
+use Sylius\Resource\Symfony\Routing\Factory\Resource\ResourceRouteCollectionFactory;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Webmozart\Assert\Assert;
+trigger_deprecation('sylius/resource', '1.13', '"%s" is deprecated, use "%s" instead.', AttributesOperationRouteFactory::class, ResourceRouteCollectionFactory::class);
+
+/**
+ * @deprecated
+ */
final class AttributesOperationRouteFactory implements AttributesOperationRouteFactoryInterface
{
public function __construct(
diff --git a/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactoryInterface.php b/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactoryInterface.php
index e950865df..89745f305 100644
--- a/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactoryInterface.php
+++ b/src/Component/src/Symfony/Routing/Factory/AttributesOperationRouteFactoryInterface.php
@@ -13,8 +13,14 @@
namespace Sylius\Resource\Symfony\Routing\Factory;
+use Sylius\Resource\Symfony\Routing\Factory\Resource\ResourceRouteCollectionFactoryInterface;
use Symfony\Component\Routing\RouteCollection;
+trigger_deprecation('sylius/resource', '1.13', '"%s" is deprecated, use "%s" instead.', AttributesOperationRouteFactoryInterface::class, ResourceRouteCollectionFactoryInterface::class);
+
+/**
+ * @deprecated
+ */
interface AttributesOperationRouteFactoryInterface
{
/** @psalm-param class-string $className */
diff --git a/src/Component/src/Symfony/Routing/Factory/OperationRouteFactory.php b/src/Component/src/Symfony/Routing/Factory/OperationRouteFactory.php
index 88c09cd4a..15b790574 100644
--- a/src/Component/src/Symfony/Routing/Factory/OperationRouteFactory.php
+++ b/src/Component/src/Symfony/Routing/Factory/OperationRouteFactory.php
@@ -44,6 +44,7 @@ public function create(MetadataInterface $metadata, ResourceMetadata $resource,
'_controller' => 'sylius.main_controller',
'_sylius' => $this->getSyliusOptions($resource, $operation),
],
+ requirements: $operation->getRouteRequirements() ?? [],
methods: $operation->getMethods() ?? [],
);
}
diff --git a/src/Component/src/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactory.php b/src/Component/src/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactory.php
new file mode 100644
index 000000000..ce4319685
--- /dev/null
+++ b/src/Component/src/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactory.php
@@ -0,0 +1,79 @@
+resourceMetadataFactory->create($className);
+
+ /** @var ResourceMetadata $resource */
+ foreach ($resourceMetadataCollection->getIterator() as $resource) {
+ $this->createRoutesForResource($routeCollection, $resource);
+ }
+
+ return $routeCollection;
+ }
+
+ private function createRoutesForResource(RouteCollection $routeCollection, ResourceMetadata $resource): void
+ {
+ foreach ($resource->getOperations() ?? new Operations() as $operation) {
+ if (!$operation instanceof HttpOperation) {
+ continue;
+ }
+
+ $this->addRouteForOperation($routeCollection, $resource, $operation);
+ }
+ }
+
+ private function addRouteForOperation(RouteCollection $routeCollection, ResourceMetadata $resource, HttpOperation $operation): void
+ {
+ $metadata = $this->resourceRegistry->get($resource->getAlias() ?? '');
+ $routeName = $operation->getRouteName();
+
+ Assert::notNull($routeName, sprintf('Operation %s has no route name. Please define one.', $operation::class));
+
+ $route = $this->createRoute($metadata, $resource, $operation);
+ $routeCollection->add($routeName, $route);
+ }
+
+ private function createRoute(MetadataInterface $metadata, ResourceMetadata $resource, HttpOperation $operation): Route
+ {
+ return $this->operationRouteFactory->create($metadata, $resource, $operation);
+ }
+}
diff --git a/src/Component/src/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactoryInterface.php b/src/Component/src/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactoryInterface.php
new file mode 100644
index 000000000..e1a44e682
--- /dev/null
+++ b/src/Component/src/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactoryInterface.php
@@ -0,0 +1,25 @@
+resourceNameCollectionFactory->create();
+
+ /**
+ * @var class-string $resourceName
+ */
+ foreach ($resourceNames as $resourceName) {
+ $routeCollection->addCollection($this->resourceRouteCollectionFactory->createRouteCollectionForClass($resourceName));
+ }
+
+ return $routeCollection;
+ }
+}
diff --git a/src/Component/src/Symfony/Session/Flash/FlashHelper.php b/src/Component/src/Symfony/Session/Flash/FlashHelper.php
index ca3698b67..770608fb9 100644
--- a/src/Component/src/Symfony/Session/Flash/FlashHelper.php
+++ b/src/Component/src/Symfony/Session/Flash/FlashHelper.php
@@ -74,7 +74,7 @@ private function buildOperationMessage(Operation $operation, string $type): stri
$resource = $operation->getResource();
Assert::notNull($resource);
- $key = sprintf('%s.%s.%s', $resource->getApplicationName() ?? '', $resource->getName() ?? '', $operation->getShortName() ?? '');
+ $key = $operation->getNotificationMessage() ?? sprintf('%s.%s.%s', $resource->getApplicationName() ?? '', $resource->getName() ?? '', $operation->getShortName() ?? '');
$fallbackKey = sprintf('sylius.resource.%s', $operation->getShortName() ?? '');
$parameters = $this->getTranslationParameters($operation);
diff --git a/src/Component/tests/Doctrine/ORM/Metadata/Resource/Factory/DoctrineORMResourceMetadataCollectionFactoryTest.php b/src/Component/tests/Doctrine/ORM/Metadata/Resource/Factory/DoctrineORMResourceMetadataCollectionFactoryTest.php
new file mode 100644
index 000000000..a760f8cd5
--- /dev/null
+++ b/src/Component/tests/Doctrine/ORM/Metadata/Resource/Factory/DoctrineORMResourceMetadataCollectionFactoryTest.php
@@ -0,0 +1,123 @@
+managerRegistry = $this->prophesize(ManagerRegistry::class);
+ $this->decorated = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
+
+ $this->factory = new DoctrineORMResourceMetadataCollectionFactory(
+ $this->managerRegistry->reveal(),
+ $this->decorated->reveal(),
+ );
+ }
+
+ public function testItIsInitializable(): void
+ {
+ $this->assertInstanceOf(DoctrineORMResourceMetadataCollectionFactory::class, $this->factory);
+ }
+
+ public function testItAddsPersistProcessorToOperationsForResourceManagedByDoctrineOrm(): void
+ {
+ $entityManager = $this->createMock(EntityManagerInterface::class);
+
+ $operation = new Create(name: 'app_dummy_create');
+ $resource = (new ResourceMetadata(alias: 'app.dummy'))
+ ->withOperations(new Operations([$operation]))
+ ->withClass('App\Dummy')
+ ;
+
+ $resourceMetadataCollection = new ResourceMetadataCollection([$resource]);
+
+ $this->decorated->create('App\Resource')->willReturn($resourceMetadataCollection);
+ $this->managerRegistry->getManagerForClass('App\Dummy')->willReturn($entityManager);
+
+ $result = $this->factory->create('App\Resource');
+
+ $this->assertEquals(
+ PersistProcessor::class,
+ $result->getOperation('app.dummy', 'app_dummy_create')->getProcessor(),
+ );
+ }
+
+ public function testItAddsRemoveProcessorToDeleteOperationsForResourceManagedByDoctrineOrm(): void
+ {
+ $entityManager = $this->createMock(EntityManagerInterface::class);
+
+ $operation = new Delete(name: 'app_dummy_delete');
+ $resource = (new ResourceMetadata(alias: 'app.dummy'))
+ ->withOperations(new Operations([$operation]))
+ ->withClass('App\Dummy')
+ ;
+
+ $resourceMetadataCollection = new ResourceMetadataCollection([$resource]);
+
+ $this->decorated->create('App\Resource')->willReturn($resourceMetadataCollection);
+ $this->managerRegistry->getManagerForClass('App\Dummy')->willReturn($entityManager);
+
+ $result = $this->factory->create('App\Resource');
+
+ $this->assertEquals(
+ RemoveProcessor::class,
+ $result->getOperation('app.dummy', 'app_dummy_delete')->getProcessor(),
+ );
+ }
+
+ public function testItDoesNothingWhenResourceIsNotManagedByDoctrineOrm(): void
+ {
+ $operation = new Create(name: 'app_dummy_create');
+ $resource = (new ResourceMetadata(alias: 'app.dummy'))
+ ->withOperations(new Operations([$operation]))
+ ->withClass('App\Dummy')
+ ;
+
+ $resourceMetadataCollection = new ResourceMetadataCollection([$resource]);
+
+ $this->decorated->create('App\Resource')->willReturn($resourceMetadataCollection);
+ $this->managerRegistry->getManagerForClass('App\Dummy')->willReturn(null);
+
+ $result = $this->factory->create('App\Resource');
+
+ $this->assertEquals(
+ null,
+ $result->getOperation('app.dummy', 'app_dummy_create')->getProcessor(),
+ );
+ }
+}
diff --git a/src/Component/tests/Doctrine/ORM/State/ProviderTest.php b/src/Component/tests/Doctrine/ORM/State/ProviderTest.php
new file mode 100644
index 000000000..4e39b5f4f
--- /dev/null
+++ b/src/Component/tests/Doctrine/ORM/State/ProviderTest.php
@@ -0,0 +1,116 @@
+managerRegistry = $this->createMock(ManagerRegistry::class);
+ $this->locator = $this->createMock(ContainerInterface::class);
+ $this->argumentParser = $this->createMock(ArgumentParserInterface::class);
+ $this->provider = new Provider($this->managerRegistry, new RepositoryArgumentResolver(), $this->argumentParser, $this->locator);
+ }
+
+ public function testItCallsRepositoryFromDoctrineManagerRegistry(): void
+ {
+ $operation = $this->createMock(Operation::class);
+ $entityManager = $this->createMock(EntityManagerInterface::class);
+ $unitOfWork = $this->createMock(UnitOfWork::class);
+ $entityPersister = $this->createMock(EntityPersister::class);
+
+ $operation->method('getRepository')->willReturn(null);
+ $operation->method('getRepositoryArguments')->willReturn(null);
+ $operation->method('getResource')->willReturn((new ResourceMetadata())->withClass('App\Dummy'));
+
+ $this->managerRegistry->method('getManagerForClass')->with('App\Dummy')->willReturn($entityManager);
+ $entityRepository = new EntityRepository($entityManager, new ClassMetadata('App\Dummy'));
+ $entityManager->method('getRepository')->willReturn($entityRepository);
+
+ $entityManager->method('getUnitOfWork')->willReturn($unitOfWork);
+ $unitOfWork->method('getEntityPersister')->willReturn($entityPersister);
+
+ $expectedResult = (object) ['id' => 'my_id'];
+ $entityPersister->method('load')->with(['id' => 'my_id'], null, null, [], null, 1)->willReturn($expectedResult);
+
+ $request = new Request([], [], ['_route_params' => ['id' => 'my_id']]);
+
+ $response = $this->provider->provide($operation, new Context(new RequestOption($request)));
+ $this->assertEquals($expectedResult, $response);
+ }
+
+ public function testItCallsRepositoryAsCallable(): void
+ {
+ $operation = $this->createMock(Operation::class);
+ $operation->method('getRepository')->willReturn([RepositoryWithCallables::class, 'find']);
+ $operation->method('getRepositoryArguments')->willReturn(null);
+
+ $request = new Request([], [], ['_route_params' => ['id' => 'my_id']]);
+
+ $response = $this->provider->provide($operation, new Context(new RequestOption($request)));
+ $this->assertInstanceOf(\stdClass::class, $response);
+ $this->assertEquals('my_id', $response->id);
+ }
+
+ public function testItCallsRepositoryAsString(): void
+ {
+ $operation = $this->createMock(Operation::class);
+ $operation->method('getRepository')->willReturn('App\\Repository');
+ $operation->method('getRepositoryMethod')->willReturn(null);
+ $operation->method('getRepositoryArguments')->willReturn(null);
+
+ $request = new Request([], [], ['_route_params' => ['id' => 'my_id', '_sylius' => ['resource' => 'app.dummy']]]);
+
+ $repository = $this->createMock(RepositoryInterface::class);
+ $stdClass = new \stdClass();
+
+ $this->locator->method('has')->with('App\\Repository')->willReturn(true);
+ $this->locator->method('get')->with('App\\Repository')->willReturn($repository);
+ $repository->method('findOneBy')->with(['id' => 'my_id'])->willReturn($stdClass);
+
+ $response = $this->provider->provide($operation, new Context(new RequestOption($request)));
+ $this->assertSame($stdClass, $response);
+ }
+}
diff --git a/src/Component/tests/Dummy/DummyResource.php b/src/Component/tests/Dummy/DummyResource.php
new file mode 100644
index 000000000..2e039f637
--- /dev/null
+++ b/src/Component/tests/Dummy/DummyResource.php
@@ -0,0 +1,21 @@
+assertInstanceOf(Index::class, $operation);
$this->assertSame('app_order_index', $operation->getName());
$this->assertSame(['GET'], $operation->getMethods());
- $this->assertSame('app.repository.order', $operation->getRepository());
$this->assertSame('App\Form\OrderType', $operation->getFormType());
$operation = $metadataCollection->getOperation('app.cart', 'app_cart_index');
$this->assertInstanceOf(Index::class, $operation);
$this->assertSame('app_cart_index', $operation->getName());
$this->assertSame(['GET'], $operation->getMethods());
- $this->assertSame('app.repository.cart', $operation->getRepository());
$this->assertSame('App\Form\CartType', $operation->getFormType());
$operation = $metadataCollection->getOperation('app.cart', 'app_cart_show');
$this->assertInstanceOf(Show::class, $operation);
$this->assertSame('app_cart_show', $operation->getName());
$this->assertSame(['GET'], $operation->getMethods());
- $this->assertSame('app.repository.cart', $operation->getRepository());
$this->assertSame('App\Form\CartType', $operation->getFormType());
}
diff --git a/src/Component/tests/Metadata/Resource/Factory/AttributesResourceNameCollectionFactoryTest.php b/src/Component/tests/Metadata/Resource/Factory/AttributesResourceNameCollectionFactoryTest.php
new file mode 100644
index 000000000..151110b83
--- /dev/null
+++ b/src/Component/tests/Metadata/Resource/Factory/AttributesResourceNameCollectionFactoryTest.php
@@ -0,0 +1,34 @@
+ [dirname(__DIR__, 3) . '/Dummy']],
+ );
+
+ $collection = $attributesResourceNameCollectionFactory->create();
+
+ $this->assertContains(DummyResource::class, $collection->getIterator());
+ $this->assertNotContains(PullRequest::class, $collection->getIterator());
+ }
+}
diff --git a/src/Component/tests/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactoryTest.php b/src/Component/tests/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactoryTest.php
index ff76e9c57..6e777f28c 100644
--- a/src/Component/tests/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactoryTest.php
+++ b/src/Component/tests/Metadata/Resource/Factory/ProviderResourceMetadataCollectionFactoryTest.php
@@ -23,7 +23,6 @@
use Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use Sylius\Resource\Metadata\Resource\ResourceMetadataCollection;
use Sylius\Resource\Metadata\ResourceMetadata;
-use Sylius\Resource\Symfony\Request\State\Provider;
final class ProviderResourceMetadataCollectionFactoryTest extends TestCase
{
@@ -44,27 +43,6 @@ public function testItIsInitializable(): void
$this->assertInstanceOf(ProviderResourceMetadataCollectionFactory::class, $this->factory);
}
- public function testItCreatesResourceMetadataWithDefaultProviderOnHttpOperations(): void
- {
- $resource = new ResourceMetadata(alias: 'app.book', name: 'book', applicationName: 'app');
-
- $index = (new Index(name: 'app_book_index'))->withResource($resource);
-
- $resource = $resource->withOperations(new Operations([
- $index->getName() => $index,
- ]));
-
- $resourceMetadataCollection = new ResourceMetadataCollection();
- $resourceMetadataCollection[] = $resource;
-
- $this->decorated->create('App\Resource')->willReturn($resourceMetadataCollection);
-
- $resourceMetadataCollection = $this->factory->create('App\Resource');
-
- $index = $resourceMetadataCollection->getOperation('app.book', 'app_book_index');
- $this->assertSame(Provider::class, $index->getProvider());
- }
-
public function testItConfiguresRequestGridProviderIfOperationHasAGrid(): void
{
$resource = new ResourceMetadata(alias: 'app.book', name: 'book', applicationName: 'app');
diff --git a/src/Component/tests/Metadata/Resource/ResourceNameCollectionTest.php b/src/Component/tests/Metadata/Resource/ResourceNameCollectionTest.php
new file mode 100644
index 000000000..c482e6557
--- /dev/null
+++ b/src/Component/tests/Metadata/Resource/ResourceNameCollectionTest.php
@@ -0,0 +1,43 @@
+assertInstanceOf(\IteratorAggregate::class, $collection);
+ }
+
+ public function testItIsCountable(): void
+ {
+ $collection = new ResourceNameCollection();
+
+ $this->assertInstanceOf(\Countable::class, $collection);
+ }
+
+ public function testItIsACollectionOfResourceNames(): void
+ {
+ $collection = new ResourceNameCollection(['first_resource', 'second_resource']);
+
+ $this->assertCount(2, $collection);
+ $this->assertEquals('first_resource', $collection->getIterator()[0]);
+ $this->assertEquals('second_resource', $collection->getIterator()[1]);
+ }
+}
diff --git a/src/Component/tests/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactoryTest.php b/src/Component/tests/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactoryTest.php
new file mode 100644
index 000000000..a7768a413
--- /dev/null
+++ b/src/Component/tests/Symfony/Routing/Factory/Resource/ResourceRouteCollectionFactoryTest.php
@@ -0,0 +1,71 @@
+resourceRegistry = $this->createMock(RegistryInterface::class);
+ $this->routePathFactory = $this->createMock(OperationRoutePathFactoryInterface::class);
+
+ $this->factory = new ResourceRouteCollectionFactory(
+ new OperationRouteFactory($this->routePathFactory),
+ new AttributesResourceMetadataCollectionFactory(
+ $this->resourceRegistry,
+ new OperationRouteNameFactory(),
+ ),
+ $this->resourceRegistry,
+ );
+ }
+
+ public function testItCreatesRoutesWithOperations(): void
+ {
+ $metadata = $this->createMock(MetadataInterface::class);
+ $metadata->method('getServiceId')->with('repository')->willReturn('app.repository.dummy');
+ $metadata->method('getClass')->willReturnMap([
+ ['form', 'App\Form'],
+ ['model', 'App\Dummy'],
+ ]);
+ $metadata->method('getApplicationName')->willReturn('app');
+ $metadata->method('getName')->willReturn('dummy');
+ $metadata->method('getPluralName')->willReturn('dummies');
+
+ $this->resourceRegistry->method('get')->with('app.dummy')->willReturn($metadata);
+
+ $routeCollection = $this->factory->createRouteCollectionForClass(DummyResourceWithOperations::class);
+
+ $this->assertEquals(4, $routeCollection->count());
+ $this->assertNotNull($routeCollection->get('app_dummy_index'), 'Route "app_dummy_index" not found but it should.');
+ $this->assertNotNull($routeCollection->get('app_dummy_create'), 'Route "app_dummy_create" not found but it should.');
+ $this->assertNotNull($routeCollection->get('app_dummy_update'), 'Route "app_dummy_update" not found but it should.');
+ $this->assertNotNull($routeCollection->get('app_dummy_show'), 'Route "app_dummy_show" not found but it should.');
+ }
+}
diff --git a/src/Component/tests/Symfony/Routing/Loader/ResourceLoaderTest.php b/src/Component/tests/Symfony/Routing/Loader/ResourceLoaderTest.php
new file mode 100644
index 000000000..3846a2c97
--- /dev/null
+++ b/src/Component/tests/Symfony/Routing/Loader/ResourceLoaderTest.php
@@ -0,0 +1,62 @@
+resourceNameCollectionFactory = $this->createMock(ResourceNameCollectionFactoryInterface::class);
+ $this->resourceRouteCollectionFactory = $this->createMock(ResourceRouteCollectionFactoryInterface::class);
+
+ $this->loader = new ResourceLoader(
+ $this->resourceNameCollectionFactory,
+ $this->resourceRouteCollectionFactory,
+ );
+ }
+
+ public function testItIsARouteLoader(): void
+ {
+ $this->assertInstanceOf(RouteLoaderInterface::class, $this->loader);
+ }
+
+ public function testItGeneratesRoutesFromResource(): void
+ {
+ $routeCollection = new RouteCollection();
+ $routeCollection->add('first_route', new Route('/first-route'));
+ $routeCollection->add('second_route', new Route('/second-route'));
+
+ $resourceNameCollection = new ResourceNameCollection(['\DummyClass']);
+
+ $this->resourceNameCollectionFactory->method('create')->willReturn($resourceNameCollection);
+ $this->resourceRouteCollectionFactory->method('createRouteCollectionForClass')->with('\DummyClass')->willReturn($routeCollection);
+
+ $this->assertEquals($routeCollection, ($this->loader)());
+ }
+}
diff --git a/tests/Application/config/packages/doctrine.yaml b/tests/Application/config/packages/doctrine.yaml
index 5c208b673..1ab04b5e0 100644
--- a/tests/Application/config/packages/doctrine.yaml
+++ b/tests/Application/config/packages/doctrine.yaml
@@ -19,6 +19,11 @@ doctrine:
type: attribute
dir: '%kernel.project_dir%/src/BoardGameBlog/Domain'
prefix: 'App\BoardGameBlog\Domain'
+ Conference:
+ is_bundle: false
+ type: attribute
+ dir: '%kernel.project_dir%/src/Conference/Entity'
+ prefix: 'App\Conference\Entity'
Subscription:
is_bundle: false
type: attribute
diff --git a/tests/Application/config/routes/sylius_resource.yaml b/tests/Application/config/routes/sylius_resource.yaml
index 0c8ade2c2..04dfe3569 100644
--- a/tests/Application/config/routes/sylius_resource.yaml
+++ b/tests/Application/config/routes/sylius_resource.yaml
@@ -1,7 +1,11 @@
sylius_crud_routes:
resource: 'sylius.routing.loader.crud_routes_attributes'
type: service
+#
+#sylius_routes:
+# resource: 'sylius.routing.loader.routes_attributes'
+# type: service
-sylius_routes:
- resource: 'sylius.routing.loader.routes_attributes'
+sylius_resource_routes:
+ resource: 'sylius_resource.symfony.routing.loader.resource'
type: service
diff --git a/tests/Application/config/services.yaml b/tests/Application/config/services.yaml
index e688256a7..b41a36298 100644
--- a/tests/Application/config/services.yaml
+++ b/tests/Application/config/services.yaml
@@ -99,9 +99,6 @@ services:
App\Subscription\:
resource: '../src/Subscription'
- App\Subscription\Factory\SubscriptionFactory:
- decorates: 'app.factory.subscription'
-
app.service.legacy_autowired_repository:
class: App\Service\LegacyAutowiredRepositoryService
autowire: true
diff --git a/tests/Application/config/sylius/resources.yaml b/tests/Application/config/sylius/resources.yaml
index e4f291993..08b41c935 100644
--- a/tests/Application/config/sylius/resources.yaml
+++ b/tests/Application/config/sylius/resources.yaml
@@ -1,5 +1,7 @@
sylius_resource:
mapping:
+ imports:
+ - '%kernel.project_dir%/config/sylius/resources'
paths:
- '%kernel.project_dir%/src/BoardGameBlog/Infrastructure/Sylius/Resource'
- '%kernel.project_dir%/src/Subscription/Entity'
@@ -61,3 +63,7 @@ sylius_resource:
classes:
model: App\Entity\Zone\ZoneMember
interface: App\Entity\Zone\ZoneMemberInterface
+
+ app.speaker:
+ classes:
+ model: App\Conference\Entity\Speaker
diff --git a/tests/Application/config/sylius/resources/custom_speaker_create.php b/tests/Application/config/sylius/resources/custom_speaker_create.php
new file mode 100644
index 000000000..c075807e5
--- /dev/null
+++ b/tests/Application/config/sylius/resources/custom_speaker_create.php
@@ -0,0 +1,26 @@
+getName()
+ ) {
+ return $operation;
+ }
+
+ return $operation->withPath('speakers/register');
+};
diff --git a/tests/Application/config/sylius/resources/speaker.php b/tests/Application/config/sylius/resources/speaker.php
new file mode 100644
index 000000000..660c1316d
--- /dev/null
+++ b/tests/Application/config/sylius/resources/speaker.php
@@ -0,0 +1,29 @@
+withRoutePrefix('/admin')
+ ->withClass(Speaker::class)
+ ->withSection('admin')
+ ->withTemplatesDir('crud')
+ ->withOperations(new Operations([
+ new Create(),
+ new Index(),
+ ]))
+;
diff --git a/tests/Application/config/sylius/resources/speaker_update.php b/tests/Application/config/sylius/resources/speaker_update.php
new file mode 100644
index 000000000..5743ce6bd
--- /dev/null
+++ b/tests/Application/config/sylius/resources/speaker_update.php
@@ -0,0 +1,27 @@
+withRoutePrefix('/admin')
+ ->withClass(Speaker::class)
+ ->withSection('admin')
+ ->withTemplatesDir('crud')
+ ->withOperations(new Operations([
+ new Update(),
+ ]))
+;
diff --git a/tests/Application/src/Conference/Entity/Speaker.php b/tests/Application/src/Conference/Entity/Speaker.php
new file mode 100644
index 000000000..3be181e85
--- /dev/null
+++ b/tests/Application/src/Conference/Entity/Speaker.php
@@ -0,0 +1,41 @@
+id;
+ }
+}
diff --git a/tests/Application/src/Subscription/Entity/Subscription.php b/tests/Application/src/Subscription/Entity/Subscription.php
index 1e2aa6e50..e721ece8e 100644
--- a/tests/Application/src/Subscription/Entity/Subscription.php
+++ b/tests/Application/src/Subscription/Entity/Subscription.php
@@ -13,7 +13,9 @@
namespace App\Subscription\Entity;
+use App\Subscription\Factory\SubscriptionFactory;
use App\Subscription\Form\Type\SubscriptionType;
+use App\Subscription\Repository\SubscriptionRepository;
use App\Subscription\Twig\Context\Factory\ShowSubscriptionContextFactory;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Resource\Metadata\Api;
@@ -35,9 +37,10 @@
formType: SubscriptionType::class,
templatesDir: 'crud',
routePrefix: '/admin',
+ driver: false,
)]
#[Index(grid: 'app_subscription')]
-#[Create]
+#[Create(factory: [SubscriptionFactory::class, 'createNew'])]
#[Update]
#[Delete]
#[BulkDelete]
@@ -59,6 +62,7 @@
routePrefix: '/ajax',
normalizationContext: ['groups' => 'subscription:read'],
denormalizationContext: ['groups' => 'subscription:write'],
+ driver: false,
)]
#[Api\GetCollection]
#[Api\Post]
@@ -66,7 +70,7 @@
#[Api\Delete]
#[Api\Get]
-#[ORM\Entity]
+#[ORM\Entity(repositoryClass: SubscriptionRepository::class)]
class Subscription implements ResourceInterface
{
#[ORM\Column(type: 'string')]
diff --git a/tests/Application/src/Subscription/Factory/SubscriptionFactory.php b/tests/Application/src/Subscription/Factory/SubscriptionFactory.php
index 01c35ffb1..230c3a7c1 100644
--- a/tests/Application/src/Subscription/Factory/SubscriptionFactory.php
+++ b/tests/Application/src/Subscription/Factory/SubscriptionFactory.php
@@ -14,11 +14,10 @@
namespace App\Subscription\Factory;
use App\Subscription\Entity\Subscription;
-use Sylius\Resource\Factory\FactoryInterface;
-final class SubscriptionFactory implements FactoryInterface
+final class SubscriptionFactory
{
- public function createNew(): Subscription
+ public static function createNew(): Subscription
{
return new Subscription(email: 'new@example.com');
}
diff --git a/tests/Application/src/Subscription/Repository/SubscriptionRepository.php b/tests/Application/src/Subscription/Repository/SubscriptionRepository.php
new file mode 100644
index 000000000..ca5b9ed45
--- /dev/null
+++ b/tests/Application/src/Subscription/Repository/SubscriptionRepository.php
@@ -0,0 +1,29 @@
+assertResponseRedirects(null, expectedCode: Response::HTTP_FOUND);
- /** @var Subscription $subscription */
- $subscription = static::getContainer()->get('app.repository.subscription')->findOneBy(['email' => 'biff.tannen@bttf.com']);
+ $subscription = $this->getSubscriptionRepository()->findOneBy(['email' => 'biff.tannen@bttf.com']);
$this->assertNotNull($subscription);
$this->assertSame('biff.tannen@bttf.com', (string) $subscription->email);
@@ -180,8 +181,7 @@ public function it_allows_deleting_a_subscription(): void
$this->assertResponseRedirects(null, expectedCode: Response::HTTP_FOUND);
- /** @var Subscription[] $subscriptions */
- $subscriptions = static::getContainer()->get('app.repository.subscription')->findAll();
+ $subscriptions = $this->getSubscriptionRepository()->findAll();
$this->assertEmpty($subscriptions);
}
@@ -196,8 +196,7 @@ public function it_allows_deleting_multiple_subscriptions(): void
$this->assertResponseRedirects(null, expectedCode: Response::HTTP_FOUND);
- /** @var Subscription[] $subscriptions */
- $subscriptions = static::getContainer()->get('app.repository.subscription')->findAll();
+ $subscriptions = $this->getSubscriptionRepository()->findAll();
$this->assertEmpty($subscriptions);
}
@@ -240,4 +239,12 @@ protected function buildMatcher(): Matcher
{
return $this->matcherFactory->createMatcher(new VoidBacktrace());
}
+
+ /**
+ * @return ObjectRepository
+ */
+ private function getSubscriptionRepository(): ObjectRepository
+ {
+ return static::getContainer()->get(EntityManagerInterface::class)->getRepository(Subscription::class);
+ }
}
diff --git a/tests/Bundle/Command/DebugResourceCommandTest.php b/tests/Bundle/Command/DebugResourceCommandTest.php
index 618c684c4..aaeb12b8d 100644
--- a/tests/Bundle/Command/DebugResourceCommandTest.php
+++ b/tests/Bundle/Command/DebugResourceCommandTest.php
@@ -283,6 +283,7 @@ public function it_displays_the_metadata_for_given_resource_operation(): void
path null
routeName null
routePrefix null
+ routeRequirements null
redirectToRoute null
redirectArguments null
vars [
@@ -308,6 +309,7 @@ public function it_displays_the_metadata_for_given_resource_operation(): void
denormalizationContext null
validationContext null
eventShortName "register"
+ notificationMessage null
------------------------ --------------------------
diff --git a/tests/Bundle/Configuration/ConfigurationTest.php b/tests/Bundle/Configuration/ConfigurationTest.php
index 54865c590..0ba04f7ed 100644
--- a/tests/Bundle/Configuration/ConfigurationTest.php
+++ b/tests/Bundle/Configuration/ConfigurationTest.php
@@ -72,6 +72,7 @@ public function it_has_no_default_mapping_paths(): void
[
'mapping' => [
'paths' => [],
+ 'imports' => [],
],
],
'mapping',
@@ -92,6 +93,7 @@ public function its_mapping_paths_can_be_customized(): void
'paths' => [
'path/to/resources',
],
+ 'imports' => [],
],
],
'mapping',
diff --git a/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php b/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php
index 11a4e2c50..021631cc4 100644
--- a/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php
+++ b/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php
@@ -126,6 +126,7 @@ public function it_registers_parameter_for_paths(): void
'paths' => [
__DIR__ . '/Dummy',
],
+ 'imports' => [],
]);
}