1. Go to this page and download the library: Download nemanjajojic/tick library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
nemanjajojic / tick example snippets
declare(strict_types=1);
use DateTimeZone;
use NemanjaJojic\Tick\Clock\SystemClock;
use NemanjaJojic\Tick\Lock\FileLock;
use NemanjaJojic\Tick\Scheduler;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
$dispatcher = new TaskDispatcher(new FileLock('/var/run/scheduler'));
$scheduler = new Scheduler($dispatcher, new SystemClock(), new DateTimeZone('Europe/Belgrade'));
// 1. Closure: inline logic with explicit name
$scheduler->call('cleanup', static fn (): void => unlink('/tmp/cache.tmp'))
->daily()
->describe('Removes the temporary cache.');
// 2. Invokable: container-resolved TaskInterface instance
$scheduler->invoke('reports', $container->get(GenerateReports::class))
->cron('*/15 * * * *');
// 3. Raw: external command as array (no shell interpolation).
// Works for any binary or interpreter — PHP, Python, Go, shell, etc.
$scheduler->raw('backup', ['rsync', '-a', 'src/', 'dst/'])
->hourly();
$scheduler->raw('etl', ['/usr/bin/python3', __DIR__ . '/etl.py', '--full'])
->daily();
$scheduler->raw('migrate', [PHP_BINARY, __DIR__ . '/migrate.php', '--force'])
->daily();
$scheduler->run();
// cron/scheduler.php
declare(strict_types=1);
use DateTimeZone;
use NemanjaJojic\Tick\Clock\SystemClock;
use NemanjaJojic\Tick\Lock\FileLock;
use NemanjaJojic\Tick\Retry\JitteredExponentialBackoffRetryPolicy;
use NemanjaJojic\Tick\Scheduler;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
uler->invoke('generate-reports', new GenerateReports())
->dailyAt('06:00');
$scheduler->raw('backup', ['rsync', '-a', '/var/data/', '/mnt/backup/'])
->dailyAt('02:00')
->withoutOverlapping(ttlSeconds: 7200);
$scheduler->raw('etl', ['/usr/bin/python3', __DIR__ . '/../scripts/etl.py'])
->everyNMinutes(15)
->retry(new JitteredExponentialBackoffRetryPolicy(
maxAttempts: 3,
baseDelayMs: 500,
maxDelayMs: 5000,
));
$scheduler->run();
declare(strict_types=1);
use NemanjaJojic\Tick\Task\TaskInterface;
final readonly class GenerateReports implements TaskInterface
{
public function __construct() {
}
public function __invoke(): void
{
// your business logic here
}
}
// bin/my-scheduler
declare(strict_types=1);
use DateTimeZone;
use DI\ContainerBuilder;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use NemanjaJojic\Tick\Clock\SystemClock;
use NemanjaJojic\Tick\Console\ListTasksCommand;
use NemanjaJojic\Tick\Console\RunCommand;
use NemanjaJojic\Tick\Console\RunTaskCommand;
use NemanjaJojic\Tick\Console\UpcomingCommand;
use NemanjaJojic\Tick\Lock\FileLock;
use NemanjaJojic\Tick\Retry\JitteredExponentialBackoffRetryPolicy;
use NemanjaJojic\Tick\Scheduler;
use NemanjaJojic\Tick\Task\Dispatcher\DryRunTaskDispatcher;
use NemanjaJojic\Tick\Task\Dispatcher\LoggingTaskDispatcherDecorator;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
use Symfony\Component\Console\Application;
($innerDispatcher, $logger);
$scheduler = new Scheduler(
$dispatcher,
new SystemClock(),
new DateTimeZone('Europe/Belgrade'),
);
// Container-resolved task: PHP-DI does the wiring
$scheduler->invoke('daily-report', $container->get(App\Task\DailyReportTask::class))
->dailyAt('08:00')
->withoutOverlapping();
$scheduler->invoke('sync-inventory', $container->get(App\Task\SyncInventoryTask::class))
->everyNMinutes(5)
->retry(new JitteredExponentialBackoffRetryPolicy(
maxAttempts: 3,
baseDelayMs: 500,
maxDelayMs: 5000,
));
$scheduler->call('cleanup-temp', static function () use ($container): void {
$container->get(App\Service\TempFileService::class)->cleanup();
})
->hourly()
->skip(static fn (): bool => 'maintenance' === getenv('APP_MODE'));
$application = new Application('my-scheduler', '1.0.0');
$application->add(new RunCommand($scheduler));
$application->add(new ListTasksCommand($scheduler));
$application->add(new RunTaskCommand($scheduler));
$application->add(new UpcomingCommand($scheduler));
$application->run();
// config/container.php
declare(strict_types=1);
use DateTimeZone;
use NemanjaJojic\Tick\Clock\SystemClock;
use NemanjaJojic\Tick\Console\ListTasksCommand;
use NemanjaJojic\Tick\Console\RunCommand;
use NemanjaJojic\Tick\Console\RunTaskCommand;
use NemanjaJojic\Tick\Console\UpcomingCommand;
use NemanjaJojic\Tick\Lock\FileLock;
use NemanjaJojic\Tick\Scheduler;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
use Psr\Container\ContainerInterface;
use function DI\autowire;
use function DI\factory;
return [
Scheduler::class => factory(static function (ContainerInterface $container): Scheduler {
$scheduler = new Scheduler(
new TaskDispatcher(new FileLock(__DIR__ . '/../var/locks')),
new SystemClock(),
new DateTimeZone('Europe/Belgrade'),
);
$scheduler->invoke('daily-report', $container->get(App\Task\DailyReportTask::class))
->dailyAt('08:00')
->withoutOverlapping();
$scheduler->invoke('sync-inventory', $container->get(App\Task\SyncInventoryTask::class))
->everyNMinutes(5);
return $scheduler;
}),
// Tick's command classes — PHP-DI autowires the Scheduler constructor argument.
RunCommand::class => autowire(),
ListTasksCommand::class => autowire(),
RunTaskCommand::class => autowire(),
UpcomingCommand::class => autowire(),
];
// bin/console
declare(strict_types=1);
use DI\ContainerBuilder;
use NemanjaJojic\Tick\Console\ListTasksCommand;
use NemanjaJojic\Tick\Console\RunCommand;
use NemanjaJojic\Tick\Console\RunTaskCommand;
use NemanjaJojic\Tick\Console\UpcomingCommand;
use Symfony\Component\Console\Application;
stTasksCommand::class));
$application->add($container->get(RunTaskCommand::class));
$application->add($container->get(UpcomingCommand::class));
$application->run();
$scheduler->call('backup', static fn (): int => 0)
->daily()
->describe('Nightly database backup to S3.');
use NemanjaJojic\Tick\Lock\LockInterface;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
$dispatcher = new TaskDispatcher(
lock: new class implements LockInterface {
public function acquire(string $key, int $ttlSeconds): bool
{
return true;
}
public function release(string $key): void
{
}
},
// ...
);
declare(strict_types=1);
namespace App\Lock;
use NemanjaJojic\Tick\Lock\LockInterface;
final class RedisLock implements LockInterface
{
public function __construct(
private readonly \Redis $redis,
) {
}
public function acquire(string $key, int $ttlSeconds): bool
{
return (bool) $this->redis->set(
'scheduler:lock:' . $key,
'1',
['NX', 'EX' => $ttlSeconds],
);
}
public function release(string $key): void
{
$this->redis->del('scheduler:lock:' . $key);
}
}
declare(strict_types=1);
namespace App\Retry;
use Closure;
use NemanjaJojic\Tick\Retry\RetryPolicyInterface;
use NemanjaJojic\Tick\Task\TaskResult;
final class CircuitBreakerRetryPolicy implements RetryPolicyInterface
{
public function execute(Closure $task): TaskResult
{
// Your custom retry logic
}
}
declare(strict_types=1);
$scheduler->invoke('heavy-aggregation', $container->get(App\Task\HeavyAggregationTask::class))
->everyNMinutes(5)
->skip(static function (): bool {
$hour = (int) (new \DateTimeImmutable('now', new \DateTimeZone('Europe/Belgrade')))->format('H');
return 2 === $hour;
});
declare(strict_types=1);
use NemanjaJojic\Tick\Lock\FileLock;
use NemanjaJojic\Tick\Task\Dispatcher\LoggingTaskDispatcherDecorator;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
$inner = new TaskDispatcher(new FileLock('/var/run/scheduler'));
$dispatcher = new LoggingTaskDispatcherDecorator($inner, $logger);
$scheduler = new Scheduler($dispatcher, $clock, $timezone);
declare(strict_types=1);
use NemanjaJojic\Tick\Lock\FileLock;
use NemanjaJojic\Tick\Task\Dispatcher\DryRunTaskDispatcher;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
$isDryRun = 'true' === getenv('SCHEDULER_DRY_RUN');
$dispatcher = true === $isDryRun
? new DryRunTaskDispatcher()
: new TaskDispatcher(new FileLock('/var/run/scheduler'));
declare(strict_types=1);
use NemanjaJojic\Tick\Lock\FileLock;
use NemanjaJojic\Tick\Task\Dispatcher\TaskDispatcher;
use NemanjaJojic\Tick\Task\Dispatcher\TimingTaskDispatcherDecorator;
$taskDispatcher = new TaskDispatcher(new FileLock('/var/run/scheduler'));
$timedDispatcher = new TimingTaskDispatcherDecorator($taskDispatcher);
$scheduler = new Scheduler($timedDispatcher, $clock, $timezone);
declare(strict_types=1);
use DateTimeZone;
// "09:00" runs at 09:00 Europe/Belgrade local time
$scheduler = new Scheduler(
$dispatcher,
new SystemClock(),
new DateTimeZone('Europe/Belgrade'),
);
$scheduler->invoke('daily-report', $container->get(App\Task\DailyReportTask::class))
->dailyAt('09:00');
declare(strict_types=1);
$belgradeScheduler = new Scheduler($dispatcher, $clock, new DateTimeZone('Europe/Belgrade'));
$belgradeScheduler->invoke('belgrade-task', $container->get(App\Task\BelgradeTask::class))
->dailyAt('09:00');
$berlinScheduler = new Scheduler($dispatcher, $clock, new DateTimeZone('Europe/Berlin'));
$berlinScheduler->invoke('berlin-task', $container->get(App\Task\BerlinTask::class))
->dailyAt('09:00');
$belgradeScheduler->run();
$berlinScheduler->run();
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.