2016-09-27 7 views
2

내 사이트의 관리자가 특정 스크립트를 실행하기 위해 서버에서 cron 작업을 설정하는 것과 비슷한 작업을 예약 할 수있는 방법을 만들려고합니다. 작업이 실행될 때 비슷한 제어권을 갖기를 바랍니다. 매일 14:00 또는 매주 목요일 12:00 등.PHP 작업 스케줄러 사이트 관리자가 구성

나는 일과 주간 등의 작업을 얼마나 자주 수행 할 것인지 질문하는 양식을 가지고 있다고 생각했습니다. 데이터베이스에 저장됩니다. 다음으로 매분마다 스크립트를 실행하는 cron 작업을 생성합니다. 이 스크립트는 데이터베이스에서 각 작업을 실행하고 실행해야하는 모든 작업을 선택합니다.

나는이 작업 스케줄러를보고 있으며, 지금까지는 대부분 웹 개발자가 프로그래밍 방식으로 작업을 예약하도록 설계된 것처럼 보였다. 대신 데이터베이스에 저장하고 SQL 쿼리를 작성하여 올바른 작업을 선택하여 실행하고 싶습니다. 데이터베이스에 일정을 저장하기 위해 어떤 구조를 사용해야하는지, 그리고 특정 시간에 실행되도록 올바른 작업을 검색하는 방법에 대해 궁금합니다.

누군가가 올바른 방향으로 나를 가리킬 수 있다면 정말 고맙겠습니다.

+0

이 당신이 그들을 제공 얼마나 많은 세부적인 제어 및 접근성에 따라 달라집니다? 이 관리자는 PHP 파일을 업로드 할 수 있습니까? 데이터베이스 항목에서 txt 파일을 생성하고 실제로 crontab을 수정할 수 있습니다. – Blake

+0

[PHP를 사용하여 crontab 작업을 생성, 편집 및 삭제할 수 있습니까?] (http://stackoverflow.com/questions/4421020/use-php-to-create-edit-and-delete-crontab-jobs) – Hackerman

+1

@ Hackerman은 op가 비 cron 접근 방식을 원했던 것처럼 보입니다. 그럼 누가 다시 알지. – Drew

답변

2

다음은 과거 프로젝트에서 구현 한 방법을 간략하게 설명한 예입니다. 간결성을 위해 보안 고려 사항을 생략했지만 사용자에게 실행 명령을 지정하게하는 것이 본질적으로 안전하지 않다는 점에 유의하십시오.

작업 SQL 표

당신이 소비하는 당신의 실행 스크립트 설정이 세 개의 열이 필요합니다. 간격 열은 cron 문자열 (분 시간 일 월 년)입니다. script_path 열은 스크립트가 실행될 경로입니다. last_executed 열은 해당 작업이 마지막으로 실행 된 시간입니다. interval 및 last_executed 열은 작업 실행 여부를 결정하는 데 사용됩니다.

+----+------------+----------------------+---------------------+ 
| id | interval |  script_path  | last_executed | 
+----+------------+----------------------+---------------------+ 
| 1 | 5 * * * * | /path/to/script1.php | 2016-01-01 00:00:00 | 
+----+------------+----------------------+---------------------+ 
| 2 | * 12 * * * | /path/to/script2.php | 2016-01-01 00:00:00 | 
+----+------------+----------------------+---------------------+ 

작업 실행 스크립트

이 스크립트는 cron 작업을 통해 분마다 실행됩니다.

#/usr/bin/env php 
<?php 

// Get tasks from the database 
$db = new PDO('dsn', 'username', 'password'); 
$stmt = $db->prepare('SELECT * FROM `tasks`'); 
$stmt->execute(); 
$tasks = $stmt->fetchAll(PDO::FETCH_OBJ); 

foreach ($tasks as $task) { 
    $timestamp = time(); 
    $lastExecutedTimestamp = strtotime($task->last_executed); 
    // Convert cron expression to timestamp 
    $intervalTimestamp = $task->interval; 

    // Check if the task should be run. 
    if ($timestamp - $lastExecutedTimestamp >= $intervalTimestamp) { 
     // Execute task 
     // ... 

     // Update the task's last_executed time. 
     $stmt = $db->prepare('UPDATE `tasks` SET `last_executed` = ? WHERE `id` = ?'); 
     $stmt->execute([date('Y-m-d H:i:s', $timestamp), $task->id]); 
    } 
} 
+0

cron-syntax interval 필드는 시간 비교에 사용하기 위해 의미있는 조작을 필요로합니다. –

+0

@MikeBrant 구현 세부 사항이며이 답변을 위해이 질문을 남겨 두었습니다. OP는 [많은] (https://packagist.org/search/?q=cron+expression+parser) cron 파싱 라이브러리 중에서이 구현을 사용하도록 선택해야합니다. – Enijar

+0

고마워. 내가 뭘했는지 정확히 알 수있어. 나는 시험의 abit을 가지고있을 것이고, 모든 것이 괜찮은 것이라면 나는 그것을 대답으로 표시 할 것이다. – nfplee

0

아이디어가 상당히 간단하고 꽤 잘 잡힌 것처럼 보입니다. 관리자가 스케줄 할 수있는 정의 된 "태스크"세트가있는 경우, 태스크를 실행할시기의 시간 소인과 함께 데이터베이스 테이블에 저장하는 것이 간단합니다. 그런 다음 필요한만큼 자주 실행되도록 예약하는 (예 : cron을 통해) 단일 스크립트 (예 : "job_runner.php")를 갖게됩니다 (이것은 반드시 정의해야하는 비즈니스 요구 사항입니다).

관리자가 일정을 위해 당신은 당신의 작업을 정의 할 수 있습니다와 같은 - 그래서 :

interface JobInterface { 
    public function run(); 
} 

class RunSalesReport implements JobInterface { 
    public function run(){ 
     // .. business logic 
    }; 

    // or maybe just __invoke() would be fine! your call! 
} 
귀하의 "작업 스케줄러"웹 양식은 관리자가 실행되도록 예약 할 수있는 작업 목록을 보유 할

예 목록은 앞에서 설명한 RunSalesReport 클래스와 관련된 "영업 보고서 실행"작업을 포함 할 수 있습니다. 웹 양식의 서버 측 핸들러는 양식 데이터를 데이터베이스 테이블에 저장합니다.

데이터베이스 테이블에는 time_to_run 열 (작업 실행시기 결정)과 job_class 열 (인스턴스화/인수/기타 사항을 포함해야하는 클래스 이름 보유)이 포함될 수 있습니다.

"job_runner.php"파일은 단순히 데이터 영역에 연결되어 실행되도록 예약되었지만 아직 실행되지 않은 "작업"을 찾습니다 ("실행"으로 플래그를 지정하거나 테이블을 실행 한 후, 귀하의 전화).

// job_runner.php - executed via cron however often you need it to be 
// if admin can only schedule jobs on the hour, then run on the hour, etc. 
$jobs = $pdo->execute("SELECT * FROM scheduled_jobs WHERE DATE(time_to_run) <= DATE(NOW())"); 
foreach($pdo->fetchAll($jobs) as $jobRow){ 
    $jobClassName = $jobRow['job_class']; 
    $job = new $jobClassName; // or get from IOC container, your decision 
    $job->run(); 
} 
+0

좋은 답변입니다. 내가 제안 할 수있는 것은 DB에있는'time_to_run' 필드는 원시 datetime 또는 timestamp 필드 여야한다는 것입니다. 그런 식으로 당신의 Cluase는 단순히'WHERE time_to_run <= NOW()'가 될 것입니다. DATE로 캐스팅하는 접근법 (어쨌든 작업 관리자를 위해 충분히 세밀하지 않을 수도 있습니다)은'time_to_run'에 인덱스를 사용할 수 없게합니다. 이 쿼리. –

2

다른 대답은 여기에 있습니다. 또한 자신이

(GUI 관리 도구에서 달력에 예약 된 모든 작업을 표시처럼) 더 복잡한 일 처리를 할 필요 발견하면 당신은 PHP의 DateTime, DateInterval, DatePeriod 및 관련 클래스를 사용하여 고려를해야한다는 지적 것 당신은 같은 것을 볼 것 DB 테이블을 포함하는 작업 일정 규칙이있을 수 있습니다

id - unique auto-increment 
name - human-readable task name 
owner - perhaps forieg key to user tables so you know who owns tasks 
interval - An string interval specification as used in DateInterval 
start_time - Datetime When rule goes into effect 
end_time - Datetime When rule is no longer in effect 
script_path - path to script of some sort of command recognized by your applcation 
last_execution - Datetime for last time script was triggered 
next_execution - Datetime in which you store value calculated to be next execution point 
active - maybe a flag to enable/disable a rule 
perhaps other admin fields like created_time, error_tracking, etc. 

을 그리고 당신은 쉽게 DatePeriod는 각 테이블 행에서의 반복 수있는 개체의 컬렉션을 만들 수있다.

다음 $tasks
// have one authoritative now that you use in this script 
$now = DateTime(); 
$now_sql = $now->format('Y-m-d H:i:s'); 


$sql = <<<EOT 

SELECT 
    id, 
    name, 
    interval, 
    /* etc */ 
FROM task_rules 
WHERE 
    active = 1 
    AND 
     (IS_NULL(start_time) OR start_time <= '{$now_sql}') 
    AND 
     (IS_NULL(end_time) OR eend_time > '{$now_sql}') 
    /* Add this filter if you are trying to query this table 
     for overdue events */ 
    AND 
     next_execution <= '{$now_sql}' 
    /* any other filtering you might want to do */ 
/* Any ORDER BY and LIMIT clauses */ 

EOT; 


$tasks = array(); 
//logic to read rows from DB 
while ($row = /* Your DB fetch mechanism */) { 
    // build your task (probably could be its own class, 
    // perhaps saturated via DB retrieval process), but this is jist 
    $task = new stdClass(); 
    $task->id = $row->id 
    $task->name = $row->name; 
    $task->interval = $row->interval; 
    $task->start_time = $row->start_time; 
    // etc. basically map DB row to an object 

    // start building DateTime and related object representations 
    // of your tasks 
    $task->dateInterval = new DateInterval($task->interval); 

    // determine start/end dates for task sequence 
    if(empty($task->start_time)) { 
     // no defined start date, so build start date from last executed time 
     $task->startDateTime = DateTime::createFromFormat(
      'Y-m-d H:i:s', 
      $task->last_execution 
     ); 
    } else { 
     // start date known, so we want to base period sequence on start date 
     $task->startDateTime = DateTime::createFromFormat(
      'Y-m-d H:i:s', 
      $task->start_date 
     ); 
    } 

    if(empty($task->end_time)) { 
     // No defined end. So set artificial end date based on app needs 
     // (like we need to show next week, month, year) 
     $end_datetime = clone $now; 
     $end_datetime->modify(+ 1 month); 
     $task->endDateTime = $end_datetime; 
    } else { 
     $task->endDateTime = DateTime::createFromFormat(
      'Y-m-d H:i:s', 
      $task->end_time 
     ); 
    } 

    $task->datePeriod = new DatePeriod(
     $task->startDateTime, 
     $task->dateInterval, 
     $task->endDateTime 
    ); 

    // iterate datePeriod to build array of occurences 
    // which is more useful than just working with Traversable 
    // interface of datePeriod and allows you to filter out past 
    // scheduled occurences 
    $task->future_occurrences = []; 
    foreach ($task->datePeriod as $occurence) { 
     if ($occurence < $now) { 
      // this is occcurrence in past, do nothing 
      continue; 
     } 

     $task->future_occurrences[] = $occurence; 
    } 

    $task->nextDateTime = null;  
    if(count($task->future_occurrences) > 0) { 
     $task->nextDateTime = $task->future_occurrences[0]; 
     $task->next_execution = $task->nextDateTime->format('Y-m-d H:i:s'); 
    }  

    $tasks[] = $task; 
} 

이 유형 PHP 날짜 시간과 함께 하나의 규칙을 나타내는 각, DatePeriod 당신이 및/또는 디스플레이 작업을 실행하는 데 사용할 수있는 구축 객체의 배열을 포함됩니다 : 즉 같은 것을 볼 수 있습니다. 예를 들어

는 :

// execute all tasks 
// just using a simple loop example here 
foreach($tasks as $task) { 
    $command = 'php ' . $task->script_path; 
    exec($command); 

    // update DB 
    $sql = <<<EOT 

UPDATE task_rules 
SET 
    last_execution = '{$now_sql}', 
    next_execution = '{$task->next_execution}' 
WHERE id = {$task->id} 

EOT; 

    // and execute using DB tool of choice 
}