Skip to content

Commit 4e4a7dd

Browse files
authored
New resource loader (#997)
| Q | A | --------------- | ----- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Related tickets | | License | MIT This work have ben reviewed via multiple PRs. Sylius/SyliusResourceBundle#985 Sylius/SyliusResourceBundle#986 Sylius/SyliusResourceBundle#987 It reworks the routing system to allow more routing system than the attributes one in the near future.
2 parents 9f04449 + 855004e commit 4e4a7dd

14 files changed

Lines changed: 523 additions & 0 deletions

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"pagerfanta/core": "^3.7 || ^4.0",
3535
"symfony/event-dispatcher": "^6.4 || ^7.1",
3636
"symfony/form": "^6.4 || ^7.1",
37+
"symfony/framework-bundle": "^6.4 || ^7.1",
3738
"symfony/http-foundation": "^6.4 || ^7.1",
3839
"symfony/http-kernel": "^6.4 || ^7.1",
3940
"symfony/property-access": "^6.4 || ^7.1",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Metadata\Resource\Factory;
15+
16+
use Sylius\Resource\Metadata\AsResource;
17+
use Sylius\Resource\Metadata\Resource\ResourceClassList;
18+
use Sylius\Resource\Reflection\ClassReflection;
19+
20+
/**
21+
* Creates a resource class list from {@see AsResource} attributes.
22+
*
23+
* @experimental
24+
*/
25+
final class AttributesResourceClassListFactory implements ResourceClassListFactoryInterface
26+
{
27+
/** @param array{paths: string[]} $mapping */
28+
public function __construct(
29+
private readonly array $mapping,
30+
private readonly ?ResourceClassListFactoryInterface $decorated = null,
31+
) {
32+
}
33+
34+
/**
35+
* @inheritdoc
36+
*/
37+
public function create(): ResourceClassList
38+
{
39+
$classes = [];
40+
41+
if ($this->decorated) {
42+
foreach ($this->decorated->create() as $resourceClass) {
43+
$classes[$resourceClass] = true;
44+
}
45+
}
46+
47+
$paths = $this->mapping['paths'] ?? [];
48+
49+
foreach (ClassReflection::getResourcesByPaths($paths) as $resourceClass) {
50+
if ([] === ClassReflection::getClassAttributes($resourceClass, AsResource::class)) {
51+
continue;
52+
}
53+
54+
$classes[$resourceClass] = true;
55+
}
56+
57+
return new ResourceClassList(array_keys($classes));
58+
}
59+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Metadata\Resource\Factory;
15+
16+
use Sylius\Resource\Metadata\Resource\ResourceClassList;
17+
18+
/**
19+
* Creates a resource class list value object.
20+
*
21+
* @experimental
22+
*/
23+
interface ResourceClassListFactoryInterface
24+
{
25+
/**
26+
* Creates the resource class list.
27+
*/
28+
public function create(): ResourceClassList;
29+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Metadata\Resource;
15+
16+
/**
17+
* A list of resource class names.
18+
*
19+
* @experimental
20+
*/
21+
final class ResourceClassList implements \IteratorAggregate, \Countable
22+
{
23+
/**
24+
* @param string[] $classes
25+
*/
26+
public function __construct(private readonly array $classes = [])
27+
{
28+
}
29+
30+
/**
31+
* @return \Traversable<string>
32+
*/
33+
public function getIterator(): \Traversable
34+
{
35+
return new \ArrayIterator($this->classes);
36+
}
37+
38+
public function count(): int
39+
{
40+
return \count($this->classes);
41+
}
42+
}

src/Symfony/Routing/Factory/AttributesOperationRouteFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
use Sylius\Resource\Metadata\RegistryInterface;
2020
use Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2121
use Sylius\Resource\Metadata\ResourceMetadata;
22+
use Sylius\Resource\Symfony\Routing\Factory\Resource\ResourceRouteCollectionFactory;
2223
use Symfony\Component\Routing\Route;
2324
use Symfony\Component\Routing\RouteCollection;
2425
use Webmozart\Assert\Assert;
2526

27+
/**
28+
* @deprecated use ResourceRouteCollectionFactory instead
29+
*/
2630
final class AttributesOperationRouteFactory implements AttributesOperationRouteFactoryInterface
2731
{
2832
public function __construct(

src/Symfony/Routing/Factory/AttributesOperationRouteFactoryInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
use Symfony\Component\Routing\RouteCollection;
1717

18+
/**
19+
* @deprecated use Sylius\Resource\Symfony\Routing\Factory\Resource\ResourceRouteCollectionFactoryInterface instead
20+
*/
1821
interface AttributesOperationRouteFactoryInterface
1922
{
2023
/** @psalm-param class-string $className */
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Symfony\Routing\Factory\Resource;
15+
16+
use Sylius\Resource\Metadata\HttpOperation;
17+
use Sylius\Resource\Metadata\MetadataInterface;
18+
use Sylius\Resource\Metadata\Operations;
19+
use Sylius\Resource\Metadata\RegistryInterface;
20+
use Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
21+
use Sylius\Resource\Metadata\ResourceMetadata;
22+
use Sylius\Resource\Symfony\Routing\Factory\OperationRouteFactoryInterface;
23+
use Symfony\Component\Routing\Route;
24+
use Symfony\Component\Routing\RouteCollection;
25+
use Webmozart\Assert\Assert;
26+
27+
/**
28+
* @experimental
29+
*/
30+
final class ResourceRouteCollectionFactory implements ResourceRouteCollectionFactoryInterface
31+
{
32+
public function __construct(
33+
private readonly OperationRouteFactoryInterface $operationRouteFactory,
34+
private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory,
35+
private readonly RegistryInterface $resourceRegistry,
36+
) {
37+
}
38+
39+
public function createRouteCollectionForClass(string $className): RouteCollection
40+
{
41+
$routeCollection = new RouteCollection();
42+
$resourceMetadata = $this->resourceMetadataFactory->create($className);
43+
44+
/** @var ResourceMetadata $resource */
45+
foreach ($resourceMetadata->getIterator() as $resource) {
46+
$this->createRoutesForResource($routeCollection, $resource);
47+
}
48+
49+
return $routeCollection;
50+
}
51+
52+
private function createRoutesForResource(RouteCollection $routeCollection, ResourceMetadata $resource): void
53+
{
54+
foreach ($resource->getOperations() ?? new Operations() as $operation) {
55+
if (!$operation instanceof HttpOperation) {
56+
continue;
57+
}
58+
59+
$this->addRouteForOperation($routeCollection, $resource, $operation);
60+
}
61+
}
62+
63+
private function addRouteForOperation(RouteCollection $routeCollection, ResourceMetadata $resource, HttpOperation $operation): void
64+
{
65+
$alias = $resource->getAlias();
66+
Assert::notNull($alias, sprintf('Resource of %s has no alias.', $resource->getClass() ?? ''));
67+
68+
$metadata = $this->resourceRegistry->get($alias);
69+
$routeName = $operation->getRouteName();
70+
71+
Assert::notNull($routeName, sprintf(
72+
'Operation %s of %s has no route name. Please define one.',
73+
$operation::class,
74+
$alias,
75+
));
76+
77+
$route = $this->createRoute($metadata, $resource, $operation);
78+
$routeCollection->add($routeName, $route);
79+
}
80+
81+
private function createRoute(MetadataInterface $metadata, ResourceMetadata $resource, HttpOperation $operation): Route
82+
{
83+
return $this->operationRouteFactory->create($metadata, $resource, $operation);
84+
}
85+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Symfony\Routing\Factory\Resource;
15+
16+
use Symfony\Component\Routing\RouteCollection;
17+
18+
/**
19+
* @experimental
20+
*/
21+
interface ResourceRouteCollectionFactoryInterface
22+
{
23+
/** @param class-string $className */
24+
public function createRouteCollectionForClass(string $className): RouteCollection;
25+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Symfony\Routing\Loader;
15+
16+
use Sylius\Resource\Metadata\Resource\Factory\ResourceClassListFactoryInterface;
17+
use Sylius\Resource\Symfony\Routing\Factory\Resource\ResourceRouteCollectionFactoryInterface;
18+
use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface;
19+
use Symfony\Component\Routing\RouteCollection;
20+
21+
/**
22+
* @experimental
23+
*/
24+
final class ResourceLoader implements RouteLoaderInterface
25+
{
26+
public function __construct(
27+
private readonly ResourceClassListFactoryInterface $resourceClassListFactory,
28+
private readonly ResourceRouteCollectionFactoryInterface $resourceRouteCollectionFactory,
29+
) {
30+
}
31+
32+
public function __invoke(): RouteCollection
33+
{
34+
$routeCollection = new RouteCollection();
35+
$resourceClasses = $this->resourceClassListFactory->create();
36+
37+
/** @var class-string $class */
38+
foreach ($resourceClasses as $class) {
39+
$routeCollection->addCollection($this->resourceRouteCollectionFactory->createRouteCollectionForClass($class));
40+
}
41+
42+
return $routeCollection;
43+
}
44+
}

tests/Dummy/DummyResource.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Tests\Dummy;
15+
16+
use Sylius\Resource\Metadata\AsResource;
17+
18+
#[AsResource]
19+
class DummyResource
20+
{
21+
}

0 commit comments

Comments
 (0)