Skip to content

Commit

Permalink
[Symfony] Add access control custom method call conversion (#561)
Browse files Browse the repository at this point in the history
* add fixture to achieve

* add more extensoins
  • Loading branch information
TomasVotruba authored Jan 16, 2024
1 parent bd312e6 commit 4cde44a
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Rector\Symfony\Tests\Rector\Closure\StringExtensionToConfigBuilderRector\Fixture;

use Rector\Symfony\Tests\CodeQuality\Rector\Closure\StringExtensionToConfigBuilderRector\Source\SomeUserProvider;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->extension('security', [
'access_control' => [
[
'path' => '^/first',
'role' => 'PUBLIC_ACCESS',
],
[
'path' => '^/second',
'role' => 'PUBLIC_ACCESS',
],
],
]);
};

?>
-----
<?php

namespace Rector\Symfony\Tests\Rector\Closure\StringExtensionToConfigBuilderRector\Fixture;

use Rector\Symfony\Tests\CodeQuality\Rector\Closure\StringExtensionToConfigBuilderRector\Source\SomeUserProvider;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (\Symfony\Config\SecurityConfig $securityConfig) : void {
$securityConfig->accessControl()->path('^/first')->roles(['PUBLIC_ACCESS']);
$securityConfig->accessControl()->path('^/second')->roles(['PUBLIC_ACCESS']);
};

?>
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Rector\Naming\Naming\PropertyNaming;
use Rector\PhpParser\Node\Value\ValueResolver;
use Rector\Rector\AbstractRector;
use Rector\Symfony\Configs\ConfigArrayHandler\SecurityAccessControlConfigArrayHandler;
use Rector\Symfony\Configs\Enum\SecurityConfigKey;
use Rector\Symfony\NodeAnalyzer\SymfonyClosureExtensionMatcher;
use Rector\Symfony\NodeAnalyzer\SymfonyPhpClosureDetector;
use Rector\Symfony\ValueObject\ExtensionKeyAndConfiguration;
Expand All @@ -35,13 +37,17 @@ final class StringExtensionToConfigBuilderRector extends AbstractRector
private const EXTENSION_KEY_TO_CLASS_MAP = [
'security' => 'Symfony\Config\SecurityConfig',
'framework' => 'Symfony\Config\FrameworkConfig',
'monolog' => 'Symfony\Config\MonologConfig',
'twig' => 'Symfony\Config\TwigConfig',
'doctrine' => 'Symfony\Config\DoctrineConfig',
];

public function __construct(
private readonly SymfonyPhpClosureDetector $symfonyPhpClosureDetector,
private readonly SymfonyClosureExtensionMatcher $symfonyClosureExtensionMatcher,
private readonly PropertyNaming $propertyNaming,
private readonly ValueResolver $valueResolver,
private readonly SecurityAccessControlConfigArrayHandler $securityAccessControlConfigArrayHandler,
) {
}

Expand Down Expand Up @@ -154,6 +160,19 @@ private function createMethodCallStmts(Array_ $configurationArray, Variable $con
$methodCallName = $this->createCamelCaseFromUnderscored($key);
}

if ($key === SecurityConfigKey::ACCESS_CONTROL) {
$accessControlMethodCalls = $this->securityAccessControlConfigArrayHandler->handle(
$configurationArray,
$configVariable
);
if ($accessControlMethodCalls !== []) {
$methodCallStmts = array_merge($methodCallStmts, $accessControlMethodCalls);
continue;
}

continue;
}

if ($splitMany) {
foreach ($value as $itemName => $itemConfiguration) {
$fluentMethodCall = $this->createNextMethodCall(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace Rector\Symfony\Configs\ConfigArrayHandler;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Expression;
use Rector\Exception\ShouldNotHappenException;

final class SecurityAccessControlConfigArrayHandler
{
/**
* @return array<Expression<MethodCall>>
*/
public function handle(Array_ $array, Variable $configVariable): array
{
if (! $array->items[0] instanceof ArrayItem) {
return [];
}

$nestedAccessControlArrayItem = $array->items[0];
$nestedAccessControlArray = $nestedAccessControlArrayItem->value;

if (! $nestedAccessControlArray instanceof Array_) {
return [];
}

$methodCallStmts = [];
foreach ($nestedAccessControlArray->items as $accessControlArrayItem) {
if (! $accessControlArrayItem instanceof ArrayItem) {
throw new ShouldNotHappenException();
}

$nestedAccessControlArrayArray = $accessControlArrayItem->value;
if (! $nestedAccessControlArrayArray instanceof Array_) {
return [];
}

// build accessControl() method call here
$accessControlMethodCall = new MethodCall($configVariable, 'accessControl');

$accessControlMethodCall = $this->decoreateAccessControlArrayArray(
$nestedAccessControlArrayArray,
$accessControlMethodCall
);

$methodCallStmts[] = new Expression($accessControlMethodCall);
}

return $methodCallStmts;
}

private function decoreateAccessControlArrayArray(
Array_ $nestedAccessControlArrayArray,
MethodCall $accessControlMethodCall
): MethodCall {
foreach ($nestedAccessControlArrayArray->items as $nestedAccessControlArrayArrayItem) {
if (! $nestedAccessControlArrayArrayItem instanceof ArrayItem) {
throw new ShouldNotHappenException();
}

if ($nestedAccessControlArrayArrayItem->key instanceof String_) {
$methodName = $nestedAccessControlArrayArrayItem->key->value;

$argValue = $nestedAccessControlArrayArrayItem->value;

// method correction
if ($methodName === 'role') {
$methodName = 'roles';
$argValue = new Array_([new ArrayItem($argValue)]);
}

$accessControlMethodCall = new MethodCall(
$accessControlMethodCall,
$methodName,
[new Arg($argValue)]
);
}
}

return $accessControlMethodCall;
}
}
13 changes: 13 additions & 0 deletions rules/Configs/Enum/SecurityConfigKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Rector\Symfony\Configs\Enum;

final class SecurityConfigKey
{
/**
* @var string
*/
public const ACCESS_CONTROL = 'access_control';
}

0 comments on commit 4cde44a

Please sign in to comment.