PHP code example of nemanjajojic / tick

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.');



declare(strict_types=1);

$scheduler->invoke('long-running', $container->get(App\Task\LongRunningTask::class))
    ->everyNMinutes(1)
    ->withoutOverlapping(ttlSeconds: 3600);

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

use NemanjaJojic\Tick\Retry\ConstantBackoffRetryPolicy;

$scheduler->invoke('my-task', $container->get(App\Task\MyTask::class))
    ->everyNMinutes(1)
    ->retry(new ConstantBackoffRetryPolicy(
        maxAttempts: 3,
        delayMs: 1000,
    ));



declare(strict_types=1);

use NemanjaJojic\Tick\Retry\LinearBackoffRetryPolicy;

$scheduler->invoke('my-task', $container->get(App\Task\MyTask::class))
    ->everyNMinutes(1)
    ->retry(new LinearBackoffRetryPolicy(
        maxAttempts: 5,
        baseDelayMs: 100,
        incrementMs: 200,
        maxDelayMs: 1000,
    ));



declare(strict_types=1);

use NemanjaJojic\Tick\Retry\JitteredExponentialBackoffRetryPolicy;

$scheduler->invoke('my-task', $container->get(App\Task\MyTask::class))
    ->everyNMinutes(1)
    ->retry(new JitteredExponentialBackoffRetryPolicy(
        maxAttempts: 5,
        baseDelayMs: 100,
        maxDelayMs: 1000,
    ));



declare(strict_types=1);

use NemanjaJojic\Tick\Retry\DecorrelatedJitterRetryPolicy;

$scheduler->invoke('my-task', $container->get(App\Task\MyTask::class))
    ->everyNMinutes(1)
    ->retry(new DecorrelatedJitterRetryPolicy(
        maxAttempts: 5,
        baseDelayMs: 100,
        maxDelayMs: 1000,
    ));



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('sync-reporting', $container->get(App\Task\SyncReportingTask::class))
    ->hourly()
    ->when(static fn (): bool => $flags->isEnabled('reporting.sync'));



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();