Skip to content

Commit

Permalink
feature #334 Sylius route with attributes (loic425)
Browse files Browse the repository at this point in the history
This PR was merged into the 1.9-dev branch.

Discussion
----------

| Q               | A
| --------------- | -----
| Bug fix?        | no
| New feature?    | yes
| BC breaks?      | no
| Deprecations?   | no
| Related tickets |
| License         | MIT

```php
<?php

declare(strict_types=1);

namespace App\Entity;

use App\Annotation\SyliusCrudRoutes;
use App\Annotation\SyliusRoute;
use Sylius\Component\Resource\Model\ResourceInterface;

#[SyliusCrudRoutes(
    alias: 'app.book',
    path: 'library',
    section: 'backend',
    redirect: 'index',
    grid: 'sylius_backend_admin_user',
    except: ['show'],
    vars: [
        'all' => [
            'subheader' => 'sylius.ui.manage_users_able_to_access_administration_panel',
        ],
        'index' => [
            'icon' => 'lock',
        ]
    ],
)]

#[SyliusRoute(
    name: 'app_backend_book_show',
    path: '/library/{id}',
    methods: ['GET'],
    controller: 'app.controller.book::indexAction',
    template: 'backend/book/show.html.twig',
    vars: [
        'subheader' => 'sylius.ui.manage_users_able_to_access_administration_panel',
    ]
)]
class Book implements ResourceInterface
{
    private ?int $id = null;

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

```

## Crud routes
- [x] CRUD routes with alias
- [x] CRUD routes with section
- [x] CRUD routes with criteria
- [x] CRUD routes with template
- [x] CRUD routes with grid
- [x] CRUD routes with vars
- [x] CRUD routes with redirect
- [x] CRUD routes with persmission
- [x] CRUD routes with except
- [x] CRUD routes with only

## Single routes
- [x] Single route with name, path and controller
- [x] Single route with methods
- [x] Single route with criteria
- [x] Single route with template
- [x] Single route with repository
- [x] Single route with serializationGroups
- [x] Single route with serializationVersion
- [x] Single route with requirements
- [x] Single route with options
- [x] Single route with host
- [x] Single route with schemes
- [x] Single route with priority
- [x] Single route with vars

Commits
-------

500410d Sylius route with attributes
35dbac9 Add Single route with template and with repository
fb80bcf Add some missing feature for single routes
23e4eae Add some other missing features for single routes
09075ca Add Phpspec tests
23f6301 Add missing headers
66f8512 Make mapping paths configurable
05ee060 Skip priority test on Symfony 4
8246dee Apply suggestions from code review
65c96aa Separate boths routing loaders
a007770 Fix Psalm and PHPStan
4bbaf6c Remove testing PHP greater than 8.0
954e199 Fix Phpspec tests
77bff28 Fix Phpspec tests for Symfony 4.4
9b061fe Add Unit tests for Class Reflection
0696b54 Skipped generating routes from resource with priority on Symfony < 5
6c8092b Add route attributes factory
  • Loading branch information
Zales0123 authored Jan 11, 2022
2 parents 72a3877 + 6c8092b commit 7ddf718
Show file tree
Hide file tree
Showing 50 changed files with 2,636 additions and 5 deletions.
6 changes: 6 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ parameters:
- %currentWorkingDirectory%/src/Component/vendor/*

ignoreErrors:
- '/Call to method getArguments\(\) on an unknown class ReflectionAttribute./'
- '/Call to an undefined method ReflectionClass::getAttributes\(\)./'
- '/Class Doctrine\\Bundle\\MongoDBBundle/'
- '/Class Doctrine\\Bundle\\PHPCRBundle/'
- '/Class Doctrine\\Common\\Persistence\\ObjectManager not found\./'
Expand All @@ -41,10 +43,14 @@ parameters:
- '/Method Sylius\\Component\\Resource\\Model\\ResourceInterface::getId\(\) has no return typehint specified./'
- '/Method Sylius\\Component\\Resource\\Model\\TimestampableInterface::setCreatedAt\(\) has no return typehint specified./'
- '/Method Sylius\\Component\\Resource\\Model\\TimestampableInterface::setUpdatedAt\(\) has no return typehint specified./'
- '/Method Symfony\\Component\\Routing\\RouteCollection::add\(\) invoked with 3 parameters, 2 required\./'
- '/Method Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface::dispatch\(\) invoked with 2 parameters, 1 required\./'
- '/Parameter \#1 \$currentPage of method Pagerfanta\\Pagerfanta<mixed>::setCurrentPage\(\) expects int<1, max>, int given\./'
- '/Parameter \#1 \$maxPerPage of method Pagerfanta\\Pagerfanta<mixed>::setMaxPerPage\(\) expects int<1, max>, int given\./'
- '/Parameter \#1 \$array[0-9]? of function array_multisort expects array, array\|int given\./'
- '/Parameter \#2 \$class of static method Webmozart\\Assert\\Assert::isInstanceOf\(\) expects class-string<object>, string given./'
- '/Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string<object>|object, object|string given./'
- '/Return typehint of method Sylius\\Bundle\\ResourceBundle\\Routing\\CrudRoutesAttributesLoader::getClassAttributes\(\) has invalid type ReflectionAttribute./'
- '/Return typehint of method Sylius\\Bundle\\ResourceBundle\\Routing\\RoutesAttributesLoader::getClassAttributes\(\) has invalid type ReflectionAttribute./'
- '/Unable to resolve the template type ExpectedType in call to method static method Webmozart\\Assert\\Assert::isInstanceOf\(\)/'
- '/is deprecated since Sylius 1\.8/'
4 changes: 4 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<file name="src/Bundle/DependencyInjection/Driver/Doctrine/DoctrinePHPCRDriver.php" />
<file name="src/Bundle/EventListener/ODM*.php" />
<file name="src/Bundle/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php" />
<file name="src/Bundle/Routing/RoutesAttributesLoader.php" />
<file name="src/Bundle/Routing/CrudRoutesAttributesLoader.php" />
</ignoreFiles>
</projectFiles>

Expand Down Expand Up @@ -102,6 +104,8 @@
<TooManyArguments>
<errorLevel type="suppress">
<referencedFunction name="Symfony\Component\HttpFoundation\HeaderBag::all" />
<referencedFunction name="Symfony\Component\Routing\RouteCollection::add" />
<referencedFunction name="Symfony\Component\Finder\Finder::sortByName" />
<referencedFunction name="Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch" />
</errorLevel>
</TooManyArguments>
Expand Down
9 changes: 9 additions & 0 deletions src/Bundle/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ public function getConfigTreeBuilder(): TreeBuilder

$rootNode
->children()
->arrayNode('mapping')
->addDefaultsIfNotSet()
->children()
->arrayNode('paths')
->defaultValue(['%kernel.project_dir%/src/Entity'])
->prototype('scalar')->end()
->end()
->end()
->end()
->scalarNode('authorization_checker')
->defaultValue('sylius.resource_controller.authorization_checker.disabled')
->cannotBeEmpty()
Expand Down
1 change: 1 addition & 0 deletions src/Bundle/DependencyInjection/SyliusResourceExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function load(array $configs, ContainerBuilder $container): void
$container->setAlias('sylius.translation_locale_provider', $config['translation']['locale_provider'])->setPublic(true);
}

$container->setParameter('sylius.resource.mapping', $config['mapping']);
$container->setParameter('sylius.resource.settings', $config['settings']);
$container->setAlias('sylius.resource_controller.authorization_checker', $config['authorization_checker']);

Expand Down
5 changes: 0 additions & 5 deletions src/Bundle/Doctrine/ORM/EntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@
namespace Sylius\Bundle\ResourceBundle\Doctrine\ORM;

use Doctrine\ORM\EntityRepository as BaseEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\ArrayAdapter;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;

/** @psalm-suppress DeprecatedInterface */
Expand Down
9 changes: 9 additions & 0 deletions src/Bundle/Doctrine/ORM/ResourceRepositoryTrait.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Doctrine\ORM;
Expand Down
15 changes: 15 additions & 0 deletions src/Bundle/Resources/config/services/routing.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,20 @@
</argument>
<tag name="routing.loader" />
</service>

<service id="sylius.routing.loader.crud_routes_attributes" class="Sylius\Bundle\ResourceBundle\Routing\CrudRoutesAttributesLoader" public="false">
<argument>%sylius.resource.mapping%</argument>
<argument type="service" id="sylius.routing.loader.resource" />
<tag name="routing.route_loader" />
</service>

<service id="sylius.routing.loader.routes_attributes" class="Sylius\Bundle\ResourceBundle\Routing\RoutesAttributesLoader" public="false">
<argument>%sylius.resource.mapping%</argument>
<argument type="service" id="sylius.routing.factory.route_attributes" />
<tag name="routing.route_loader" />
</service>

<service id="sylius.routing.factory.route_attributes" class="Sylius\Bundle\ResourceBundle\Routing\RouteAttributesFactory" public="false" />
<service id="Sylius\Bundle\ResourceBundle\Routing\RouteAttributesFactoryInterface" alias="sylius.routing.factory.route_attributes" />
</services>
</container>
58 changes: 58 additions & 0 deletions src/Bundle/Routing/CrudRoutesAttributesLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Routing;

use Sylius\Component\Resource\Annotation\SyliusCrudRoutes;
use Sylius\Component\Resource\Reflection\ClassReflection;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Yaml\Yaml;

final class CrudRoutesAttributesLoader
{
private array $mapping;

private ResourceLoader $resourceLoader;

public function __construct(
array $mapping,
ResourceLoader $resourceLoader
) {
$this->mapping = $mapping;
$this->resourceLoader = $resourceLoader;
}

public function __invoke(): RouteCollection
{
$routeCollection = new RouteCollection();
$paths = $this->mapping['paths'] ?? [];

/** @var string $className */
foreach (ClassReflection::getResourcesByPaths($paths) as $className) {
$this->addRoutesForSyliusCrudRoutesAttributes($routeCollection, $className);
}

return $routeCollection;
}

private function addRoutesForSyliusCrudRoutesAttributes(RouteCollection $routeCollection, string $className): void
{
$attributes = ClassReflection::getClassAttributes($className, SyliusCrudRoutes::class);

foreach ($attributes as $reflectionAttribute) {
$resource = Yaml::dump($reflectionAttribute->getArguments());
$resourceRouteCollection = $this->resourceLoader->load($resource);
$routeCollection->addCollection($resourceRouteCollection);
}
}
}
75 changes: 75 additions & 0 deletions src/Bundle/Routing/RouteAttributesFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Routing;

use Sylius\Component\Resource\Annotation\SyliusRoute;
use Sylius\Component\Resource\Reflection\ClassReflection;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Webmozart\Assert\Assert;

final class RouteAttributesFactory implements RouteAttributesFactoryInterface
{
public function createRouteForClass(RouteCollection $routeCollection, string $className): void
{
$attributes = ClassReflection::getClassAttributes($className, SyliusRoute::class);

foreach ($attributes as $reflectionAttribute) {
$arguments = $reflectionAttribute->getArguments();

Assert::keyExists($arguments, 'name', 'Your route should have a name attribute.');

$syliusOptions = [];

if (isset($arguments['template'])) {
$syliusOptions['template'] = $arguments['template'];
}

if (isset($arguments['vars'])) {
$syliusOptions['vars'] = $arguments['vars'];
}

if (isset($arguments['criteria'])) {
$syliusOptions['criteria'] = $arguments['criteria'];
}

if (isset($arguments['repository'])) {
$syliusOptions['repository'] = $arguments['repository'];
}

if (isset($arguments['serializationGroups'])) {
$syliusOptions['serialization_groups'] = $arguments['serializationGroups'];
}

if (isset($arguments['serializationVersion'])) {
$syliusOptions['serialization_version'] = $arguments['serializationVersion'];
}

$route = new Route(
$arguments['path'],
[
'_controller' => $arguments['controller'] ?? null,
'_sylius' => $syliusOptions,
],
$arguments['requirements'] ?? [],
$arguments['options'] ?? [],
$arguments['host'] ?? '',
$arguments['schemes'] ?? [],
$arguments['methods'] ?? []
);

$routeCollection->add($arguments['name'], $route, $arguments['priority'] ?? 0);
}
}
}
21 changes: 21 additions & 0 deletions src/Bundle/Routing/RouteAttributesFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Routing;

use Symfony\Component\Routing\RouteCollection;

interface RouteAttributesFactoryInterface
{
public function createRouteForClass(RouteCollection $routeCollection, string $className): void;
}
56 changes: 56 additions & 0 deletions src/Bundle/Routing/RoutesAttributesLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Routing;

use Sylius\Component\Resource\Reflection\ClassReflection;
use Symfony\Component\Routing\RouteCollection;

final class RoutesAttributesLoader
{
private array $mapping;

private RouteAttributesFactoryInterface $routesAttributesFactory;

public function __construct(array $mapping, RouteAttributesFactoryInterface $routesAttributesFactory)
{
$this->mapping = $mapping;
$this->routesAttributesFactory = $routesAttributesFactory;
}

public function __invoke(): RouteCollection
{
$routeCollection = new RouteCollection();
$paths = $this->mapping['paths'] ?? [];

/** @var string $className */
foreach (ClassReflection::getResourcesByPaths($paths) as $className) {
$this->routesAttributesFactory->createRouteForClass($routeCollection, $className);
}

return $routeCollection;
}

private function getClasses(): iterable
{
$paths = $this->mapping['paths'] ?? [];

foreach ($paths as $resourceDirectory) {
$resources = ClassReflection::getResourcesByPath($resourceDirectory);

foreach ($resources as $className) {
yield $className;
}
}
}
}
42 changes: 42 additions & 0 deletions src/Bundle/Tests/Configuration/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,48 @@ public function it_does_not_break_if_not_customized()
);
}

/**
* @test
*/
public function it_has_default_mapping_paths()
{
$this->assertProcessedConfigurationEquals(
[
[],
],
[
'mapping' => [
'paths' => [
'%kernel.project_dir%/src/Entity',
],
],
],
'mapping'
);
}

/**
* @test
*/
public function its_mapping_paths_can_be_customized()
{
$this->assertProcessedConfigurationEquals(
[
['mapping' => [
'paths' => ['path/to/resources'],
]],
],
[
'mapping' => [
'paths' => [
'path/to/resources',
],
],
],
'mapping'
);
}

/**
* @test
*/
Expand Down
Loading

0 comments on commit 7ddf718

Please sign in to comment.