Skip to content

Commit

Permalink
Use Twig for placeholders
Browse files Browse the repository at this point in the history
  • Loading branch information
loevgaard committed Sep 3, 2024
1 parent 40aa4e2 commit 11af160
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 45 deletions.
5 changes: 4 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/.github export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/tests export-ignore
/ecs.php export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
/README.md export-ignore
/rector.php export-ignore
57 changes: 39 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
[![Build Status][ico-github-actions]][link-github-actions]
[![Coverage Status][ico-code-coverage]][link-code-coverage]

Build your crontab from configuration files in your repository. The configuration files are written in PHP and can be placed anywhere in your repository.

## Installation

```bash
$ composer require setono/cron-builder
composer require setono/cron-builder
```

## Usage
Expand All @@ -18,45 +20,64 @@ The following two code snippets outlines the simplest usage of the cron builder.

**1. Add your commands**

The default directory to look for cronjobs is inside `etc/cronjobs`. This can be changed in the options of the `CronBuilder`.
```php
<?php
# etc/cronjobs/jobs.php

Here we define two jobs. Two PHP commands that run on the given time.
declare(strict_types=1);

```yaml
# etc/cronjobs/jobs.yaml
use Setono\CronBuilder\CronJob;

- schedule: "0 0 * * *"
command: "php first-script.php"
return static function (array $context): iterable {
yield new CronJob('0 0 * * *', '/usr/bin/php {{ release_path }}/send-report.php {{ args|join(" ") }}', 'Run every day at midnight');

- schedule: "1 0 * * *"
command: "php second-script.php"
if ($context['env'] ?? '' === 'prod') {
yield new CronJob('0 0 * * *', '/usr/bin/php {{ release_path }}/process.php {{ args|join(" ") }}');
}
};
```

**Notice** the `{{ release_path }}` and `{{ args|join(" ") }}` placeholders.
These are placeholders that will be replaced with the values from the context array.
This is done using the [Twig](https://twig.symfony.com/) templating engine, which means
you can use all the features of Twig in your cronjob definitions.

**2. Build the crontab**

When your cronjobs are defined, you can build the crontab file:
When your cronjobs are defined, you can output the crontab file:

```php
<?php
use Setono\CronBuilder\CronBuilder;

$cronBuilder = new CronBuilder();
$crontab = $cronBuilder->build();
use Symfony\Component\Finder\Finder;

echo (new CronBuilder())
->addFiles(
(new Finder())
->files()
->in(__DIR__ . '/etc/cronjobs')
->name('*.php')
)
->addContext('release_path', '/home/johndoe/public_html')
->addContext('env', 'prod')
->addContext('args', ['--verbose'])
->build()
;
```

This will output the following:

```text
###> Automatically generated by Setono Cron Builder - Do not edit ###
0 0 * * * php first-script.php
1 0 * * * php second-script.php
###< Automatically generated by Setono Cron Builder - Do not edit ###
###> Automatically generated by Setono Cron Builder - DO NOT EDIT ###
0 0 * * * /usr/bin/php /home/johndoe/public_html/send-report.php --verbose # Run every day at midnight
0 0 * * * /usr/bin/php /home/johndoe/public_html/process.php --verbose
###< Automatically generated by Setono Cron Builder - DO NOT EDIT ###
```

You can save this in a file named `crontab.txt` and add it to your crontab like this:

```bash
$ cat crontab.txt | crontab -
cat crontab.txt | crontab -
```

[ico-version]: https://poser.pugx.org/setono/cron-builder/v/stable
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
],
"require": {
"php": ">=8.1",
"dragonmantank/cron-expression": "^2.3 || ^3.0"
"dragonmantank/cron-expression": "^2.3 || ^3.0",
"twig/twig": "2.0 || ^3.0"
},
"require-dev": {
"infection/infection": "^0.27.11",
Expand Down
31 changes: 16 additions & 15 deletions src/CronBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@

namespace Setono\CronBuilder;

use Twig\Environment;
use Twig\Loader\ArrayLoader;

final class CronBuilder
{
private readonly Environment $twig;

private string $delimiter = 'Automatically generated by Setono Cron Builder - DO NOT EDIT';

/**
Expand All @@ -15,9 +20,14 @@ final class CronBuilder
*/
private array $files = [];

/** @var array<string, scalar> */
/** @var array<string, mixed> */
private array $context = [];

public function __construct(Environment $twig = null)
{
$this->twig = $twig ?? new Environment(new ArrayLoader());
}

/**
* @param 'begin'|'end' $type
*/
Expand Down Expand Up @@ -68,10 +78,13 @@ public function addFiles(iterable $files): self
}

/**
* @param array<string, scalar> $context
* @param array<string, mixed> $context
*/
public function setContext(array $context): self
{
/**
* @var mixed $value
*/
foreach ($context as $key => $value) {
$this->addContext($key, $value);
}
Expand All @@ -81,10 +94,6 @@ public function setContext(array $context): self

public function addContext(string $key, mixed $value): self
{
if (!is_scalar($value)) {
throw new \InvalidArgumentException(sprintf('The value must be a scalar, got %s for key %s', gettype($value), $key));
}

$this->context[$key] = $value;

return $this;
Expand Down Expand Up @@ -167,15 +176,7 @@ public static function merge(string $existingCron, self $cronBuilder): string

private function parse(string $value): string
{
$search = [];
$replace = [];

foreach ($this->context as $key => $val) {
$search[] = '%' . $key . '%';
$replace[] = self::scalarToString($val);
}

return str_replace($search, $replace, $value);
return $this->twig->createTemplate($value)->render($this->context);
}

private static function scalarToString(mixed $value): string
Expand Down
10 changes: 6 additions & 4 deletions src/CronJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@

final class CronJob implements \Stringable
{
public readonly string $schedule;
public readonly CronExpression $schedule;

public function __construct(
string $schedule,
CronExpression|string $schedule,
public readonly string $command,
public readonly ?string $description = null,
) {
new CronExpression($schedule);
if (is_string($schedule)) {
$schedule = new CronExpression($schedule);
}

$this->schedule = $schedule;
}
Expand All @@ -24,7 +26,7 @@ public function toString(): string
{
return sprintf(
'%s %s%s',
$this->schedule,
(string) $this->schedule,
$this->command,
$this->description === null ? '' : (' # ' . $this->description),
);
Expand Down
9 changes: 5 additions & 4 deletions tests/CronBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function it_builds(): void

$expected = <<<CRON
###> Automatically generated by Setono Cron Builder - DO NOT EDIT ###
0 0 * * * /usr/bin/php /home/johndoe/public_html/send-report.php # Run every day at midnight
0 0 * * * /usr/bin/php /home/johndoe/public_html/send-report.php --verbose # Run every day at midnight
###< Automatically generated by Setono Cron Builder - DO NOT EDIT ###
CRON;
Expand All @@ -38,8 +38,8 @@ public function it_builds_with_prod_env_set(): void

$expected = <<<CRON
###> Automatically generated by Setono Cron Builder - DO NOT EDIT ###
0 0 * * * /usr/bin/php /home/johndoe/public_html/send-report.php # Run every day at midnight
0 0 * * * /usr/bin/php /home/johndoe/public_html/process.php
0 0 * * * /usr/bin/php /home/johndoe/public_html/send-report.php --verbose # Run every day at midnight
0 0 * * * /usr/bin/php /home/johndoe/public_html/process.php --verbose
###< Automatically generated by Setono Cron Builder - DO NOT EDIT ###
CRON;
Expand Down Expand Up @@ -70,7 +70,7 @@ public function it_merges(): void
0 0 * * * /usr/bin/php /home/johndoe/public_html/other-job.php
###> Automatically generated by Setono Cron Builder - DO NOT EDIT ###
0 0 * * * /usr/bin/php /home/johndoe/public_html/send-report.php # Run every day at midnight
0 0 * * * /usr/bin/php /home/johndoe/public_html/send-report.php --verbose # Run every day at midnight
###< Automatically generated by Setono Cron Builder - DO NOT EDIT ###
CRON;
Expand All @@ -83,6 +83,7 @@ private static function getCronBuilder(): CronBuilder
return (new CronBuilder())
->addFiles((new Finder())->files()->in(__DIR__ . '/cronjobs'))
->addContext('release_path', '/home/johndoe/public_html')
->addContext('args', ['--verbose'])
;
}
}
4 changes: 2 additions & 2 deletions tests/cronjobs/jobs.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
use Setono\CronBuilder\CronJob;

return static function (array $context): iterable {
yield new CronJob('0 0 * * *', '/usr/bin/php %release_path%/send-report.php', 'Run every day at midnight');
yield new CronJob('0 0 * * *', '/usr/bin/php {{ release_path }}/send-report.php {{ args|join(" ") }}', 'Run every day at midnight');

if ($context['env'] ?? '' === 'prod') {
yield new CronJob('0 0 * * *', '/usr/bin/php %release_path%/process.php');
yield new CronJob('0 0 * * *', '/usr/bin/php {{ release_path }}/process.php {{ args|join(" ") }}');
}
};

0 comments on commit 11af160

Please sign in to comment.