Skip to content

Instantly share code, notes, and snippets.

@inventor96
Created July 30, 2026 00:17
Show Gist options
  • Select an option

  • Save inventor96/2fb3d7bd4e7bf9ac9703f611d48e5f3e to your computer and use it in GitHub Desktop.

Select an option

Save inventor96/2fb3d7bd4e7bf9ac9703f611d48e5f3e to your computer and use it in GitHub Desktop.
Auto-load Mako cron commands for use with crunz
# Crunz Configuration Settings
# Required: set Tasks source dir
source: app/console/crunz
# Optional: set logging to Mako's logging dir
log_errors: true
errors_log_file: /var/www/html/app/storage/logs/crunz-errors.log
log_output: true
output_log_file: /var/www/html/app/storage/logs/crunz-output.log
<?php
/*
* app/console/commands/crons/Example.php
*/
namespace app\console\commands\crons;
use app\console\crunz\ScheduleInterface;
use Crunz\Event;
use mako\reactor\attributes\CommandDescription;
use mako\reactor\attributes\CommandName;
use mako\reactor\Command;
#[CommandName('cron:example')]
#[CommandDescription('Wastes the user\'s time for 5 seconds.')]
class Example extends Command implements ScheduleInterface
{
public function schedule(Event $event): void
{
$event->everyMinute();
}
public function execute(): void
{
sleep(5); // Simulate a long-running task
$this->write('Hello there! This command wasted your time for 5 seconds.');
}
}
<?php
/*
* app/console/crunz/MakoCronTasks.php
*/
/**
* Mako cron tasks auto-loader.
*
* This is a Tasks file for phpcrunz/crunz. Task definitions and scheduling are
* delegated to the Mako commands in the app/console/commands/crons/ directory.
* Each command class must implement the app\console\crunz\ScheduleInterface
* interface, which defines a schedule() method that receives a Crunz\Event
* object. The schedule() method is responsible for defining the schedule for
* the command using the Crunz\Event API.
*
* Scheduling crons in this way allows for running cron jobs within a Mako
* application instance, thereby allowing access to the Mako framework's
* features and configuration.
*/
use Crunz\Schedule;
use app\console\crunz\ScheduleInterface;
use mako\cli\Environment;
use mako\cli\input\arguments\ArgvParser;
use mako\cli\input\Input;
use mako\cli\input\reader\ReaderInterface;
use mako\cli\output\Output;
use mako\cli\output\writer\WriterInterface;
use mako\reactor\attributes\CommandDescription;
use mako\reactor\attributes\CommandName;
$runTs = date('Y-m-d H:i:s');
$schedule = new Schedule();
$matchingClasses = [];
$path = realpath(__DIR__ . '/../commands/crons');
$errorFile = (__DIR__ . '/../../storage/logs') . '/crunz-errors.log';
$targetInterface = ScheduleInterface::class;
// ensure the interface itself is loaded first
if (!interface_exists($targetInterface)) {
file_put_contents($errorFile, "[{$runTs}] Interface {$targetInterface} not found. Ensure it is loaded.\n", FILE_APPEND);
return $schedule;
}
// fake the input
$argvParser = new ArgvParser([]);
$reader = new class() implements ReaderInterface {
public function read(): string { return ''; }
public function readCharacter(): string { return ''; }
public function readBytes(int $length): string { return ''; }
};
$input = new Input($reader, $argvParser);
$input->makeNonInteractive();
// fake the writer
$writer = new class() implements WriterInterface {
public string $output = '';
public function setStream($stream): void {}
public function isDirect(): bool { return false; }
public function write(string $string): void { $this->output .= $string; }
};
// fake the environment
$env = new class() extends Environment {
protected ?bool $hasStty = false;
protected ?bool $hasAnsiSupport = false;
protected ?bool $noColor = true;
protected function getDimensionsForUnixLike(): ?array {
return ['width' => self::DEFAULT_WIDTH, 'height' => self::DEFAULT_HEIGHT];
}
};
// create the output
$output = new Output($writer, $writer, $env);
// determine PHP executable path -- IMPORTANT: this probably requires updating to match your environment
$phpExecutable = isset($_SERVER['OS']) && $_SERVER['OS'] === 'Windows_NT'
? str_replace('-cgi', '', PHP_BINARY) // Windows
: '/usr/local/bin/php'; // Docker
// get the Mako env
$makoEnv = getenv('MAKO_ENV');
$envOption = empty($makoEnv) ? '' : "--env={$makoEnv}";
// find all PHP files in the crons directory
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
);
$phpFiles = new RegexIterator($iterator, '/\.php$/');
// process each file
foreach ($phpFiles as $phpFile) {
// construct FQCN
$relativePath = str_replace($path, '', dirname($phpFile->getRealPath()));
$namespace = 'app\\console\\commands\\crons' . str_replace(DIRECTORY_SEPARATOR, '\\', trim($relativePath, '\\/'));
$className = $phpFile->getBasename('.php');
// skip if filename looks like it doesn't match class name
if ($className === '') {
file_put_contents($errorFile, "[{$runTs}] Invalid class name for file {$phpFile->getRealPath()}.\n", FILE_APPEND);
continue;
}
$fqcn = $namespace ? "{$namespace}\\{$className}" : $className;
// double-check if class exists
if (!class_exists($fqcn)) {
file_put_contents($errorFile, "[{$runTs}] Class {$fqcn} not found in file {$phpFile->getRealPath()}.\n", FILE_APPEND);
continue;
}
// check if it implements the interface
$reflection = new ReflectionClass($fqcn);
if ($reflection->implementsInterface($targetInterface)) {
// get command name
$commandNameAttrs = $reflection->getAttributes(CommandName::class);
$commandName = '';
if (isset($commandNameAttrs[0])) {
$commandName = $commandNameAttrs[0]->newInstance()->getName();
}
// can't use the command if it doesn't have a name
if ($commandName === '') {
file_put_contents($errorFile, "[{$runTs}] Command name not found for class {$fqcn}. Ensure it has a #[CommandName('name')] attribute.\n", FILE_APPEND);
continue;
}
// get command description
$commandDescriptionAttrs = $reflection->getAttributes(CommandDescription::class);
$commandDescription = isset($commandDescriptionAttrs[0]) ? ($commandDescriptionAttrs[0]->newInstance()->getDescription() ?: $commandName) : $commandName;
// instantiate and call schedule method
$commandInstance = $reflection->newInstance($input, $output);
$event = $schedule->run("{$phpExecutable} app/reactor {$commandName} {$envOption}");
$commandInstance->schedule($event);
$event->description($commandDescription);
}
}
return $schedule;
<?php
/*
* app/console/crunz/ScheduleInterface.php
*/
namespace app\console\crunz;
use Crunz\Event;
interface ScheduleInterface
{
/**
* Sets the schedule for the cron job. PLEASE NOTE: This method is called
* in the context of the Crunz scheduler, NOT the Mako application context.
* This means that you cannot use any Mako services or helpers in this
* method.
*
* @param Event $event
* @return void
*/
public function schedule(Event $event): void;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment