こんにちは!今回は、PHPの標準関数であるset_time_limit()について詳しく解説していきます。PHPスクリプトの最大実行時間を制御できる、重要な関数です!
set_time_limit関数とは?
set_time_limit()関数は、PHPスクリプトの最大実行時間を秒単位で設定する関数です。
デフォルトでは30秒に設定されていますが、長時間実行が必要な処理(バッチ処理、データインポート、画像処理など)では、この制限を変更する必要があります!
基本的な構文
set_time_limit(int $seconds): bool
- $seconds: 最大実行時間(秒単位)、
0で無制限 - 戻り値: 成功時は
true、失敗時はfalse
重要な注意点
// セーフモードが有効な場合、set_time_limit()は無効
// php.iniのmax_execution_timeの設定が優先される場合がある
// set_time_limit()は実行時間をリセット
// この関数が呼ばれた時点から新たにカウント開始
// スリープ時間は含まれない
set_time_limit(30);
sleep(10); // この10秒は実行時間にカウントされない
// 外部プログラムの実行時間は含まれる
set_time_limit(30);
exec('long-running-command'); // この時間は含まれる
基本的な使用例
シンプルな時間制限設定
// デフォルトは30秒
echo "デフォルトの制限: " . ini_get('max_execution_time') . "秒\n";
// 60秒に設定
set_time_limit(60);
echo "新しい制限: " . ini_get('max_execution_time') . "秒\n";
// 長時間の処理
for ($i = 0; $i < 1000000; $i++) {
// 何らかの処理
}
echo "処理完了\n";
無制限の実行時間
// 制限を解除(無制限)
set_time_limit(0);
// 非常に長い処理でもタイムアウトしない
while (true) {
// 処理
if ($someCondition) {
break;
}
}
処理の途中でリセット
// 初期設定: 60秒
set_time_limit(60);
for ($i = 0; $i < 10; $i++) {
echo "Processing batch {$i}\n";
// 各バッチの処理
processBatch($i);
// 制限時間をリセット(この時点から60秒)
set_time_limit(60);
}
エラーハンドリング
// set_time_limit()が失敗する場合がある
if (set_time_limit(300)) {
echo "制限時間を300秒に設定しました\n";
} else {
echo "制限時間の設定に失敗しました\n";
echo "セーフモードまたは無効化されている可能性があります\n";
}
// または例外処理
try {
ini_set('max_execution_time', 300);
set_time_limit(300);
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
実践的な使用例
例1: バッチ処理マネージャー
class BatchProcessor {
private $timeLimit;
private $batchSize;
private $resetInterval;
/**
* バッチ処理を初期化
*/
public function __construct($timeLimit = 300, $batchSize = 100, $resetInterval = 10) {
$this->timeLimit = $timeLimit;
$this->batchSize = $batchSize;
$this->resetInterval = $resetInterval;
// 初期の制限時間を設定
set_time_limit($this->timeLimit);
}
/**
* データを処理
*/
public function process($data, $callback) {
$total = count($data);
$processed = 0;
$batches = array_chunk($data, $this->batchSize);
foreach ($batches as $batchIndex => $batch) {
// バッチ処理の開始
$startTime = microtime(true);
foreach ($batch as $item) {
call_user_func($callback, $item);
$processed++;
}
$endTime = microtime(true);
$elapsed = $endTime - $startTime;
// 進捗を表示
$this->showProgress($processed, $total, $elapsed);
// 定期的に制限時間をリセット
if (($batchIndex + 1) % $this->resetInterval === 0) {
set_time_limit($this->timeLimit);
echo " [Time limit reset]";
}
echo "\n";
}
return [
'total' => $total,
'processed' => $processed,
'success' => $processed === $total
];
}
/**
* 進捗を表示
*/
private function showProgress($current, $total, $batchTime) {
$percentage = ($current / $total) * 100;
echo sprintf(
"Progress: %d/%d (%.1f%%) - Batch time: %.3fs",
$current,
$total,
$percentage,
$batchTime
);
}
/**
* 現在の制限時間を取得
*/
public function getTimeLimit() {
return ini_get('max_execution_time');
}
}
// 使用例
echo "=== バッチ処理 ===\n";
$processor = new BatchProcessor(300, 50, 5);
// テストデータを生成
$data = range(1, 500);
echo "制限時間: " . $processor->getTimeLimit() . "秒\n";
echo "総アイテム数: " . count($data) . "\n\n";
// 処理を実行
$result = $processor->process($data, function($item) {
// 各アイテムの処理をシミュレート
usleep(1000); // 1ミリ秒
});
echo "\n=== 処理完了 ===\n";
echo "処理済み: {$result['processed']}/{$result['total']}\n";
echo "成功: " . ($result['success'] ? 'Yes' : 'No') . "\n";
例2: データインポートシステム
class DataImporter {
private $maxExecutionTime;
private $safetyMargin;
/**
* インポーターを初期化
*/
public function __construct($maxExecutionTime = 600, $safetyMargin = 60) {
$this->maxExecutionTime = $maxExecutionTime;
$this->safetyMargin = $safetyMargin;
// 制限時間を設定
set_time_limit($this->maxExecutionTime);
}
/**
* CSVファイルをインポート
*/
public function importCsv($filename, $processor) {
if (!file_exists($filename)) {
throw new Exception("File not found: {$filename}");
}
$fp = fopen($filename, 'r');
if ($fp === false) {
throw new Exception("Failed to open file");
}
$startTime = time();
$imported = 0;
$errors = [];
// ヘッダー行を読む
$headers = fgetcsv($fp);
while (($row = fgetcsv($fp)) !== false) {
// 残り時間をチェック
$elapsed = time() - $startTime;
$remaining = $this->maxExecutionTime - $elapsed - $this->safetyMargin;
if ($remaining < 0) {
echo "\nTime limit approaching, stopping import\n";
break;
}
try {
// データを処理
$data = array_combine($headers, $row);
call_user_func($processor, $data);
$imported++;
// 100件ごとに進捗表示と制限時間リセット
if ($imported % 100 === 0) {
echo "Imported: {$imported} records (Remaining: {$remaining}s)\n";
set_time_limit($this->maxExecutionTime);
}
} catch (Exception $e) {
$errors[] = [
'row' => $imported + 1,
'error' => $e->getMessage()
];
}
}
fclose($fp);
return [
'imported' => $imported,
'errors' => $errors,
'execution_time' => time() - $startTime
];
}
/**
* JSONファイルをインポート
*/
public function importJson($filename, $processor) {
if (!file_exists($filename)) {
throw new Exception("File not found: {$filename}");
}
$startTime = time();
$data = json_decode(file_get_contents($filename), true);
if ($data === null) {
throw new Exception("Invalid JSON");
}
$imported = 0;
$errors = [];
foreach ($data as $index => $record) {
// 残り時間をチェック
$elapsed = time() - $startTime;
$remaining = $this->maxExecutionTime - $elapsed - $this->safetyMargin;
if ($remaining < 0) {
echo "\nTime limit approaching, stopping import\n";
break;
}
try {
call_user_func($processor, $record);
$imported++;
if ($imported % 100 === 0) {
echo "Imported: {$imported} records (Remaining: {$remaining}s)\n";
set_time_limit($this->maxExecutionTime);
}
} catch (Exception $e) {
$errors[] = [
'index' => $index,
'error' => $e->getMessage()
];
}
}
return [
'imported' => $imported,
'errors' => $errors,
'execution_time' => time() - $startTime
];
}
}
// 使用例
echo "=== データインポート ===\n";
// テストCSVを作成
$csvFile = '/tmp/import_test.csv';
$fp = fopen($csvFile, 'w');
fputcsv($fp, ['id', 'name', 'email', 'age']);
for ($i = 1; $i <= 1000; $i++) {
fputcsv($fp, [$i, "User {$i}", "user{$i}@example.com", rand(18, 80)]);
}
fclose($fp);
$importer = new DataImporter(300, 30);
$result = $importer->importCsv($csvFile, function($data) {
// データ処理をシミュレート
usleep(1000); // 1ミリ秒
// 検証
if (empty($data['email'])) {
throw new Exception("Email is required");
}
});
echo "\n=== インポート結果 ===\n";
echo "インポート済み: {$result['imported']}件\n";
echo "エラー: " . count($result['errors']) . "件\n";
echo "実行時間: {$result['execution_time']}秒\n";
// クリーンアップ
unlink($csvFile);
例3: 画像処理システム
class ImageProcessor {
private $timeLimit;
private $outputDir;
/**
* 画像処理を初期化
*/
public function __construct($outputDir, $timeLimit = 300) {
$this->outputDir = $outputDir;
$this->timeLimit = $timeLimit;
if (!is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
set_time_limit($timeLimit);
}
/**
* 複数の画像をリサイズ
*/
public function resizeImages($images, $width, $height) {
$startTime = time();
$processed = 0;
$errors = [];
foreach ($images as $index => $imagePath) {
try {
// 残り時間をチェック
$elapsed = time() - $startTime;
if ($elapsed > $this->timeLimit - 30) {
echo "Time limit approaching, stopping processing\n";
break;
}
$this->resizeImage($imagePath, $width, $height);
$processed++;
echo "Processed: {$processed}/" . count($images) . "\n";
// 10枚ごとに制限時間をリセット
if ($processed % 10 === 0) {
set_time_limit($this->timeLimit);
}
} catch (Exception $e) {
$errors[] = [
'image' => $imagePath,
'error' => $e->getMessage()
];
}
}
return [
'processed' => $processed,
'total' => count($images),
'errors' => $errors,
'execution_time' => time() - $startTime
];
}
/**
* 画像を生成してリサイズ(テスト用)
*/
private function resizeImage($imagePath, $width, $height) {
if (!file_exists($imagePath)) {
throw new Exception("Image not found: {$imagePath}");
}
// 画像処理をシミュレート(実際にはGDやImagickを使用)
usleep(50000); // 50ミリ秒(画像処理をシミュレート)
$outputPath = $this->outputDir . '/' . basename($imagePath);
copy($imagePath, $outputPath);
}
/**
* サムネイル生成
*/
public function generateThumbnails($images, $size = 150) {
set_time_limit($this->timeLimit);
$generated = 0;
foreach ($images as $image) {
// 処理
$this->createThumbnail($image, $size);
$generated++;
// 5枚ごとにリセット
if ($generated % 5 === 0) {
set_time_limit($this->timeLimit);
echo "Generated {$generated} thumbnails\n";
}
}
return $generated;
}
/**
* サムネイルを作成(テスト用)
*/
private function createThumbnail($image, $size) {
usleep(30000); // 30ミリ秒
}
}
// 使用例
echo "=== 画像処理 ===\n";
// テスト用の画像ファイルを作成
$testImages = [];
for ($i = 1; $i <= 50; $i++) {
$imagePath = "/tmp/test_image_{$i}.jpg";
file_put_contents($imagePath, "dummy image content {$i}");
$testImages[] = $imagePath;
}
$processor = new ImageProcessor('/tmp/resized', 180);
echo "制限時間: 180秒\n";
echo "画像数: " . count($testImages) . "\n\n";
$result = $processor->resizeImages($testImages, 800, 600);
echo "\n=== 処理結果 ===\n";
echo "処理済み: {$result['processed']}/{$result['total']}\n";
echo "エラー: " . count($result['errors']) . "件\n";
echo "実行時間: {$result['execution_time']}秒\n";
// クリーンアップ
foreach ($testImages as $image) {
@unlink($image);
}
例4: API呼び出しマネージャー
class ApiCallManager {
private $baseUrl;
private $timeout;
private $maxRetries;
/**
* API呼び出しマネージャーを初期化
*/
public function __construct($baseUrl, $timeout = 300, $maxRetries = 3) {
$this->baseUrl = $baseUrl;
$this->timeout = $timeout;
$this->maxRetries = $maxRetries;
// 十分な実行時間を確保
set_time_limit($timeout + 60);
}
/**
* 複数のAPIエンドポイントを呼び出し
*/
public function batchCall($endpoints) {
$startTime = time();
$results = [];
$errors = [];
foreach ($endpoints as $index => $endpoint) {
// 残り時間をチェック
$elapsed = time() - $startTime;
if ($elapsed > $this->timeout - 30) {
echo "Time limit approaching, stopping API calls\n";
break;
}
try {
$result = $this->callWithRetry($endpoint);
$results[$endpoint] = $result;
echo "Called: {$endpoint} (" . ($index + 1) . "/" . count($endpoints) . ")\n";
// 10回ごとに制限時間をリセット
if (($index + 1) % 10 === 0) {
set_time_limit($this->timeout + 60);
}
} catch (Exception $e) {
$errors[$endpoint] = $e->getMessage();
}
}
return [
'results' => $results,
'errors' => $errors,
'execution_time' => time() - $startTime
];
}
/**
* リトライ付きでAPIを呼び出し
*/
private function callWithRetry($endpoint, $attempt = 1) {
try {
return $this->makeApiCall($endpoint);
} catch (Exception $e) {
if ($attempt < $this->maxRetries) {
echo "Retry {$attempt}/{$this->maxRetries} for {$endpoint}\n";
sleep(1); // 待機
return $this->callWithRetry($endpoint, $attempt + 1);
}
throw $e;
}
}
/**
* 実際のAPI呼び出し(テスト用)
*/
private function makeApiCall($endpoint) {
// API呼び出しをシミュレート
usleep(rand(100000, 500000)); // 100-500ミリ秒
// ランダムに失敗させる
if (rand(1, 10) > 8) {
throw new Exception("API call failed");
}
return [
'status' => 'success',
'data' => ['key' => 'value'],
'endpoint' => $endpoint
];
}
/**
* ページネーション付きでデータを取得
*/
public function fetchPaginatedData($endpoint, $maxPages = 10) {
set_time_limit($this->timeout + 60);
$allData = [];
$page = 1;
while ($page <= $maxPages) {
// 残り時間をチェック
$elapsed = time() - $_SERVER['REQUEST_TIME'];
if ($elapsed > $this->timeout - 30) {
echo "Time limit approaching, stopping pagination\n";
break;
}
$data = $this->makeApiCall("{$endpoint}?page={$page}");
$allData = array_merge($allData, $data);
echo "Fetched page {$page}\n";
// 5ページごとに制限時間をリセット
if ($page % 5 === 0) {
set_time_limit($this->timeout + 60);
}
$page++;
}
return $allData;
}
}
// 使用例
echo "=== API呼び出し管理 ===\n";
$apiManager = new ApiCallManager('https://api.example.com', 120, 3);
// 呼び出すエンドポイント
$endpoints = [];
for ($i = 1; $i <= 30; $i++) {
$endpoints[] = "/api/data/{$i}";
}
echo "エンドポイント数: " . count($endpoints) . "\n";
echo "制限時間: 120秒\n\n";
$result = $apiManager->batchCall($endpoints);
echo "\n=== 呼び出し結果 ===\n";
echo "成功: " . count($result['results']) . "件\n";
echo "失敗: " . count($result['errors']) . "件\n";
echo "実行時間: {$result['execution_time']}秒\n";
例5: レポート生成システム
class ReportGenerator {
private $timeLimit;
private $reportDir;
/**
* レポート生成を初期化
*/
public function __construct($reportDir, $timeLimit = 600) {
$this->reportDir = $reportDir;
$this->timeLimit = $timeLimit;
if (!is_dir($reportDir)) {
mkdir($reportDir, 0755, true);
}
set_time_limit($timeLimit);
}
/**
* 大量データからレポートを生成
*/
public function generateReport($data, $reportName) {
$startTime = time();
$reportFile = $this->reportDir . '/' . $reportName;
$fp = fopen($reportFile, 'w');
if ($fp === false) {
throw new Exception("Failed to create report file");
}
// ヘッダー
fwrite($fp, "=== {$reportName} ===\n");
fwrite($fp, "Generated: " . date('Y-m-d H:i:s') . "\n\n");
$sections = [
'summary' => function() use ($data) {
return $this->generateSummary($data);
},
'details' => function() use ($data) {
return $this->generateDetails($data);
},
'statistics' => function() use ($data) {
return $this->generateStatistics($data);
},
'charts' => function() use ($data) {
return $this->generateCharts($data);
}
];
foreach ($sections as $sectionName => $generator) {
// 残り時間をチェック
$elapsed = time() - $startTime;
$remaining = $this->timeLimit - $elapsed - 30;
if ($remaining < 0) {
fwrite($fp, "\n[Report generation stopped due to time limit]\n");
break;
}
echo "Generating section: {$sectionName} (Remaining: {$remaining}s)\n";
// セクションを生成
$content = call_user_func($generator);
fwrite($fp, "\n## {$sectionName} ##\n");
fwrite($fp, $content . "\n");
// 制限時間をリセット
set_time_limit($this->timeLimit);
}
fwrite($fp, "\n=== End of Report ===\n");
fclose($fp);
return [
'file' => $reportFile,
'size' => filesize($reportFile),
'execution_time' => time() - $startTime
];
}
/**
* サマリーを生成
*/
private function generateSummary($data) {
usleep(100000); // 処理をシミュレート
return "Total records: " . count($data) . "\n";
}
/**
* 詳細を生成
*/
private function generateDetails($data) {
$output = '';
foreach (array_slice($data, 0, 100) as $record) {
$output .= json_encode($record) . "\n";
usleep(1000);
}
return $output;
}
/**
* 統計を生成
*/
private function generateStatistics($data) {
usleep(200000); // 処理をシミュレート
return "Average: 50\nMedian: 45\nStdDev: 15\n";
}
/**
* チャートを生成
*/
private function generateCharts($data) {
usleep(300000); // 処理をシミュレート
return "[Chart data would be here]\n";
}
}
// 使用例
echo "=== レポート生成 ===\n";
// テストデータ
$testData = [];
for ($i = 0; $i < 10000; $i++) {
$testData[] = [
'id' => $i,
'value' => rand(1, 100),
'category' => 'Category ' . (($i % 5) + 1)
];
}
$generator = new ReportGenerator('/tmp/reports', 300);
echo "データ件数: " . count($testData) . "\n";
echo "制限時間: 300秒\n\n";
$result = $generator->generateReport($testData, 'monthly_report.txt');
echo "\n=== 生成完了 ===\n";
echo "ファイル: {$result['file']}\n";
echo "サイズ: " . number_format($result['size']) . " bytes\n";
echo "実行時間: {$result['execution_time']}秒\n";
例6: タイムアウト監視システム
class TimeoutMonitor {
private $maxExecutionTime;
private $warningThreshold;
private $startTime;
/**
* モニターを初期化
*/
public function __construct($maxExecutionTime = 300, $warningThreshold = 0.8) {
$this->maxExecutionTime = $maxExecutionTime;
$this->warningThreshold = $warningThreshold;
$this->startTime = time();
set_time_limit($maxExecutionTime);
// シャットダウン関数を登録
register_shutdown_function([$this, 'handleShutdown']);
}
/**
* 残り時間を取得
*/
public function getRemainingTime() {
$elapsed = time() - $this->startTime;
return max(0, $this->maxExecutionTime - $elapsed);
}
/**
* 使用率を取得
*/
public function getUsagePercentage() {
$elapsed = time() - $this->startTime;
return ($elapsed / $this->maxExecutionTime) * 100;
}
/**
* 警告しきい値に達したかチェック
*/
public function isWarningThresholdReached() {
$usage = $this->getUsagePercentage() / 100;
return $usage >= $this->warningThreshold;
}
/**
* 安全に処理を続行できるかチェック
*/
public function canContinue($safetyMargin = 30) {
return $this->getRemainingTime() > $safetyMargin;
}
/**
* 制限時間をリセット
*/
public function reset() {
set_time_limit($this->maxExecutionTime);
$this->startTime = time();
}
/**
* シャットダウン時の処理
*/
public function handleShutdown() {
$error = error_get_last();
if ($error !== null && $error['type'] === E_ERROR) {
if (strpos($error['message'], 'Maximum execution time') !== false) {
echo "\n[TIMEOUT] Script exceeded maximum execution time\n";
echo "Elapsed: " . (time() - $this->startTime) . " seconds\n";
}
}
}
/**
* ステータスを表示
*/
public function displayStatus() {
$remaining = $this->getRemainingTime();
$usage = $this->getUsagePercentage();
echo sprintf(
"[TIME] Remaining: %ds (%.1f%% used)",
$remaining,
$usage
);
if ($this->isWarningThresholdReached()) {
echo " [WARNING]";
}
echo "\n";
}
}
// 使用例
echo "=== タイムアウト監視 ===\n";
$monitor = new TimeoutMonitor(60, 0.7); // 60秒、警告70%
echo "最大実行時間: 60秒\n";
echo "警告しきい値: 70%\n\n";
for ($i = 0; $i < 100; $i++) {
// 何らかの処理
usleep(500000); // 0.5秒
if ($i % 5 === 0) {
$monitor->displayStatus();
}
// 警告しきい値に達したらアラート
if ($monitor->isWarningThresholdReached()) {
echo "警告: 実行時間の70%を超えました\n";
}
// 続行できるかチェック
if (!$monitor->canContinue(10)) {
echo "安全マージン不足のため処理を中断します\n";
break;
}
// 20回ごとにリセット
if ($i > 0 && $i % 20 === 0) {
echo "制限時間をリセットします\n";
$monitor->reset();
}
}
echo "\n処理完了\n";
php.iniとの関係
// php.iniの設定を確認
echo "max_execution_time: " . ini_get('max_execution_time') . "\n";
// ini_set()で変更
ini_set('max_execution_time', 300);
// set_time_limit()で変更
set_time_limit(300);
// 両方とも同じ効果だが、set_time_limit()は実行時間をリセット
// ini_set()は設定を変更するだけ
// .htaccessでの設定
// php_value max_execution_time 300
セーフモードと制限事項
// セーフモードが有効な場合
if (ini_get('safe_mode')) {
echo "セーフモードが有効です\n";
echo "set_time_limit()は無効化されている可能性があります\n";
}
// disable_functionsでset_time_limitが無効化されている場合
$disabled = ini_get('disable_functions');
if (strpos($disabled, 'set_time_limit') !== false) {
echo "set_time_limit()は無効化されています\n";
}
// CLIモードでは通常デフォルトで0(無制限)
if (php_sapi_name() === 'cli') {
echo "CLIモード: デフォルトで実行時間無制限\n";
echo "現在の設定: " . ini_get('max_execution_time') . "\n";
}
まとめ
set_time_limit()関数の特徴をまとめると:
できること:
- スクリプトの最大実行時間を設定
- 実行時間制限のリセット
- 長時間処理の実現
引数:
秒数: 最大実行時間(秒単位)0: 無制限
推奨される使用場面:
- バッチ処理
- データインポート/エクスポート
- 画像処理
- レポート生成
- API呼び出し
- クローリング/スクレイピング
重要な注意点:
- 呼び出し時点から時間をカウント開始(リセット)
sleep()の時間は含まれない- 外部プログラムの実行時間は含まれる
- セーフモードでは無効
disable_functionsで無効化されている場合あり
ベストプラクティス:
// 1. 適切な制限時間を設定
set_time_limit(300); // 5分
// 2. 定期的にリセット
foreach ($batches as $batch) {
processBatch($batch);
set_time_limit(300); // リセット
}
// 3. 残り時間を監視
$startTime = time();
if (time() - $startTime > 270) { // 30秒マージン
break;
}
// 4. シャットダウン関数を登録
register_shutdown_function(function() {
// タイムアウト時の処理
});
関連設定:
max_execution_time: php.iniの設定max_input_time: 入力処理の最大時間memory_limit: メモリ制限
関連関数:
ini_set(): PHP設定を変更ini_get(): PHP設定を取得register_shutdown_function(): シャットダウン関数登録ignore_user_abort(): ユーザー切断時の動作制御
set_time_limit()は、長時間実行が必要な処理において非常に重要な関数です。適切に使用することで、タイムアウトエラーを防ぎ、大規模な処理を安全に実行できます!
