Created
July 22, 2024 02:58
-
-
Save victorshdev/0026d1fc741f6da5dc98258962cc7df5 to your computer and use it in GitHub Desktop.
Implementation of OneTime command in Symfony
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Entity; | |
use App\Repository\OneTimeCommandRepository; | |
use Doctrine\ORM\Mapping as ORM; | |
#[ORM\Entity(repositoryClass: OneTimeCommandRepository::class)] | |
class OneTimeCommand | |
{ | |
#[ORM\Id] | |
#[ORM\GeneratedValue] | |
#[ORM\Column] | |
private ?int $id = null; | |
#[ORM\Column(length: 255, unique: true)] | |
private ?string $name = null; | |
#[ORM\Column] | |
private ?\DateTimeImmutable $launchedAt = null; | |
public function __construct() | |
{ | |
$this->launchedAt = new \DateTimeImmutable(); | |
} | |
public function getId(): ?int | |
{ | |
return $this->id; | |
} | |
public function getName(): ?string | |
{ | |
return $this->name; | |
} | |
public function setName(string $name): static | |
{ | |
$this->name = $name; | |
return $this; | |
} | |
public function getLaunchedAt(): ?\DateTimeImmutable | |
{ | |
return $this->launchedAt; | |
} | |
public function setLaunchedAt(\DateTimeImmutable $launchedAt): static | |
{ | |
$this->launchedAt = $launchedAt; | |
return $this; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\EventListener; | |
use App\Attributes\RunOnlyOnce; | |
use App\Entity\OneTimeCommand; | |
use App\Repository\OneTimeCommandRepository; | |
use Doctrine\ORM\EntityManagerInterface; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\Console\Command\Command; | |
use Symfony\Component\Console\ConsoleEvents; | |
use Symfony\Component\Console\Event\ConsoleCommandEvent; | |
use Symfony\Component\Console\Event\ConsoleTerminateEvent; | |
use Symfony\Component\EventDispatcher\Attribute\AsEventListener; | |
/** | |
* Class OneTimeCommandListener | |
* @package App\EventListener | |
*/ | |
final class OneTimeCommandListener | |
{ | |
public function __construct( | |
private readonly OneTimeCommandRepository $repository, | |
private readonly EntityManagerInterface $entityManager, | |
private readonly LoggerInterface $logger | |
) { | |
} | |
/** | |
* Event fired before command execution. Checks if the command was already run. | |
* If so, disables the command. | |
* | |
* @param ConsoleCommandEvent $event | |
* @return void | |
*/ | |
#[AsEventListener(event: ConsoleEvents::COMMAND)] | |
public function onBeforeRun(ConsoleCommandEvent $event): void | |
{ | |
// Check if command meets the requirements to be processed by this listener | |
if (!$this->checkRequirements($event->getCommand())) { | |
return; | |
} | |
$command = $event->getCommand(); | |
// Check if the command was already run | |
if (!empty($history = $this->repository->findOneBy(['name' => $command->getName()]))) { | |
$event->disableCommand(); | |
$this->logger->warning('Command {command} was already run at {date}', [ | |
'command' => $command::class, | |
'listener' => self::class, | |
'date' => $history->getLaunchedAt()->format('Y-m-d H:i:s') | |
]); | |
} | |
} | |
/** | |
* Event fired after command execution. If the command was successful, saves the command to the database. | |
* | |
* @param ConsoleTerminateEvent $event | |
* @return void | |
*/ | |
#[AsEventListener(event: ConsoleEvents::TERMINATE)] | |
public function onAfterRun(ConsoleTerminateEvent $event): void | |
{ | |
// Check if command meets the requirements to be processed by this listener | |
if (!$this->checkRequirements($event->getCommand())) { | |
return; | |
} | |
$command = $event->getCommand(); | |
if ($event->getExitCode() !== Command::SUCCESS) { | |
return; | |
} | |
$entity = (new OneTimeCommand())->setName($command->getName()); | |
$this->entityManager->persist($entity); | |
$this->entityManager->flush(); | |
} | |
/** | |
* Check if command has RunOnlyOnce attribute. If so, the command should be run only once. | |
* | |
* @param Command $command | |
* @return bool | |
*/ | |
private function checkRequirements(Command $command): bool | |
{ | |
return !empty((new \ReflectionClass(get_class($command)))->getAttributes(RunOnlyOnce::class)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Command\OneTime; | |
use App\Attributes\RunOnlyOnce; | |
use App\Entity\Dictionary; | |
use App\Enum\DictionaryType; | |
use App\Repository\DictionaryRepository; | |
use Symfony\Component\Console\Attribute\AsCommand; | |
use Symfony\Component\Console\Command\Command; | |
use Symfony\Component\Console\Input\InputInterface; | |
use Symfony\Component\Console\Output\OutputInterface; | |
use Symfony\Component\Console\Style\SymfonyStyle; | |
#[AsCommand( | |
name: 'app:one-time-example', | |
description: 'Example of one time command', | |
)] | |
#[RunOnlyOnce] | |
class OneTimeExampleCommand extends Command { | |
public function __construct() | |
{ | |
parent::__construct(); | |
} | |
protected function configure(): void | |
{ | |
} | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
return Command::SUCCESS; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Attributes; | |
#[\Attribute(\Attribute::TARGET_CLASS)] | |
class RunOnlyOnce | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace DoctrineMigrations; | |
use Doctrine\DBAL\Schema\Schema; | |
use Doctrine\Migrations\AbstractMigration; | |
/** | |
* Auto-generated Migration: Please modify to your needs! | |
*/ | |
final class Version20240722014725 extends AbstractMigration | |
{ | |
public function getDescription(): string | |
{ | |
return ''; | |
} | |
public function up(Schema $schema): void | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->addSql('CREATE SEQUENCE one_time_command_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); | |
$this->addSql('CREATE TABLE one_time_command (id INT NOT NULL, name VARCHAR(255) NOT NULL, launched_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); | |
$this->addSql('CREATE UNIQUE INDEX UNIQ_734172A35E237E06 ON one_time_command (name)'); | |
$this->addSql('COMMENT ON COLUMN one_time_command.launched_at IS \'(DC2Type:datetime_immutable)\''); | |
} | |
public function down(Schema $schema): void | |
{ | |
// this down() migration is auto-generated, please modify it to your needs | |
$this->addSql('CREATE SCHEMA public'); | |
$this->addSql('DROP SEQUENCE one_time_command_id_seq CASCADE'); | |
$this->addSql('DROP TABLE one_time_command'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment