はじめに
PHPでJSON処理を行う際、エラーが発生した原因を素早く特定することは、効率的な開発において非常に重要です。json_last_error()関数でエラーコードを取得できますが、数値だけでは具体的な問題がわかりにくい場合があります。
そこで活用したいのが「json_last_error_msg」関数です。この関数を使うことで、人間にとってわかりやすいエラーメッセージを取得でき、デバッグ作業を大幅に効率化できます。
この記事では、json_last_error_msg関数の基本的な使い方から、実践的な活用テクニックまで詳しく解説していきます。
json_last_error_msg関数とは?
json_last_error_msg関数は、PHP 5.5.0で導入された関数で、最後に実行されたJSON操作で発生したエラーの詳細メッセージを文字列で返します。json_last_error()と組み合わせることで、より詳細なエラー情報を取得できます。
基本的な構文
json_last_error_msg(): string
パラメータはなく、戻り値として文字列のエラーメッセージを返します。エラーが発生していない場合は「No error」を返します。
基本的な使用方法
シンプルなエラーメッセージの取得
// 不正なJSON文字列
$invalidJson = '{"name": "田中太郎", "age": 30,}'; // 末尾のカンマが不正
$result = json_decode($invalidJson);
// エラーチェック
if (json_last_error() !== JSON_ERROR_NONE) {
echo "エラーが発生しました: " . json_last_error_msg() . "\n";
echo "エラーコード: " . json_last_error() . "\n";
}
// 出力:
// エラーが発生しました: Syntax error
// エラーコード: 4
json_encodeでのエラーメッセージ取得
// 無限ループを含むデータ
$a = [];
$b = [];
$a['ref'] = &$b;
$b['ref'] = &$a;
$json = json_encode($a);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "エンコードエラー: " . json_last_error_msg() . "\n";
}
// 出力: エンコードエラー: Recursion detected
各種エラーメッセージの詳細
エラーメッセージとその意味
function demonstrateErrorMessages() {
$testCases = [
// 構文エラー
'{"invalid": json}' => 'JSON構文が正しくありません',
// 制御文字エラー
'{"test": "' . chr(1) . '"}' => '制御文字が含まれています',
// 深度エラー(深すぎるネスト)
str_repeat('[', 600) . str_repeat(']', 600) => 'ネストが深すぎます',
// UTF-8エラー
'{"name": "' . "\x80\x81" . '"}' => 'UTF-8エンコーディングエラー'
];
foreach ($testCases as $json => $description) {
echo "\n=== {$description} ===\n";
echo "テストJSON: " . substr($json, 0, 50) . "...\n";
$result = json_decode($json);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "エラーコード: " . json_last_error() . "\n";
echo "エラーメッセージ: " . json_last_error_msg() . "\n";
} else {
echo "エラーなし\n";
}
}
}
demonstrateErrorMessages();
実践的なエラーハンドリング関数
詳細なエラー情報を提供する関数
class JsonErrorHandler {
// エラーメッセージの日本語化マッピング
private static $errorMessages = [
'No error' => 'エラーはありません',
'Maximum stack depth exceeded' => '最大スタック深度を超過しました',
'State mismatch (invalid or malformed JSON)' => 'JSONの状態が一致しません(無効または不正な形式)',
'Control character error, possibly incorrectly encoded' => '制御文字エラー(エンコーディングが正しくない可能性があります)',
'Syntax error' => '構文エラー(JSONの形式が正しくありません)',
'Malformed UTF-8 characters, possibly incorrectly encoded' => 'UTF-8文字が正しくありません(エンコーディングエラー)',
'Recursion detected' => '再帰的な参照が検出されました',
'Inf and NaN cannot be JSON encoded' => 'INFやNANの値はJSONエンコードできません',
'Type is not supported' => 'サポートされていないデータ型です',
'The decoded property name is invalid' => 'デコードされたプロパティ名が無効です',
'Single unpaired UTF-16 surrogate in unicode escape' => 'UTF-16サロゲートが正しくありません'
];
public static function getDetailedError() {
$errorCode = json_last_error();
$errorMessage = json_last_error_msg();
// 日本語メッセージがある場合は置き換え
$japaneseMessage = self::$errorMessages[$errorMessage] ?? $errorMessage;
return [
'has_error' => $errorCode !== JSON_ERROR_NONE,
'error_code' => $errorCode,
'error_message_en' => $errorMessage,
'error_message_ja' => $japaneseMessage,
'timestamp' => date('Y-m-d H:i:s'),
'suggestions' => self::getSuggestions($errorCode, $errorMessage)
];
}
private static function getSuggestions($errorCode, $errorMessage) {
$suggestions = [];
switch ($errorCode) {
case JSON_ERROR_SYNTAX:
$suggestions = [
'末尾のカンマを削除してください',
'プロパティ名と値をダブルクォートで囲んでください',
'JSONのフォーマットが正しいか確認してください',
'オンラインのJSONバリデーターで確認してみてください'
];
break;
case JSON_ERROR_UTF8:
$suggestions = [
'ファイルがUTF-8でエンコードされているか確認してください',
'mb_convert_encoding()を使用して文字エンコーディングを修正してください',
'バイナリデータが含まれていないか確認してください'
];
break;
case JSON_ERROR_RECURSION:
$suggestions = [
'循環参照を削除してください',
'オブジェクト構造を見直してください',
'JsonSerializableインターフェースの使用を検討してください'
];
break;
case JSON_ERROR_DEPTH:
$suggestions = [
'データ構造のネストレベルを減らしてください',
'json_decode()の第3引数(depth)を増やしてください',
'データを平坦化することを検討してください'
];
break;
case JSON_ERROR_CTRL_CHAR:
$suggestions = [
'制御文字(改行、タブなど)をエスケープしてください',
'addslashes()やjson_encode()のフラグを適切に設定してください'
];
break;
}
return $suggestions;
}
public static function displayError($context = '') {
$error = self::getDetailedError();
if (!$error['has_error']) {
echo "✅ JSONエラーはありません\n";
return;
}
echo "❌ JSONエラーが発生しました\n";
if ($context) {
echo "📍 コンテキスト: {$context}\n";
}
echo "🔢 エラーコード: {$error['error_code']}\n";
echo "🇺🇸 英語メッセージ: {$error['error_message_en']}\n";
echo "🇯🇵 日本語メッセージ: {$error['error_message_ja']}\n";
echo "⏰ 発生時刻: {$error['timestamp']}\n";
if (!empty($error['suggestions'])) {
echo "💡 解決策の提案:\n";
foreach ($error['suggestions'] as $i => $suggestion) {
echo " " . ($i + 1) . ". {$suggestion}\n";
}
}
}
}
// 使用例
$invalidJson = '{"name": "田中太郎",}'; // 末尾のカンマエラー
$result = json_decode($invalidJson);
JsonErrorHandler::displayError('ユーザーデータの解析');
ログ機能付きJSON処理クラス
包括的なJSONハンドリングクラス
class JsonProcessor {
private $logFile;
private $enableLogging;
public function __construct($logFile = 'json_errors.log', $enableLogging = true) {
$this->logFile = $logFile;
$this->enableLogging = $enableLogging;
}
public function decode($json, $assoc = false, $depth = 512, $flags = 0) {
$startTime = microtime(true);
// 入力検証
if (!is_string($json)) {
throw new InvalidArgumentException('JSON文字列が必要です');
}
// デコード実行
$result = json_decode($json, $assoc, $depth, $flags);
$processingTime = microtime(true) - $startTime;
// エラーチェック
$errorCode = json_last_error();
$errorMessage = json_last_error_msg();
if ($errorCode !== JSON_ERROR_NONE) {
$this->logError('decode', $json, null, $errorCode, $errorMessage, $processingTime);
throw new JsonException("JSON decode error: {$errorMessage} (Code: {$errorCode})");
}
$this->logSuccess('decode', strlen($json), $processingTime);
return $result;
}
public function encode($data, $flags = 0, $depth = 512) {
$startTime = microtime(true);
// エンコード実行
$result = json_encode($data, $flags, $depth);
$processingTime = microtime(true) - $startTime;
// エラーチェック
$errorCode = json_last_error();
$errorMessage = json_last_error_msg();
if ($errorCode !== JSON_ERROR_NONE || $result === false) {
$this->logError('encode', null, $data, $errorCode, $errorMessage, $processingTime);
throw new JsonException("JSON encode error: {$errorMessage} (Code: {$errorCode})");
}
$this->logSuccess('encode', strlen($result), $processingTime);
return $result;
}
private function logError($operation, $json, $data, $errorCode, $errorMessage, $processingTime) {
if (!$this->enableLogging) return;
$logEntry = [
'timestamp' => date('c'),
'operation' => $operation,
'error_code' => $errorCode,
'error_message' => $errorMessage,
'processing_time' => round($processingTime * 1000, 3) . 'ms',
'memory_usage' => $this->formatBytes(memory_get_usage()),
'php_version' => PHP_VERSION
];
if ($json !== null) {
$logEntry['json_sample'] = mb_substr($json, 0, 200);
$logEntry['json_length'] = strlen($json);
}
if ($data !== null) {
$logEntry['data_type'] = gettype($data);
if (is_array($data)) {
$logEntry['data_count'] = count($data);
}
}
$this->writeLog('ERROR', $logEntry);
}
private function logSuccess($operation, $dataSize, $processingTime) {
if (!$this->enableLogging) return;
$logEntry = [
'timestamp' => date('c'),
'operation' => $operation,
'status' => 'success',
'data_size' => $this->formatBytes($dataSize),
'processing_time' => round($processingTime * 1000, 3) . 'ms',
'memory_usage' => $this->formatBytes(memory_get_usage())
];
$this->writeLog('INFO', $logEntry);
}
private function writeLog($level, $data) {
$logMessage = "[{$level}] " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n";
file_put_contents($this->logFile, $logMessage, FILE_APPEND | LOCK_EX);
}
private function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= (1 << (10 * $pow));
return round($bytes, 2) . ' ' . $units[$pow];
}
public function getLastErrorDetails() {
return JsonErrorHandler::getDetailedError();
}
}
// 使用例
$processor = new JsonProcessor();
try {
// 正常なケース
$data = ['name' => '田中太郎', 'age' => 30];
$json = $processor->encode($data, JSON_UNESCAPED_UNICODE);
echo "エンコード成功: {$json}\n";
$decoded = $processor->decode($json, true);
echo "デコード成功: " . print_r($decoded, true) . "\n";
} catch (JsonException $e) {
echo "エラー: " . $e->getMessage() . "\n";
$errorDetails = $processor->getLastErrorDetails();
print_r($errorDetails);
}
try {
// エラーケース
$invalidJson = '{"name": "田中", "age":}';
$processor->decode($invalidJson);
} catch (JsonException $e) {
echo "予期されたエラー: " . $e->getMessage() . "\n";
}
デバッグ支援ツール
インタラクティブなJSONデバッガー
class JsonDebugger {
public static function analyzeJson($json, $showSuggestions = true) {
echo "=== JSON分析結果 ===\n";
echo "入力データ: " . substr($json, 0, 100) . (strlen($json) > 100 ? '...' : '') . "\n";
echo "データサイズ: " . strlen($json) . " bytes\n\n";
// デコード試行
$result = json_decode($json, true);
$errorCode = json_last_error();
$errorMessage = json_last_error_msg();
if ($errorCode === JSON_ERROR_NONE) {
echo "✅ JSON解析成功\n";
echo "結果のデータ型: " . gettype($result) . "\n";
if (is_array($result)) {
echo "配列要素数: " . count($result) . "\n";
self::analyzeStructure($result);
}
} else {
echo "❌ JSON解析失敗\n";
echo "エラーコード: {$errorCode}\n";
echo "エラーメッセージ: {$errorMessage}\n\n";
// 詳細分析
self::performDetailedAnalysis($json, $errorCode, $errorMessage);
if ($showSuggestions) {
self::showFixSuggestions($json, $errorCode);
}
}
}
private static function analyzeStructure($data, $depth = 0, $maxDepth = 3) {
if ($depth > $maxDepth) {
echo str_repeat(' ', $depth) . "...(省略)\n";
return;
}
if (is_array($data)) {
foreach ($data as $key => $value) {
$indent = str_repeat(' ', $depth);
$type = gettype($value);
if (is_array($value)) {
echo "{$indent}{$key}: array(" . count($value) . ")\n";
self::analyzeStructure($value, $depth + 1, $maxDepth);
} else {
$valueStr = is_string($value) ? '"' . substr($value, 0, 20) . '"' : $value;
echo "{$indent}{$key}: {$type}({$valueStr})\n";
}
}
}
}
private static function performDetailedAnalysis($json, $errorCode, $errorMessage) {
echo "🔍 詳細分析:\n";
// 文字数と行数
$lines = explode("\n", $json);
echo "・行数: " . count($lines) . "\n";
echo "・文字数: " . mb_strlen($json) . "\n";
// 特殊文字の検出
if (preg_match('/[\x00-\x1F\x7F]/', $json)) {
echo "・⚠️ 制御文字が検出されました\n";
}
// UTF-8検証
if (!mb_check_encoding($json, 'UTF-8')) {
echo "・⚠️ UTF-8エンコーディングエラーが検出されました\n";
}
// 一般的な構文エラーをチェック
if ($errorCode === JSON_ERROR_SYNTAX) {
self::checkCommonSyntaxErrors($json);
}
}
private static function checkCommonSyntaxErrors($json) {
echo "\n🔧 構文エラーの詳細分析:\n";
// 末尾カンマ
if (preg_match('/,\s*[}\]]/', $json)) {
echo "・末尾のカンマが検出されました\n";
}
// シングルクォート
if (preg_match("/'/", $json)) {
echo "・シングルクォートが検出されました(ダブルクォートを使用してください)\n";
}
// クォートされていないキー
if (preg_match('/[{,]\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:/', $json)) {
echo "・クォートされていないプロパティ名が検出されました\n";
}
// 括弧の不一致
$openBraces = substr_count($json, '{');
$closeBraces = substr_count($json, '}');
$openBrackets = substr_count($json, '[');
$closeBrackets = substr_count($json, ']');
if ($openBraces !== $closeBraces) {
echo "・波括弧の数が一致しません (開く: {$openBraces}, 閉じる: {$closeBraces})\n";
}
if ($openBrackets !== $closeBrackets) {
echo "・角括弧の数が一致しません (開く: {$openBrackets}, 閉じる: {$closeBrackets})\n";
}
}
private static function showFixSuggestions($json, $errorCode) {
echo "\n💡 修正提案:\n";
$fixedJson = $json;
$fixes = [];
// 末尾カンマの修正
if (preg_match('/,(\s*[}\]])/', $json)) {
$fixedJson = preg_replace('/,(\s*[}\]])/', '$1', $fixedJson);
$fixes[] = '末尾のカンマを削除';
}
// シングルクォートの修正
if (preg_match("/'/", $json)) {
$fixedJson = str_replace("'", '"', $fixedJson);
$fixes[] = 'シングルクォートをダブルクォートに変更';
}
if (!empty($fixes)) {
echo "適用した修正: " . implode(', ', $fixes) . "\n\n";
echo "修正後のJSON:\n";
echo $fixedJson . "\n\n";
// 修正版をテスト
$testResult = json_decode($fixedJson);
if (json_last_error() === JSON_ERROR_NONE) {
echo "✅ 修正により解析が成功しました!\n";
} else {
echo "❌ 修正後もエラーが残っています: " . json_last_error_msg() . "\n";
}
}
}
}
// 使用例
echo "=== JSON デバッガーのテスト ===\n\n";
$testCases = [
'{"name": "田中太郎", "age": 30}', // 正常
'{"name": "田中", "age": 30,}', // 末尾カンマエラー
"{'name': '佐藤', 'age': 25}", // シングルクォートエラー
'{name: "山田", age: 40}' // クォートなしキーエラー
];
foreach ($testCases as $i => $testJson) {
echo "--- テストケース " . ($i + 1) . " ---\n";
JsonDebugger::analyzeJson($testJson);
echo "\n" . str_repeat("=", 50) . "\n\n";
}
パフォーマンス測定とモニタリング
JSON処理のパフォーマンス分析
class JsonPerformanceMonitor {
private static $metrics = [];
public static function measureJsonOperation($operation, $data, $flags = 0) {
$startTime = microtime(true);
$startMemory = memory_get_usage();
if ($operation === 'encode') {
$result = json_encode($data, $flags);
} else {
$result = json_decode($data, true, 512, $flags);
}
$endTime = microtime(true);
$endMemory = memory_get_usage();
$metrics = [
'operation' => $operation,
'processing_time' => ($endTime - $startTime) * 1000, // ミリ秒
'memory_used' => $endMemory - $startMemory,
'peak_memory' => memory_get_peak_usage(),
'error_code' => json_last_error(),
'error_message' => json_last_error_msg(),
'data_size' => is_string($data) ? strlen($data) : 0,
'timestamp' => date('c')
];
self::$metrics[] = $metrics;
return [
'result' => $result,
'metrics' => $metrics,
'success' => json_last_error() === JSON_ERROR_NONE
];
}
public static function generateReport() {
if (empty(self::$metrics)) {
echo "パフォーマンスデータがありません\n";
return;
}
echo "=== JSON処理パフォーマンスレポート ===\n\n";
$totalOperations = count(self::$metrics);
$successfulOperations = count(array_filter(self::$metrics, function($m) {
return $m['error_code'] === JSON_ERROR_NONE;
}));
$avgProcessingTime = array_sum(array_column(self::$metrics, 'processing_time')) / $totalOperations;
$avgMemoryUsage = array_sum(array_column(self::$metrics, 'memory_used')) / $totalOperations;
echo "総操作数: {$totalOperations}\n";
echo "成功率: " . round(($successfulOperations / $totalOperations) * 100, 2) . "%\n";
echo "平均処理時間: " . round($avgProcessingTime, 3) . "ms\n";
echo "平均メモリ使用量: " . self::formatBytes($avgMemoryUsage) . "\n\n";
// エラー統計
$errors = array_filter(self::$metrics, function($m) {
return $m['error_code'] !== JSON_ERROR_NONE;
});
if (!empty($errors)) {
echo "=== エラー統計 ===\n";
$errorCounts = [];
foreach ($errors as $error) {
$key = $error['error_message'];
$errorCounts[$key] = ($errorCounts[$key] ?? 0) + 1;
}
foreach ($errorCounts as $message => $count) {
echo "・{$message}: {$count}回\n";
}
echo "\n";
}
// 処理時間の分析
$processingTimes = array_column(self::$metrics, 'processing_time');
sort($processingTimes);
echo "=== 処理時間分析 ===\n";
echo "最小: " . round(min($processingTimes), 3) . "ms\n";
echo "最大: " . round(max($processingTimes), 3) . "ms\n";
echo "中央値: " . round($processingTimes[intval(count($processingTimes) / 2)], 3) . "ms\n";
echo "95パーセンタイル: " . round($processingTimes[intval(count($processingTimes) * 0.95)], 3) . "ms\n";
}
private static function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= (1 << (10 * $pow));
return round($bytes, 2) . ' ' . $units[$pow];
}
public static function resetMetrics() {
self::$metrics = [];
}
}
// 使用例とベンチマーク
echo "=== JSON処理パフォーマンステスト ===\n\n";
// テストデータの生成
$testData = [
'small' => ['name' => '田中', 'age' => 30],
'medium' => array_fill(0, 1000, ['id' => rand(1, 1000), 'name' => 'ユーザー']),
'large' => array_fill(0, 10000, ['id' => rand(1, 1000), 'name' => 'ユーザー', 'data' => str_repeat('x', 100)])
];
foreach ($testData as $size => $data) {
echo "--- {$size}データのテスト ---\n";
// エンコードテスト
$encodeResult = JsonPerformanceMonitor::measureJsonOperation('encode', $data, JSON_UNESCAPED_UNICODE);
echo "エンコード: " . ($encodeResult['success'] ? '成功' : 'エラー: ' . $encodeResult['metrics']['error_message']) . "\n";
echo "処理時間: " . round($encodeResult['metrics']['processing_time'], 3) . "ms\n";
if ($encodeResult['success']) {
// デコードテスト
$decodeResult = JsonPerformanceMonitor::measureJsonOperation('decode', $encodeResult['result']);
echo "デコード: " . ($decodeResult['success'] ? '成功' : 'エラー: ' . $decodeResult['metrics']['error_message']) . "\n";
echo "処理時間: " . round($decodeResult['metrics']['processing_time'], 3) . "ms\n";
}
echo "\n";
}
// パフォーマンスレポートの生成
JsonPerformanceMonitor::generateReport();
Web開発での実践的活用例
フォーム処理でのエラーハンドリング
class FormHandler {
public static function processAjaxRequest() {
header('Content-Type: application/json; charset=utf-8');
try {
// POSTデータの取得
$input = file_get_contents('php://input');
if (empty($input)) {
throw new InvalidArgumentException('リクエストデータが空です');
}
// JSON解析
$data = json_decode($input, true);
// エラーチェック
if (json_last_error() !== JSON_ERROR_NONE) {
$errorMsg = json_last_error_msg();
// フロントエンド向けのユーザーフレンドリーなメッセージ
$userMessage = self::getUserFriendlyMessage($errorMsg);
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $userMessage,
'technical_details' => [
'error_code' => json_last_error(),
'error_message' => $errorMsg,
'input_length' => strlen($input)
]
], JSON_UNESCAPED_UNICODE);
return;
}
// フォームデータの処理
$result = self::validateAndProcessForm($data);
echo json_encode([
'success' => true,
'message' => '処理が完了しました',
'data' => $result
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'サーバーエラーが発生しました',
'message' => $e->getMessage()
], JSON_UNESCAPED_UNICODE);
}
}
private static function getUserFriendlyMessage($technicalMessage) {
$friendlyMessages = [
'Syntax error' => 'データの形式が正しくありません。入力内容を確認してください。',
'Control character error, possibly incorrectly encoded' => '入力に無効な文字が含まれています。',
'Malformed UTF-8 characters, possibly incorrectly encoded' => '文字エンコーディングに問題があります。',
'Maximum stack depth exceeded' => 'データが複雑すぎます。',
'State mismatch (invalid or malformed JSON)' => 'データの構造に問題があります。'
];
return $friendlyMessages[$technicalMessage] ?? 'データの処理中にエラーが発生しました。';
}
private static function validateAndProcessForm($data) {
// バリデーション処理(例)
$required = ['name', 'email'];
foreach ($required as $field) {
if (!isset($data[$field]) || empty($data[$field])) {
throw new InvalidArgumentException("{$field}は必須項目です");
}
}
return ['id' => uniqid(), 'processed_at' => date('c')];
}
}
// 使用例(APIエンドポイント)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
FormHandler::processAjaxRequest();
}
設定ファイル読み込みでの堅牢なエラーハンドリング
class ConfigLoader {
private $configPath;
private $config = null;
public function __construct($configPath) {
$this->configPath = $configPath;
}
public function load() {
if (!file_exists($this->configPath)) {
throw new RuntimeException("設定ファイルが見つかりません: {$this->configPath}");
}
$content = file_get_contents($this->configPath);
if ($content === false) {
throw new RuntimeException("設定ファイルの読み込みに失敗しました: {$this->configPath}");
}
$config = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$errorMsg = json_last_error_msg();
$errorCode = json_last_error();
// 詳細なエラー情報をログに記録
$errorDetails = [
'file' => $this->configPath,
'error_code' => $errorCode,
'error_message' => $errorMsg,
'file_size' => strlen($content),
'encoding_check' => mb_check_encoding($content, 'UTF-8'),
'sample_content' => mb_substr($content, 0, 200)
];
error_log('Config load error: ' . json_encode($errorDetails, JSON_UNESCAPED_UNICODE));
// 構文エラーの場合、行番号を特定
if ($errorCode === JSON_ERROR_SYNTAX) {
$lineInfo = $this->findErrorLine($content);
if ($lineInfo) {
$errorMsg .= " (推定位置: {$lineInfo['line']}行目付近)";
}
}
throw new RuntimeException("設定ファイルのJSONが不正です: {$errorMsg}");
}
$this->config = $config;
return $config;
}
private function findErrorLine($content) {
// 簡易的なエラー行検出
$lines = explode("\n", $content);
for ($i = 0; $i < count($lines); $i++) {
$partialJson = implode("\n", array_slice($lines, 0, $i + 1));
json_decode($partialJson);
if (json_last_error() !== JSON_ERROR_NONE && json_last_error() !== JSON_ERROR_STATE_MISMATCH) {
return [
'line' => $i + 1,
'content' => trim($lines[$i])
];
}
}
return null;
}
public function get($key, $default = null) {
if ($this->config === null) {
$this->load();
}
return $this->config[$key] ?? $default;
}
public function save($config = null) {
$dataToSave = $config ?? $this->config;
if ($dataToSave === null) {
throw new RuntimeException('保存するデータがありません');
}
$json = json_encode($dataToSave, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
if (json_last_error() !== JSON_ERROR_NONE) {
$errorMsg = json_last_error_msg();
throw new RuntimeException("設定データのJSON変換に失敗しました: {$errorMsg}");
}
$result = file_put_contents($this->configPath, $json, LOCK_EX);
if ($result === false) {
throw new RuntimeException("設定ファイルの保存に失敗しました: {$this->configPath}");
}
$this->config = $dataToSave;
return true;
}
}
// 使用例
try {
$config = new ConfigLoader('app_config.json');
$settings = $config->load();
echo "設定読み込み成功\n";
echo "データベースホスト: " . $config->get('database.host', 'localhost') . "\n";
} catch (RuntimeException $e) {
echo "設定エラー: " . $e->getMessage() . "\n";
// フォールバック設定
$defaultConfig = [
'database' => ['host' => 'localhost', 'port' => 3306],
'cache' => ['enabled' => false]
];
echo "デフォルト設定を使用します\n";
}
API開発での包括的エラーレスポンス
class ApiErrorResponseBuilder {
private $errors = [];
private $context = [];
public function addJsonError($operation, $data = null) {
$errorCode = json_last_error();
$errorMessage = json_last_error_msg();
if ($errorCode === JSON_ERROR_NONE) {
return $this; // エラーなし
}
$this->errors[] = [
'type' => 'json_error',
'operation' => $operation,
'code' => $errorCode,
'message' => $errorMessage,
'user_message' => $this->getLocalizedMessage($errorMessage),
'timestamp' => date('c')
];
// デバッグ情報(開発環境のみ)
if (defined('DEBUG') && DEBUG) {
$debugInfo = [
'php_version' => PHP_VERSION,
'memory_usage' => memory_get_usage(true)
];
if ($data !== null) {
$debugInfo['data_type'] = gettype($data);
if (is_string($data)) {
$debugInfo['data_length'] = strlen($data);
$debugInfo['data_sample'] = mb_substr($data, 0, 100);
}
}
$this->errors[count($this->errors) - 1]['debug'] = $debugInfo;
}
return $this;
}
public function addContext($key, $value) {
$this->context[$key] = $value;
return $this;
}
public function buildResponse($httpCode = 400) {
$response = [
'success' => false,
'timestamp' => date('c'),
'errors' => $this->errors
];
if (!empty($this->context)) {
$response['context'] = $this->context;
}
// レスポンスヘッダーの設定
header('Content-Type: application/json; charset=utf-8');
http_response_code($httpCode);
// レスポンスのJSON化
$jsonResponse = json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
// レスポンス自体のJSON化でエラーが発生した場合の緊急対応
if (json_last_error() !== JSON_ERROR_NONE) {
$emergencyResponse = [
'success' => false,
'error' => 'Critical error: Unable to encode error response',
'details' => json_last_error_msg()
];
echo json_encode($emergencyResponse);
} else {
echo $jsonResponse;
}
}
private function getLocalizedMessage($technicalMessage) {
$messages = [
'Syntax error' => [
'ja' => 'リクエストデータの形式が正しくありません',
'en' => 'Invalid request data format'
],
'Control character error, possibly incorrectly encoded' => [
'ja' => '不正な文字が含まれています',
'en' => 'Invalid characters detected'
],
'Malformed UTF-8 characters, possibly incorrectly encoded' => [
'ja' => '文字エンコーディングエラー',
'en' => 'Character encoding error'
],
'Maximum stack depth exceeded' => [
'ja' => 'データが複雑すぎます',
'en' => 'Data structure too complex'
]
];
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
$isJapanese = strpos($lang, 'ja') !== false;
if (isset($messages[$technicalMessage])) {
return $messages[$technicalMessage][$isJapanese ? 'ja' : 'en'];
}
return $isJapanese ? 'データ処理エラー' : 'Data processing error';
}
}
// 使用例
function handleApiRequest() {
$errorBuilder = new ApiErrorResponseBuilder();
try {
$input = file_get_contents('php://input');
$data = json_decode($input, true);
// JSON解析エラーをチェック
$errorBuilder->addJsonError('request_parsing', $input);
if (!empty($errorBuilder->errors)) {
$errorBuilder
->addContext('endpoint', $_SERVER['REQUEST_URI'])
->addContext('method', $_SERVER['REQUEST_METHOD'])
->addContext('user_agent', $_SERVER['HTTP_USER_AGENT'] ?? 'unknown')
->buildResponse(400);
return;
}
// 正常処理
$result = processData($data);
// レスポンス作成
$response = ['success' => true, 'data' => $result];
$jsonResponse = json_encode($response, JSON_UNESCAPED_UNICODE);
$errorBuilder->addJsonError('response_encoding', $response);
if (!empty($errorBuilder->errors)) {
$errorBuilder
->addContext('operation', 'response_generation')
->buildResponse(500);
return;
}
header('Content-Type: application/json; charset=utf-8');
echo $jsonResponse;
} catch (Exception $e) {
$errorBuilder
->addContext('exception', $e->getMessage())
->addContext('file', $e->getFile())
->addContext('line', $e->getLine())
->buildResponse(500);
}
}
function processData($data) {
// データ処理のダミー実装
return ['processed' => true, 'count' => count($data)];
}
まとめ
json_last_error_msg関数は、JSON処理におけるデバッグとエラーハンドリングを格段に向上させる重要なツールです。この関数を効果的に活用することで、以下のメリットが得られます:
主要なメリット:
- 迅速な問題特定: 数値エラーコードではなく、わかりやすいメッセージでエラーの原因を把握
- ユーザーエクスペリエンスの向上: 技術的なエラーを一般ユーザー向けのメッセージに変換
- デバッグ効率の大幅改善: 詳細なエラー情報により開発時間を短縮
- 運用時の安定性向上: 適切なエラーハンドリングによりシステムクラッシュを防止
実装のベストプラクティス:
- 常にjson_last_error()と組み合わせて使用する
- エラーメッセージの日本語化でユーザビリティを向上
- ログ出力でトラブルシューティングを効率化
- 開発環境と本番環境で異なるエラー表示レベルを設定
- パフォーマンス測定と組み合わせて最適化を図る
覚えておくべきポイント:
- PHP 5.5.0以降で使用可能
- json_decode、json_encode両方のエラーに対応
- 英語メッセージのため、必要に応じて日本語化を実装
- エラーハンドリングはセキュリティの観点からも重要
- 適切なログ出力でシステム監視を強化
json_last_error_msg関数をマスターすることで、より堅牢で保守性の高いPHPアプリケーションを開発できるようになります。JSON処理は現代のWeb開発において中核的な技術ですので、ぜひこれらのテクニックを実際のプロジェクトで活用してください。