8000 PoC PHP file resource metadata factory · Sylius/SyliusResourceBundle@d90f4ee · GitHub
[go: up one dir, main page]

Skip to content

Commit d90f4ee

Browse files
committed
PoC PHP file resource metadata factory
1 parent 69d39f1 commit d90f4ee

File tree

17 files changed

+443
-4
lines changed

17 files changed

+443
-4
lines changed

psalm.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@
116116
<file name="src/Bundle/Grid/Parser/OptionsParser.php" />
117117
</errorLevel>
118118
</InvalidReturnStatement>
119+
120+
<InvalidNullableReturnType>
121+
<errorLevel type="suppress">
122+
<file name="src/Component/src/Metadata/Extractor/PhpFileMetadataExtractor.php" />
123+
</errorLevel>
124+
</InvalidNullableReturnType>
119125

120126
<InvalidReturnType>
121127
<errorLevel type="suppress">
@@ -179,6 +185,12 @@
179185
</errorLevel>
180186
</NullArgument>
181187

188+
<NullableReturnStatement>
189+
<errorLevel type="suppress">
190+
<file name="src/Component/src/Metadata/Extractor/PhpFileMetadataExtractor.php" />
191+
</errorLevel>
192+
</NullableReturnStatement>
193+
182194
<PossiblyFalseOperand>
183195
<errorLevel type="suppress">
184196
<file name="src/Component/src/Reflection/ClassInfoTrait.php" />
@@ -320,6 +332,12 @@
320332
<directory name="src/Component/legacy/src/Translation" />
321333
</errorLevel>
322334
</UnrecognizedStatement>
335+
336+
<UnresolvableInclude>
337+
<errorLevel type="suppress">
338+
<file name="src/Component/src/Metadata/Extractor/PhpFileMetadataExtractor.php" />
339+
</errorLevel>
340+
</UnresolvableInclude>
323341

324342
<UnsupportedReferenceUsage>
325343
<errorLevel type="suppress">

src/Bundle/DependencyInjection/Configuration.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public function getConfigTreeBuilder(): TreeBuilder
3939
->arrayNode('mapping')
4040
->addDefaultsIfNotSet()
4141
->children()
42+
->arrayNode('imports')
43+
->prototype('scalar')->end()
44+
->end()
4245< 10000 /td>
->arrayNode('paths')
4346
->prototype('scalar')->end()
4447
->end()

src/Bundle/Resources/config/services/metadata.xml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,29 @@
2121
<tag name="cache.pool" />
2222
</service>
2323

24+
<service id="sylius_resource.metadata.extractor.php_file" class="Sylius\Resource\Metadata\Extractor\PhpFileMetadataExtractor">
25+
<argument>%sylius.resource.mapping%</argument>
26+
<argument type="service" id="service_container" />
27+
</service>
28+
29+
<service id="sylius.resource_metadata_collection.factory" alias="sylius.resource_metadata_collection.factory.php_file" />
30+
<service id="Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface" alias="sylius.resource_metadata_collection.factory.php_file" />
31+
32+
<service id="sylius.resource_metadata_collection.factory.php_file" class="Sylius\Resource\Metadata\Resource\Factory\PhpFileResourceMetadataCollectionFactory">
33+
<argument type="service" id="sylius.resource_registry" />
34+
<argument type="service" id="sylius.routing.factory.operation_route_name_factory" />
35+
<argument type="service" id="sylius_resource.metadata.extractor.php_file" />
36+
</service>
37+
2438
<service id="sylius.resource_metadata_collection.factory.attributes"
2539
class="Sylius\Resource\Metadata\Resource\Factory\AttributesResourceMetadataCollectionFactory"
40+
decorates="sylius.resource_metadata_collection.factory"
41+
decoration-priority="400"
2642
>
2743
<argument type="service" id="sylius.resource_registry" />
2844
<argument type="service" id="sylius.routing.factory.operation_route_name_factory" />
45+
<argument type="service" id=".inner" />
2946
</service>
30-
<service id="sylius.resource_metadata_collection.factory" alias="sylius.resource_metadata_collection.factory.attributes" />
31-
<service id="Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface" alias="sylius.resource_metadata_collection.factory.attributes" />
3247

3348
<service id="sylius.resource_metadata_collection.factory.state_machine"
3449
class="Sylius\Resource\Metadata\Resource\Factory\StateMachineResourceMetadataCollectionFactory"

src/Bundle/Resources/config/services/metadata/resource_name.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,15 @@
2222
<argument>%sylius.resource.mapping%</argument>
2323
</service>
2424
<service id="Sylius\Resource\Metadata\Resource\Factory\AttributesResourceNameCollectionFactory" alias="sylius_resource.metadata.resource.name_collection.factory.attributes" />
25+
26+
<service id="sylius_resource.metadata.resource.name_collection.factory.php_file"
27+
class="Sylius\Resource\Metadata\Resource\Factory\PhpFileResourceNameCollectionFactory"
28+
decorates="sylius_resource.metadata.resource.name_collection.factory"
29+
decoration-priority="100"
30+
>
31+
<argument type="service" id="sylius_resource.metadata.extractor.php_file" />
32+
<argument type="service" id=".inner" />
33+
</service>
34+
<service id="Sylius\Resource\Metadata\Resource\Factory\PhpFileResourceMetadataCollectionFactory" alias="sylius_resource.metadata.resource.name_collection.factory.php_file" />
2535
</services>
2636
</container>

src/Bundle/Resources/config/services/state.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464

6565
<service id="Sylius\Resource\Grid\State\RequestGridProvider">
6666
<argument type="service" id="sylius.grid.view_factory.resource" on-invalid="null" />
67-
<argument type="service" id="sylius.grid.service_grid_provider" on-invalid="null" />
67+
<argument type="service" id="sylius.grid.provider" on-invalid="null" />
6868
<tag name="sylius.state_provider" />
6969
</service>
7070
</services>

src/Component/spec/Symfony/Routing/Factory/AttributesOperationRouteFactorySpec.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ function let(
4242
new AttributesResourceMetadataCollectionFactory(
4343
$resourceRegistry->getWrappedObject(),
4444
new OperationRouteNameFactory(),
45-
'symfony',
4645
),
4746
);
4847
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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\Extractor;
15+
16+
use Sylius\Resource\Metadata\ResourceMetadata;
17+
18+
interface MetadataExtractorInterface
19+
{
20+
/**
21+
* @return ResourceMetadata[]
22+
*/
23+
public function extract(): array;
24+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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\Extractor;
15+
16+
use Psr\Container\ContainerInterface;
17+
use Sylius\Resource\Metadata\ResourceMetadata;
18+
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
19+
use Symfony\Component\Finder\Finder;
20+
21+
final class PhpFileMetadataExtractor implements MetadataExtractorInterface
22+
{
23+
private array $collectedParameters = [];
24+
25+
public function __construct(
26+
private readonly array $resourceMapping,
27+
private readonly ?ContainerInterface $container = null,
28+
) {
29+
}
30+
31+
/**
32+
* @inheritDoc
33+
*/
34+
public function extract(): array
35+
{
36+
$metadata = [];
37+
38+
foreach ($this->getResourceFilePaths() as $filePath) {
39+
if (!is_readable($filePath)) {
40+
continue;
41+
}
42+
43+
$resource = $this->getPHPFileClosure($filePath)();
44+
45+
if (!$resource instanceof ResourceMetadata) {
46+
continue;
47+
}
48+
49+
$resourceReflection = new \ReflectionClass($resource);
50+
51+
foreach ($resourceReflection->getProperties() as $property) {
52+
$property->setAccessible(true);
53+
$resolvedValue = $this->resolve($property->getValue($resource));
54+
$property->setValue($resource, $resolvedValue);
55+
}
56+
57+
$metadata[] = $resource;
58+ 10000
}
59+
60+
return $metadata;
61+
}
62+
63+
private function getResourceFilePaths(): iterable
64+
{
65+
foreach ($this->createFinder() as $file) {
66+
yield $file->getPathname();
67+
}
68+
}
69+
70+
private function createFinder(): Finder
71+
{
72+
$finder = (new Finder())->files();
73+
74+
foreach ($this->resourceMapping['imports'] ?? [] as $path) {
75+
$finder->in($path);
76+
}
77+
78+
return $finder->files();
79+
}
80+
81+
/**
82+
* Scope isolated include.
83+
*
84+
* Prevents access to $this/self from included files.
85+
*/
86+
private function getPHPFileClosure(string $filePath): \Closure
87+
{
88+
return \Closure::bind(function () use ($filePath): mixed {
89+
return require $filePath;
90+
}, null, null);
91+
}
92+
93+
/**
94+
* Recursively replaces placeholders with the service container parameters.
95+
*
96+
* @see https://github.com/symfony/symfony/blob/6fec32c/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php
97+
*
98+
* @param mixed $value The source which might contain "%placeholders%"
99+
*
100+
* @throws \RuntimeException When a container value is not a string or a numeric value
101+
*
102+
* @return mixed The source with the placeholders replaced by the container
103+
* parameters. Arrays are resolved recursively.
104+
*/
105+
private function resolve(mixed $value): mixed
106+
{
107+
$container = $this->container;
108+
109+
if (null === $container) {
110+
return $value;
111+
}
112+
113+
if (\is_array($value)) {
114+
foreach ($value as $key => $val) {
115+
$value[$key] = $this->resolve($val);
116+
}
117+
118+
return $value;
119+
}
120+
121+
if (!\is_string($value)) {
122+
return $value;
123+
}
124+
125+
$escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($value, $container) {
126+
$parameter = $match[1] ?? null;
127+
128+
// skip %%
129+
if (!isset($parameter)) {
130+
return '%%';
131+
}
132+
133+
if (preg_match('/^env\(\w+\)$/', $parameter)) {
134+
throw new \RuntimeException(\sprintf('Using "%%%s%%" is not allowed in routing configuration.', $parameter));
135+
}
136+
137+
if (\array_key_exists($parameter, $this->collectedParameters)) {
138+
return $this->collectedParameters[$parameter];
139+
}
140+
141+
if ($container instanceof SymfonyContainerInterface) {
142+
$resolved = $container->getParameter($parameter);
143+
} else {
144+
$resolved = $container->get($parameter);
145+
}
146+
147+
if (\is_string($resolved) || is_numeric($resolved)) {
148+
$this->collectedParameters[$parameter] = $resolved;
149+
150+
return (string) $resolved;
151+
}
152+
153+
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)));
154+
}, $value);
155+
156+
return str_replace('%%', '%', $escapedValue ?? '');
157+
}
158+
}

src/Component/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@ final class AttributesResourceMetadataCollectionFactory implements ResourceMetad
3030
public function __construct(
3131
private RegistryInterface $resourceRegistry,
3232
private OperationRouteNameFactory $operationRouteNameFactory,
33+
private ?ResourceMetadataCollectionFactoryInterface $decorated = null,
3334
) {
3435
}
3536

3637
public function create(string $resourceClass): ResourceMetadataCollection
3738
{
3839
$resourceMetadataCollection = new ResourceMetadataCollection();
40+
if ($this->decorated) {
41+
$resourceMetadataCollection = $this->decorated->create($resourceClass);
42+
}
3943

4044
$attributes = ClassReflection::getClassAttributes($resourceClass);
4145

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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\Extractor\MetadataExtractorInterface;
17+
use Sylius\Resource\Metadata\Operation;
18+
use Sylius\Resource\Metadata\Operations;
19+
use Sylius\Resource\Metadata\RegistryInterface;
20+
use Sylius\Resource\Metadata\Resource\ResourceMetadataCollection;
21+
use Sylius\Resource\Symfony\Routing\Factory\RouteName\OperationRouteNameFactory;
22+
23+
final class PhpFileResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
24+
{
25+
use OperationDefaultsTrait;
26+
27+
public function __construct(
28+
private readonly RegistryInterface $resourceRegistry,
29+
private readonly OperationRouteNameFactory $operationRouteNameFactory,
30+
private readonly MetadataExtractorInterface $metadataExtractor,
31+
private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
32+
) {
33+
}
34+
35+
public function create(string $resourceClass): ResourceMetadataCollection
36+
{
37+
$resourceMetadataCollection = new ResourceMetadataCollection();
38+
if ($this->decorated) {
39+
$resourceMetadataCollection = $this->decorated->create($resourceClass);
40+
}
41+
42+
foreach ($this->metadataExtractor->extract() as $resource) {
43+
if ($resourceClass !== $resource->getClass()) {
44+
continue;
45+
}
46+
47+
$resourceAlias = $resource->getAlias();
48+
49+
if (null !== $resourceAlias) {
50+
$resourceConfiguration = $this->resourceRegistry->get($resource->getAlias() ?? '');
51+
} else {
52+
$resourceConfiguration = $this->resourceRegistry->getByClass($resourceClass);
53+
}
54+
55+
$resource = $this->getResourceWithDefaults($resourceClass, $resource, $resourceConfiguration);
56+
57+
$operations = [];
58+
/** @var Operation $operation */
59+
foreach ($resource->getOperations() ?? new Operations() as $operation) {
60+
[$key, $operation] = $this->getOperationWithDefaults($this->operationRouteNameFactory, $this->resourceRegistry, $resource, $operation);
61+
$operations[$key] = $operation;
62+
}
63+
64+
if ($operations) {
65+
$resource = $resource->withOperations(new Operations($operations));
66+
}
67+
68+
$resourceMetadataCollection[] = $resource;
69+
}
70+
71+
return $resourceMetadataCollection;
72+
}
73+
}

0 commit comments

Comments
 (0)
0