Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"symfony/expression-language": "^5.4 || ^6.4 || ^7.0",
"symfony/filesystem": "^5.4 || ^6.4 || ^7.0",
"symfony/http-client": "^5.4 || ^6.4 || ^7.0",
"symfony/maker-bundle": "^1.62",
"symfony/messenger": "^5.4 || ^6.4 || ^7.0",
"symfony/process": "^5.4 || ^6.4 || ^7.0",
"symfony/serializer": "^5.4 || ^6.4 || ^7.0",
Expand Down
7 changes: 7 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Sofascore\PurgatoryBundle\Command\DebugCommand;
use Sofascore\PurgatoryBundle\Doctrine\DBAL\Middleware;
use Sofascore\PurgatoryBundle\Listener\EntityChangeListener;
use Sofascore\PurgatoryBundle\Maker\MakeRouteProvider;
use Sofascore\PurgatoryBundle\Purger\AsyncPurger;
use Sofascore\PurgatoryBundle\Purger\InMemoryPurger;
use Sofascore\PurgatoryBundle\Purger\Messenger\PurgeMessageHandler;
Expand Down Expand Up @@ -234,5 +235,11 @@
service('doctrine'),
])
->tag('console.command')

->set('sofascore.purgatory.maker.make_provider', MakeRouteProvider::class)
->args([
service('doctrine'),
])
->tag('maker.command')
;
};
29 changes: 28 additions & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.3.0@222dda8483516044c2ed7a4c3f197d7c9d6c3ddb">
<files psalm-version="6.10.1@f9fd6bc117e9ce1e854c2ed6777e7135aaa4966b">
<file src="src/Cache/Configuration/CachedConfigurationLoader.php">
<MixedArgument>
<code><![CDATA[require $cache->getPath()]]></code>
Expand Down Expand Up @@ -85,4 +85,31 @@
<code><![CDATA[PurgatoryConnection]]></code>
</DuplicateClass>
</file>
<file src="src/Maker/MakeRouteProvider.php">
<InternalClass>
<code><![CDATA[ClassData::create(
class: \sprintf('Purgatory\RouteProvider\%s', $name),
suffix: 'RouteProvider',
useStatements: [
RouteProviderInterface::class,
PurgeRoute::class,
Action::class,
$entity,
],
)]]></code>
</InternalClass>
<InternalMethod>
<code><![CDATA[ClassData::create(
class: \sprintf('Purgatory\RouteProvider\%s', $name),
suffix: 'RouteProvider',
useStatements: [
RouteProviderInterface::class,
PurgeRoute::class,
Action::class,
$entity,
],
)]]></code>
<code><![CDATA[generateClassFromClassData]]></code>
</InternalMethod>
</file>
</files>
196 changes: 196 additions & 0 deletions src/Maker/MakeRouteProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php

declare(strict_types=1);

namespace Sofascore\PurgatoryBundle\Maker;

use Doctrine\Persistence\ManagerRegistry;
use Sofascore\PurgatoryBundle\Listener\Enum\Action;
use Sofascore\PurgatoryBundle\Maker\Util\ActionCollection;
use Sofascore\PurgatoryBundle\RouteProvider\PurgeRoute;
use Sofascore\PurgatoryBundle\RouteProvider\RouteProviderInterface;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Question\Question;

final class MakeRouteProvider extends AbstractMaker
{
public function __construct(
private readonly ManagerRegistry $managerRegistry,
) {
}

/**
* {@inheritDoc}
*/
public static function getCommandName(): string
{
return 'make:purgatory-provider';
}

public static function getCommandDescription(): string
{
return 'Create a purge route provider';
}

/**
* {@inheritDoc}
*/
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
{
$command->addArgument('name', InputArgument::OPTIONAL, 'The name of the Purgatory route provider class (e.g. <fg=yellow>BlogPostRouteProvider</>)');
}

/**
* {@inheritDoc}
*/
public function configureDependencies(DependencyBuilder $dependencies): void
{
}

/**
* {@inheritDoc}
*/
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
{
/** @var string $name */
$name = $input->getArgument('name');

if (false === $entity = $this->inputEntity($io)) {
return;
}

$variables = [
'entity' => Str::getShortClassName($entity),
];

if (null !== $actions = $this->inputActions($io)) {
$variables['actions'] = new ActionCollection($actions);
}

$classData = ClassData::create(
class: \sprintf('Purgatory\RouteProvider\%s', $name),
suffix: 'RouteProvider',
useStatements: [
RouteProviderInterface::class,
PurgeRoute::class,
Action::class,
$entity,
],
);

$generator->generateClassFromClassData(
classData: $classData,
templateName: __DIR__.'/../../templates/maker/RouteProvider.tpl.php',
variables: $variables,
);

$generator->writeChanges();
}

/**
* @return class-string|false
*/
private function inputEntity(ConsoleStyle $io): string|false
{
$q = new Question('What entity is the purge route provider for?');
$q->setTrimmable(true);

/** @var string $purgeEntity */
$purgeEntity = $io->askQuestion($q);

$entities = $this->getEntityCollection();
$entities = array_filter(
$entities,
static fn (string $name): bool => str_contains(strtolower($name), strtolower($purgeEntity)),
\ARRAY_FILTER_USE_KEY,
);

if ([] === $entities) {
$io->error('No entities found');

return false;
}

if (1 === \count($entities) && 1 === \count($entities[array_key_first($entities)])) {
$entity = $entities[array_key_first($entities)][0];
} else {
$entityChoices = array_keys($entities);
sort($entityChoices, \SORT_STRING | \SORT_FLAG_CASE);

/** @var string $target */
$target = $io->choice('Select one of the available entities', $entityChoices);

/** @var class-string $entity */
$entity = \count($entities[$target]) > 1
? $io->choice('Select one of the available entities', $entities[$target])

Check warning on line 134 in src/Maker/MakeRouteProvider.php

View check run for this annotation

Codecov / codecov/patch

src/Maker/MakeRouteProvider.php#L134

Added line #L134 was not covered by tests
: $entities[$target][0];
}

return $entity;
}

/**
* @return ?non-empty-list<'Create'|'Update'|'Delete'>
*/
private function inputActions(ConsoleStyle $io): ?array
{
if ($io->confirm('Should route provider handle only some actions (update/create/delete)?', default: false)) {
/** @var non-empty-list<'Create'|'Update'|'Delete'> $actions */
$actions = $io->choice(
question: 'Select actions',
choices: [
Action::Create->name,
Action::Update->name,
Action::Delete->name,
],
multiSelect: true,
);

$actions = array_values(array_unique($actions));
} else {
$actions = null;
}

return $actions;
}

/**
* @return array<string, non-empty-list<class-string>>
*/
private function getEntityCollection(): array
{
/** @var array<string, non-empty-list<class-string>> $entities */
$entities = [];

foreach ($this->managerRegistry->getManagers() as $manager) {
foreach ($manager->getMetadataFactory()->getAllMetadata() as $metadata) {
$entityFqcn = $metadata->getName();
$name = strrchr($entityFqcn, '\\');
$name = substr(false === $name ? $entityFqcn : $name, 1);

if (isset($entities[$name])) {
if (!\in_array($entityFqcn, $entities[$name], true)) {
$entities[$name][] = $entityFqcn;

Check warning on line 182 in src/Maker/MakeRouteProvider.php

View check run for this annotation

Codecov / codecov/patch

src/Maker/MakeRouteProvider.php#L181-L182

Added lines #L181 - L182 were not covered by tests
}
} else {
$entities[$name] = [$entityFqcn];
}
}
}

foreach ($entities as &$entityFqcns) {
sort($entityFqcns, \SORT_STRING | \SORT_FLAG_CASE);
}

return $entities;
}
}
30 changes: 30 additions & 0 deletions src/Maker/Util/ActionCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Sofascore\PurgatoryBundle\Maker\Util;

final class ActionCollection
{
/**
* @param non-empty-list<'Create'|'Update'|'Delete'> $actions
*/
public function __construct(
private readonly array $actions,
) {
}

public function __toString(): string
{
if (1 === \count($this->actions)) {
return "Action::{$this->actions[0]} === \$action";
}

$haystack = implode(
', ',
array_map(static fn (string $name): string => "Action::$name", $this->actions),
);

return "\in_array(\$action, [$haystack], true)";
}
}
37 changes: 37 additions & 0 deletions templates/maker/RouteProvider.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php echo "<?php\n"; ?>

namespace <?php echo $class_data->getNamespace(); ?>;

<?php echo $class_data->getUseStatements(); ?>

/**
* @implements RouteProviderInterface<<?php echo $entity; ?>>
*/
<?php echo $class_data->getClassDeclaration(); ?> implements RouteProviderInterface
{
/**
* {@inheritDoc}
*/
public function provideRoutesFor(Action $action, object $entity, array $entityChangeSet): iterable
{
// add with your own logic if needed

yield new PurgeRoute(
name: 'app_route', // replace it with your route
params: [
'param1' => $entity, // replace it with correct data for route parameters
],
);
}

public function supports(Action $action, object $entity): bool
{
return $entity instanceof <?php echo $entity; ?>
<?php if (!isset($actions)) { ?>
;
<?php } else { ?>

&& <?php echo $actions; ?>;
<?php } ?>
}
}
3 changes: 3 additions & 0 deletions tests/Functional/TestApplication/config/app_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ services:

Sofascore\PurgatoryBundle\Tests\Functional\TestApplication\:
resource: '../'

maker:
root_namespace: 'Sofascore\PurgatoryBundle\Tests\Functional\TestApplication\Generated'
2 changes: 2 additions & 0 deletions tests/Functional/TestKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Sofascore\PurgatoryBundle\PurgatoryBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\MakerBundle\MakerBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
Expand Down Expand Up @@ -63,6 +64,7 @@ public function registerBundles(): iterable
yield new FrameworkBundle();
yield new DoctrineBundle();
yield new PurgatoryBundle();
yield new MakerBundle();
}

public function shutdown(): void
Expand Down
35 changes: 35 additions & 0 deletions tests/Maker/Expected/AnimalCompetitionRouteProvider.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Sofascore\PurgatoryBundle\Tests\Functional\TestApplication\Generated\Purgatory\RouteProvider;

use Sofascore\PurgatoryBundle\Listener\Enum\Action;
use Sofascore\PurgatoryBundle\RouteProvider\PurgeRoute;
use Sofascore\PurgatoryBundle\RouteProvider\RouteProviderInterface;
use Sofascore\PurgatoryBundle\Tests\Functional\TestApplication\Entity\Competition\AnimalCompetition;

/**
* @implements RouteProviderInterface<AnimalCompetition>
*/
final class AnimalCompetitionRouteProvider implements RouteProviderInterface
{
/**
* {@inheritDoc}
*/
public function provideRoutesFor(Action $action, object $entity, array $entityChangeSet): iterable
{
// add with your own logic if needed

yield new PurgeRoute(
name: 'app_route', // replace it with your route
params: [
'param1' => $entity, // replace it with correct data for route parameters
],
);
}

public function supports(Action $action, object $entity): bool
{
return $entity instanceof AnimalCompetition
&& \in_array($action, [Action::Create, Action::Update, Action::Delete], true);
}
}
Loading
Loading