Create a table in the database (I am using Mysql). Logging tasks in the database can easily enable and disable a task, as well as change it when necessary.
CREATE TABLE `cron_task` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(250) NOT NULL,
`minute` varchar(10) NOT NULL,
`hour` varchar(10) NOT NULL,
`day` varchar(10) NOT NULL,
`month` varchar(10) NOT NULL,
`year` varchar(10) NOT NULL,
`weekday` varchar(10) NOT NULL,
`type` varchar(20) NOT NULL,
`active` tinyint(1) NOT NULL,
`priority` tinyint(3) NOT NULL,
`first_execution` timestamp NULL DEFAULT NULL,
`last_execution` timestamp NULL DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
Use an interface to standardize task classes.
interface ICronTask {
public static function add( ICronTask $task );
public function activate();
public function deactivate();
public function executeTask();
public function makeLog( $content );
}
Where the method add
serves to save a task, activate
and deactivate
to enable and disable the task, executeTask
will contain the context of job execution, be it email sending, url request, backup, or whatever else, and the method makeLog
to generate custom logs for the execution of tasks.
Create an abstract class to implement the basic methods for all tasks.
abstract class CronTask implements ICronTask {
public $id;
public $description;
public $minute;
public $hour;
public $day;
public $month;
public $year;
public $weekday;
public $type;
public $priority;
public $active;
public $first_execution;
public $last_execution;
public $created_at;
public $updated_at;
public $execution_start;
public $execution_end;
public function __construct( $id = null ) {
// caso o $id possua valor, busque informações no banco sobre a tarefa
// e preencha todos os atributos.
}
public final static function add( ICronTask $task ) {
// Salve no banco de dados e chame o método save()
}
public final function activate() {
// Atualize a tarefa no banco para ativar
}
public final function deactivate() {
// Atualize a tarefa no banco para desativar
}
public function makeLog( $content ) {
// Salve o log da tarefa
// Se precisar de um log personalizado para a tarefa, sobrescreva esse método.
}
public final function executeTask() {
// Executa a tarefa
$this->execution_start = round( microtime(true), 4 );
$content = $this->execute();
$this->execution_end = round( microtime(true), 4 );
$this->makeLog($content);
}
public final function isNow() {
// Faz a verificação da hora de execução, garantindo que deve ser executada
// no momento em que for chamado.
return (
$this->parserTime($this->minute, 'i') &&
$this->parserTime($this->hour, 'H') &&
$this->parserTime($this->month, 'm') &&
$this->parserTime($this->day, 'd') &&
$this->parserTime($this->weekday, 'w')
);
}
private function parserTime( $value, $element ) {
// Obtem o tempo atual
$time = date( $element );
// Verifica se o valor é igual à "*" representando toda momento.
if( $value == '*' ) {
return true;
}
// Separa os conjuntos de tempos separados por vírgula
$groups = explode( ',', $value );
foreach ( $groups as $part ) {
// Verifica se é um intervalo composto. Ex: "*/5" ou "20-40/2"
// Se é um intervalo compost, deverá retornar true se o valor atual
// estiver dentro do intervalo definido antes da barra, e na frequência
// definida após a barra.
if( strpos( $part, '/' ) ) {
$groupsInterval = explode( '/', $part );
// Verificando a frequência
$frequency = $time % $groupsInterval[1] == 0;
// Verificando o intervalo
$interval = explode( '-', $groupsInterval[0] );
$intervalResult = false;
if( $interval[0] == '*' ) {
$intervalResult = true;
} else {
$intervalResult = ( $time >= $interval[0] && $time <= $interval[1] );
}
return $frequency && $intervalResult;
}
// Verifica se é um intervalo simples. Ex: "10-50"
// Se é um intervalo, deverá retornar true se o valor atual estiver
// dentro desse intervalo.
if( strpos( $part, '-' ) ) {
$interval = explode( '-', $part );
return $time >= $interval[0] && $time <= $interval[1];
}
// Se for um número simples verifica se é o tempo certo
if( $time == $part ) {
return true;
}
}
return false;
}
abstract protected function execute();
abstract protected function save();
}
And an example of implementing a task class would be
class CronTaskTest extends CronTask {
public $type = 'Test';
public $priority = 0;
protected function execute() {
return 'Tarefa executada com sucesso';
}
protected function save() {
return true;
}
}
The main class that will be executed at all times by checking the tasks, has been implemented as follows:
class Cron {
public static function execute() {
$tasks = self::getTasks();
foreach ( $tasks as $task ) {
if( $task->isNow() ) {
$task->executeTask();
}
}
}
public static function getTasks() {
try {
$tasks = // Busque todas as tarefas ativas ordenadas por prioridade DESC;
$return = array();
foreach ( $tasks as $record ) {
$taskName = 'CronTask' . $record['type'];
require_once __DIR__ . '/tasks/' . $taskName . '.php';
$return[] = new $taskName( $record['id'] );
}
} catch ( PDOException $exception ) {
die( $exception->getMessage() );
}
return $return;
}
}
Create a PHP file to perform tasks by calling the method Cron::execute()
.
Schedule in the CRON
# crontab -e
* * * * * /usr/local/bin/php /var/www/projeto/meu-script.php
The server is remote? Why could you use his task scheduler to run the script. I had the same problem and solved so.
– StillBuggin
Alternative to cronjob (linux) or schtask(windows), which I know, you don’t have. What you’ll be able to do is some sort of "loop.
– Daniel Omine
If you can put a call to your application at machine startup, you don’t need cron. You can control the ranges directly in the application. The advantage would be to have independent control of the cron, and be able to change the behavior of the task without needing administrative access (except in the installation). This is because PHP can loop when running from the console, just disable the timeout. This avoids the script needing administrative access as well. And if it is by the console, the loop (since made with Sleep not to consume resources for no reason) is definitely not gambiarra.
– Bacco