Skip to content

Cron を実行する

Cron は、メンテナンスタスクに便利です。ワーカーと異なり、短い期間でスケジュール実行されます。

コメントをクリーンアップする

スパムとして判定されたコメント、管理者によって拒否されたコメントは、データベース内に残っているので、管理者は少しの間調べることがあるかもしれません。しかし、ある程度時間が経てば、これらのコメントは消されるべきです。コメントが作成されてから1週間くらい残しておくのがちょうど良いでしょう。

コメントリポジトリに、拒否されたコメントを取得したり、数を数えたり、削除したりするためのユーティリティメソッドを追加しましょう:

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
49
--- 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,6 +17,8 @@ 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)
@@ -23,6 +26,29 @@ class CommentRepository extends ServiceEntityRepository
         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 \DateTimeImmutable(-self::DAYS_BEFORE_REJECTED_REMOVAL.' days'),
+            ])
+        ;
+    }
+
     public function getCommentPaginator(Conference $conference, int $offset): Paginator
     {
         $query = $this->createQueryBuilder('c')

Tip

より複雑なクエリーが必要な際は、生成されたSQLステートメントを確認すると便利です(これらはログや、Webリクエストのプロファイラで見つけることができます)。

クラス定数、コンテナのパラメーター、環境変数を使用する

7日としましたが、10日にするかもしれないですし、20日にするかもしれません。この数値は時間と共に変更される可能性があります。ここでは、クラスの定数として格納すると決めましたが、コンテナのパラメータとして格納するかもしれませんし、環境変数として定義するかもしれません。

どのアブストラクションを使用するか決める経験則は以下の通りです:

  • 値が注意が必要なもの(パスワードや APIトークン)だった際は、 Symfony の シークレットストレージ かヴォールトを使用してください;
  • 値が動的に変わり、デプロイすることなく 変更したいときは、 環境変数 を使用してください;
  • 値が環境によって異なっていれば、 コンテナパラメーター を使用してください;
  • その他のケースでは、 クラス定数 のようにコードに値を格納してください。

CLI コマンドを作成する

古くなったコメントを削除するのは、Cron ジョブの良いタスクです。そして、定期的に実行されるべきで、少し遅延しても大きな影響はありません。

src/Command/CommentCleanupCommand.php ファイルを作成して、app:comment:cleanup と命名した CLI コマンドを作成します:

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;
    }
}

Symfony にビルトインされている全てのアプリケーションのコマンドは、 symfony console からアクセス可能です。使用可能なコマンドの数は、とても多くなるので、ネームスペースを付けてください。規約として、アプリケーションコマンドは、 app ネームスペース以下に格納してください。そして、コロン(:) で区切りを付けて、サブネームスペースを付けてください。サブネームスペースは1つでも複数個でも良いです。

コマンドは input (コマンドから渡ってきた引数やオプション)を取得し、 コンソールに書き出すのに output を使うことができます。

このコマンドを実行してデータベースをクリーンアップしてください:

1
$ symfony console app:comment:cleanup

Platform.sh で Cron をセットアップする

Platform.sh の便利な点の一つは、ほとんどの設定は .platform.app.yaml に格納されていることです。メンテナンスを楽にするために、Webコンテナ、ワーカー、Cron ジョブが一緒に記述されています:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
--- a/.platform.app.yaml
+++ b/.platform.app.yaml
@@ -60,6 +60,14 @@ crons:
         spec: '50 23 * * *'
         cmd: if [ "$PLATFORM_BRANCH" = "main" ]; then croncape php-security-checker; fi

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

crons セクションは、全ての Cron ジョブを定義しています。各 Cron は、 spec スケジュールに応じて実行されます。

croncap ユーティリティは、コマンドの実行をモニターして、コマンドが 0 でないコードで終わったときに、環境変数 MAILTO に定義されているメールアドレスにメールを送ります。

MAILTO 環境変数を設定する

1
$ symfony cloud:variable:create --sensitive=1 --level=project -y --name=env:MAILTO --value=ops@example.com

Cron は、全ての Platform.sh のブランチにセットアップされています。本番以外の環境で Cron を走らせたくない場合は、環境変数の $PLATFORM_BRANCH をチェックしてください:

1
2
3
if [ "$PLATFORM_BRANCH" = "master" ]; then
    croncape symfony app:invoices:send
fi
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version