Passo 24: Eseguire Cron

5.0 version
Maintained

Eseguire Cron

I cron sono utili per svolgere attività di manutenzione. A differenza dei worker, hanno un orario di esecuzione predefinito, e vengono eseguiti per un breve periodo di tempo.

Pulire i commenti

I commenti contrassegnati come spam o rifiutati dall’amministratore sono conservati nel database, in quanto l’amministratore potrebbe volerli ispezionare per un po' di tempo. Ma probabilmente dovrebbero essere rimossi in un secondo momento. Probabilmente è sufficiente conservarli per una settimana.

Creare alcuni metodi nel repository dei commenti: per trovare i commenti rifiutati, per contarli e per cancellarli:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
--- a/src/Repository/CommentRepository.php
+++ b/src/Repository/CommentRepository.php
@@ -6,6 +6,7 @@ use App\Entity\Comment;
 use App\Entity\Conference;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
+use Doctrine\ORM\QueryBuilder;
 use Doctrine\ORM\Tools\Pagination\Paginator;

 /**
@@ -16,12 +17,37 @@ use Doctrine\ORM\Tools\Pagination\Paginator;
  */
 class CommentRepository extends ServiceEntityRepository
 {
+    private const DAYS_BEFORE_REJECTED_REMOVAL = 7;
+
     public const PAGINATOR_PER_PAGE = 2;

     public function __construct(ManagerRegistry $registry)
     {
         parent::__construct($registry, Comment::class);
     }
+
+    public function countOldRejected(): int
+    {
+        return $this->getOldRejectedQueryBuilder()->select('COUNT(c.id)')->getQuery()->getSingleScalarResult();
+    }
+
+    public function deleteOldRejected(): int
+    {
+        return $this->getOldRejectedQueryBuilder()->delete()->getQuery()->execute();
+    }
+
+    private function getOldRejectedQueryBuilder(): QueryBuilder
+    {
+        return $this->createQueryBuilder('c')
+            ->andWhere('c.state = :state_rejected or c.state = :state_spam')
+            ->andWhere('c.createdAt < :date')
+            ->setParameters([
+                'state_rejected' => 'rejected',
+                'state_spam' => 'spam',
+                'date' => new \DateTime(-self::DAYS_BEFORE_REJECTED_REMOVAL.' days'),
+            ])
+        ;
+    }

     public function getCommentPaginator(Conference $conference, int $offset): Paginator
     {

Suggerimento

Per le query più complesse, a volte è utile dare un’occhiata alle istruzioni SQL generate (si possono trovare nei log e nel profiler per le richieste web).

Utilizzo delle costanti di classe, dei parametri del container e delle variabili d’ambiente

Sette giorni? Avremmo potuto scegliere un altro numero, forse dieci o venti. Questo numero potrebbe evolvere nel tempo. Abbiamo deciso di memorizzarlo come costante di classe, ma avremmo potuto memorizzarlo come parametro nel container o ancora come variabile d’ambiente.

Ecco alcune regole empiriche per decidere quale astrazione usare:

  • Se il valore deve essere mantenuto segreto (password, token API, ….), usare il portachiavi di Symfony o un sistema esterno di portachiavi;
  • Se il valore è dinamico e lo si può cambiare senza dover rifare un deploy, usare una variabile d’ambiente;
  • Se il valore può essere diverso da un ambiente all’altro, usare un parametro del container;
  • Per tutto il resto, memorizzare il valore nel codice, come costante di classe.

Creazione di un comando CLI

La rimozione dei vecchi commenti è il compito perfetto per un cron. Andrebbe fatta su base regolare e un piccolo ritardo non ha un impatto significativo.

Creare un comando CLI denominato app:comment:cleanup, creando un file src/Command/CommentCleanupCommand.php:

src/Command/CommentCleanupCommand.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace App\Command;

use App\Repository\CommentRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class CommentCleanupCommand extends Command
{
    private $commentRepository;

    protected static $defaultName = 'app:comment:cleanup';

    public function __construct(CommentRepository $commentRepository)
    {
        $this->commentRepository = $commentRepository;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setDescription('Deletes rejected and spam comments from the database')
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Dry run')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        if ($input->getOption('dry-run')) {
            $io->note('Dry mode enabled');

            $count = $this->commentRepository->countOldRejected();
        } else {
            $count = $this->commentRepository->deleteOldRejected();
        }

        $io->success(sprintf('Deleted "%d" old rejected/spam comments.', $count));

        return 0;
    }
}

Tutti i comandi dell’applicazione sono registrati insieme a quelli integrati in Symfony e sono tutti accessibili tramite symfony console. Poiché il numero di comandi disponibili potrebbe essere elevato, dovremmo raggrupparli per nome. Per convenzione, i comandi dell’applicazione vengono raggruppati sotto il namespace app. Si possono aggiungere ulteriori namespace, separati con i due punti (:).

Un comando riceve un input (parametri e opzioni passati al comando) ed è possibile utilizzare il suo output per scrivere nella console.

Pulire il database eseguendo il comando:

1
$ symfony console app:comment:cleanup

Impostare un cron su SymfonyCloud

Una delle cose belle di SymfonyCloud è che la maggior parte della configurazione è memorizzata in un unico file: .symfony.cloud.yaml. Il container web, i worker e i cron sono descritti insieme, per facilitare la manutenzione:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
--- a/.symfony.cloud.yaml
+++ b/.symfony.cloud.yaml
@@ -43,6 +43,15 @@ hooks:

         (>&2 symfony-deploy)

+crons:
+    comment_cleanup:
+        # Cleanup every night at 11.50 pm (UTC).
+        spec: '50 23 * * *'
+        cmd: |
+            if [ "$SYMFONY_BRANCH" = "master" ]; then
+                croncape symfony console app:comment:cleanup
+            fi
+
 workers:
     messages:
         commands:

La sezione crons definisce tutti i processi di cron. Ogni cron funziona secondo quanto indicato in spec.

Il programma croncape controlla l’esecuzione del comando e, se questo restituisce un codice di uscita diverso da 0, invia un’email agli indirizzi definiti nella variabile d’ambiente MAILTO.

Configurare la variabile d’ambiente MAILTO:

1
$ symfony var:set MAILTO=[email protected]

È possibile forzare l’esecuzione di un cron dalla macchina locale:

1
$ symfony cron comment_cleanup

Si noti che i cron sono impostati su tutti i branch di SymfonyCloud. Se si desidera escluderne qualcuno in ambienti non di produzione, controllare la variabile d’ambiente $SYMFONY_BRANCH:

1
2
3
if [ "$SYMFONY_BRANCH" = "master" ]; then
    croncape symfony app:invoices:send
fi

  • « Previous Passo 23: Ridimensionamento delle immagini
  • Next » Passo 25: Notifiche per tutti

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.