[PHP]parse_ini_string関数の使い方を完全解説!文字列から設定を読み込む方法

PHP

はじめに

PHPでアプリケーションを開発する際、文字列として持っている設定情報を解析したいと思ったことはありませんか?

データベースから取得した設定、APIレスポンスの設定データ、動的に生成された設定など、ファイルではなく文字列として設定情報を扱いたいケースは多くあります。

その文字列形式のINI設定を解析するのが**parse_ini_string関数**です。この関数を使えば、INI形式の文字列を直接PHPの配列として読み込めます。

この記事では、parse_ini_stringの基本から実践的な活用方法まで、詳しく解説します。

parse_ini_stringとは?

parse_ini_stringは、INI形式の文字列を解析してPHPの配列として返す関数です。

基本構文

parse_ini_string(
    string $ini_string,
    bool $process_sections = false,
    int $scanner_mode = INI_SCANNER_NORMAL
): array|false

パラメータ

  • $ini_string: INI形式の文字列
  • $process_sections: セクションを処理するか(デフォルト: false
  • $scanner_mode: スキャナーモード
    • INI_SCANNER_NORMAL – デフォルト
    • INI_SCANNER_RAW – 値を加工しない
    • INI_SCANNER_TYPED – 型を保持(PHP 5.6.1以降)

戻り値

  • 成功時: 連想配列
  • 失敗時: false

parse_ini_fileとの違い

関数入力用途
parse_ini_file()ファイルパスファイルから設定を読み込む
parse_ini_string()文字列文字列から設定を解析

対応バージョン

  • PHP 5.3.0 以降で使用可能
  • INI_SCANNER_TYPEDはPHP 5.6.1以降

基本的な使い方

シンプルな設定の解析

<?php
$ini_string = <<<INI
app_name = "My Application"
debug = true
max_users = 100
version = 1.0.5
INI;

$config = parse_ini_string($ini_string);

if ($config === false) {
    die("INI文字列の解析に失敗しました\n");
}

echo "アプリ名: {$config['app_name']}\n";
echo "デバッグ: " . ($config['debug'] ? 'ON' : 'OFF') . "\n";
echo "最大ユーザー数: {$config['max_users']}\n";
echo "バージョン: {$config['version']}\n";

// 出力:
// アプリ名: My Application
// デバッグ: ON
// 最大ユーザー数: 100
// バージョン: 1.0.5
?>

セクション付き設定の解析

<?php
$ini_string = <<<INI

[app]

name = “My App” version = “1.0.0”

[database]

host = localhost port = 3306 username = root password = secret

[cache]

driver = redis ttl = 3600 INI; // セクションを処理する $config = parse_ini_string($ini_string, true); echo “アプリ名: {$config[‘app’][‘name’]}\n”; echo “DBホスト: {$config[‘database’][‘host’]}\n”; echo “キャッシュドライバー: {$config[‘cache’][‘driver’]}\n”; // セクションごとに処理 foreach ($config as $section => $values) { echo “\n[{$section}]\n”; foreach ($values as $key => $value) { echo ” {$key} = {$value}\n”; } } ?>

型を保持した解析

<?php
$ini_string = <<<INI
string_value = "hello"
int_value = 123
float_value = 3.14
bool_true = true
bool_false = false
null_value = null
INI;

// 型を保持して解析
$config = parse_ini_string($ini_string, false, INI_SCANNER_TYPED);

foreach ($config as $key => $value) {
    $type = gettype($value);
    $value_str = var_export($value, true);
    echo "{$key}: {$value_str} ({$type})\n";
}

// 出力:
// string_value: 'hello' (string)
// int_value: 123 (integer)
// float_value: 3.14 (double)
// bool_true: true (boolean)
// bool_false: false (boolean)
// null_value: NULL (NULL)
?>

実践的な使用例

例1: データベースから設定を取得

<?php
class DatabaseConfig {
    private $pdo;
    
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    
    /**
     * データベースから設定を取得して解析
     */
    public function getConfig($config_key) {
        // データベースから設定文字列を取得
        $stmt = $this->pdo->prepare(
            "SELECT config_value FROM app_configs WHERE config_key = :key"
        );
        $stmt->execute(['key' => $config_key]);
        
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if (!$row) {
            throw new RuntimeException("設定 '{$config_key}' が見つかりません");
        }
        
        $ini_string = $row['config_value'];
        
        // INI文字列を解析
        $config = parse_ini_string($ini_string, true, INI_SCANNER_TYPED);
        
        if ($config === false) {
            throw new RuntimeException("設定の解析に失敗しました");
        }
        
        return $config;
    }
    
    /**
     * 設定をデータベースに保存
     */
    public function saveConfig($config_key, $ini_string) {
        // 保存前に解析して検証
        $config = parse_ini_string($ini_string, true, INI_SCANNER_TYPED);
        
        if ($config === false) {
            throw new RuntimeException("無効なINI形式です");
        }
        
        // データベースに保存
        $stmt = $this->pdo->prepare(
            "INSERT INTO app_configs (config_key, config_value, updated_at) 
             VALUES (:key, :value, NOW())
             ON DUPLICATE KEY UPDATE 
             config_value = :value, updated_at = NOW()"
        );
        
        $stmt->execute([
            'key' => $config_key,
            'value' => $ini_string
        ]);
        
        return true;
    }
    
    /**
     * すべての設定を取得
     */
    public function getAllConfigs() {
        $stmt = $this->pdo->query(
            "SELECT config_key, config_value FROM app_configs"
        );
        
        $configs = [];
        
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $parsed = parse_ini_string(
                $row['config_value'],
                true,
                INI_SCANNER_TYPED
            );
            
            if ($parsed !== false) {
                $configs[$row['config_key']] = $parsed;
            }
        }
        
        return $configs;
    }
}

// 使用例(テーブル作成)
/*
CREATE TABLE app_configs (
    config_key VARCHAR(100) PRIMARY KEY,
    config_value TEXT NOT NULL,
    updated_at DATETIME NOT NULL
);
*/

// 使用例
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');
$dbConfig = new DatabaseConfig($pdo);

// 設定を保存
$ini_string = <<<INI

[email]

host = smtp.example.com port = 587 username = user@example.com INI; $dbConfig->saveConfig(‘email_config’, $ini_string); // 設定を取得 $email_config = $dbConfig->getConfig(‘email_config’); echo “SMTPホスト: {$email_config[‘email’][‘host’]}\n”; ?>

例2: 動的な設定生成とテスト

<?php
class ConfigBuilder {
    private $sections = [];
    
    /**
     * セクションを追加
     */
    public function addSection($name, $values) {
        $this->sections[$name] = $values;
        return $this;
    }
    
    /**
     * キーバリューを追加
     */
    public function add($key, $value) {
        $this->sections[$key] = $value;
        return $this;
    }
    
    /**
     * INI文字列を生成
     */
    public function build() {
        $ini_lines = [];
        
        foreach ($this->sections as $key => $value) {
            if (is_array($value)) {
                // セクション
                $ini_lines[] = "[{$key}]";
                
                foreach ($value as $k => $v) {
                    $ini_lines[] = $this->formatLine($k, $v);
                }
                
                $ini_lines[] = '';
            } else {
                // セクションなし
                $ini_lines[] = $this->formatLine($key, $value);
            }
        }
        
        return implode("\n", $ini_lines);
    }
    
    private function formatLine($key, $value) {
        if (is_bool($value)) {
            $value = $value ? 'true' : 'false';
        } elseif (is_null($value)) {
            $value = 'null';
        } elseif (is_string($value) && preg_match('/[\s;#"]/', $value)) {
            $value = '"' . addslashes($value) . '"';
        }
        
        return "{$key} = {$value}";
    }
    
    /**
     * ビルドして解析を確認
     */
    public function buildAndValidate() {
        $ini_string = $this->build();
        
        $parsed = parse_ini_string($ini_string, true, INI_SCANNER_TYPED);
        
        if ($parsed === false) {
            throw new RuntimeException("生成されたINI文字列が無効です");
        }
        
        return [
            'ini_string' => $ini_string,
            'parsed' => $parsed
        ];
    }
}

// 使用例
$builder = new ConfigBuilder();

$builder
    ->addSection('app', [
        'name' => 'Test App',
        'debug' => true,
        'version' => '2.0.0'
    ])
    ->addSection('database', [
        'host' => 'localhost',
        'port' => 3306,
        'timeout' => 30
    ])
    ->addSection('features', [
        'cache_enabled' => true,
        'api_enabled' => false
    ]);

$result = $builder->buildAndValidate();

echo "生成されたINI:\n";
echo $result['ini_string'] . "\n\n";

echo "解析結果:\n";
print_r($result['parsed']);
?>

例3: 設定のマージとオーバーライド

<?php
class ConfigMerger {
    /**
     * 複数のINI文字列をマージ
     */
    public static function merge(...$ini_strings) {
        $merged_config = [];
        
        foreach ($ini_strings as $ini_string) {
            $config = parse_ini_string($ini_string, true, INI_SCANNER_TYPED);
            
            if ($config === false) {
                throw new RuntimeException("INI文字列の解析に失敗しました");
            }
            
            $merged_config = self::mergeArrays($merged_config, $config);
        }
        
        return $merged_config;
    }
    
    private static function mergeArrays($base, $override) {
        foreach ($override as $key => $value) {
            if (is_array($value) && isset($base[$key]) && is_array($base[$key])) {
                $base[$key] = self::mergeArrays($base[$key], $value);
            } else {
                $base[$key] = $value;
            }
        }
        
        return $base;
    }
    
    /**
     * 環境変数でオーバーライド
     */
    public static function mergeWithEnv($ini_string, $prefix = 'APP_') {
        $config = parse_ini_string($ini_string, true, INI_SCANNER_TYPED);
        
        if ($config === false) {
            throw new RuntimeException("INI文字列の解析に失敗しました");
        }
        
        // 環境変数でオーバーライド
        foreach ($_ENV as $env_key => $env_value) {
            if (strpos($env_key, $prefix) === 0) {
                // APP_DATABASE_HOST -> database.host
                $key = strtolower(substr($env_key, strlen($prefix)));
                $parts = explode('_', $key);
                
                if (count($parts) === 2) {
                    [$section, $name] = $parts;
                    if (isset($config[$section])) {
                        $config[$section][$name] = $env_value;
                    }
                }
            }
        }
        
        return $config;
    }
}

// 使用例
$base_config = <<<INI

[app]

name = “My App” debug = false version = “1.0.0”

[database]

host = localhost port = 3306 INI; $override_config = <<<INI

[app]

debug = true

[database]

host = db.example.com username = admin password = secret INI; // マージ $merged = ConfigMerger::merge($base_config, $override_config); echo “=== マージ結果 ===\n”; print_r($merged); // 環境変数でオーバーライド $_ENV[‘APP_DATABASE_HOST’] = ‘prod-db.example.com’; $_ENV[‘APP_DATABASE_PORT’] = ‘3307’; $final_config = ConfigMerger::mergeWithEnv($base_config); echo “\n=== 環境変数適用後 ===\n”; print_r($final_config); ?>

例4: 設定のバリデーションとサニタイズ

<?php
class ConfigSanitizer {
    /**
     * INI文字列をサニタイズ
     */
    public static function sanitize($ini_string) {
        // コメント行を保持しながら解析
        $lines = explode("\n", $ini_string);
        $sanitized_lines = [];
        $current_section = null;
        
        foreach ($lines as $line) {
            $line = trim($line);
            
            // 空行
            if (empty($line)) {
                $sanitized_lines[] = '';
                continue;
            }
            
            // コメント行
            if ($line[0] === ';' || $line[0] === '#') {
                $sanitized_lines[] = $line;
                continue;
            }
            
            // セクション
            if (preg_match('/^\[([^\]]+)\]$/', $line, $matches)) {
                $current_section = trim($matches[1]);
                $sanitized_lines[] = "[{$current_section}]";
                continue;
            }
            
            // キー=値
            if (preg_match('/^([^=]+)=(.*)$/', $line, $matches)) {
                $key = trim($matches[1]);
                $value = trim($matches[2]);
                
                // 値をサニタイズ
                $sanitized_value = self::sanitizeValue($value);
                $sanitized_lines[] = "{$key} = {$sanitized_value}";
            }
        }
        
        return implode("\n", $sanitized_lines);
    }
    
    private static function sanitizeValue($value) {
        // クォートを除去
        $value = trim($value, '"\'');
        
        // HTMLエスケープ
        $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
        
        // 再度クォート
        if (preg_match('/[\s;#]/', $value)) {
            $value = '"' . $value . '"';
        }
        
        return $value;
    }
    
    /**
     * 危険な設定値をチェック
     */
    public static function validate($ini_string) {
        $config = parse_ini_string($ini_string, true, INI_SCANNER_TYPED);
        
        if ($config === false) {
            throw new RuntimeException("INI文字列の解析に失敗しました");
        }
        
        $issues = [];
        
        // 危険なパターンをチェック
        foreach ($config as $section => $values) {
            if (!is_array($values)) {
                $values = [$section => $values];
                $section = 'root';
            }
            
            foreach ($values as $key => $value) {
                // SQLインジェクションの可能性
                if (is_string($value) && preg_match('/(union|select|insert|update|delete|drop)/i', $value)) {
                    $issues[] = "[{$section}] {$key}: SQLキーワードを含んでいます";
                }
                
                // ファイルパストラバーサル
                if (is_string($value) && preg_match('/\.\.\//', $value)) {
                    $issues[] = "[{$section}] {$key}: パストラバーサルの可能性があります";
                }
                
                // コマンドインジェクション
                if (is_string($value) && preg_match('/[;&|`$()]/', $value)) {
                    $issues[] = "[{$section}] {$key}: コマンドインジェクションの可能性があります";
                }
            }
        }
        
        if (!empty($issues)) {
            return [
                'valid' => false,
                'issues' => $issues
            ];
        }
        
        return [
            'valid' => true,
            'config' => $config
        ];
    }
}

// 使用例
$unsafe_ini = <<<INI

[database]

host = localhost’; DROP TABLE users; — path = ../../../etc/passwd

[command]

exec = rm -rf / INI; // バリデーション $validation = ConfigSanitizer::validate($unsafe_ini); if (!$validation[‘valid’]) { echo “⚠ セキュリティ上の問題が検出されました:\n”; foreach ($validation[‘issues’] as $issue) { echo ” – {$issue}\n”; } } // サニタイズ $safe_ini = <<<INI

[app]

name = <script>alert(‘xss’)</script> path = /var/www/html INI; $sanitized = ConfigSanitizer::sanitize($safe_ini); echo “\nサニタイズ後:\n{$sanitized}\n”; ?>

例5: 設定のテンプレート処理

<?php
class ConfigTemplate {
    /**
     * テンプレート変数を置換
     */
    public static function render($ini_template, $variables) {
        // 変数を置換(例: {{var_name}} -> 実際の値)
        $rendered = $ini_template;
        
        foreach ($variables as $key => $value) {
            $placeholder = '{{' . $key . '}}';
            $rendered = str_replace($placeholder, $value, $rendered);
        }
        
        // 未置換の変数をチェック
        if (preg_match_all('/\{\{([^}]+)\}\}/', $rendered, $matches)) {
            $missing = array_unique($matches[1]);
            throw new RuntimeException(
                "未定義の変数があります: " . implode(', ', $missing)
            );
        }
        
        return $rendered;
    }
    
    /**
     * 条件付きセクション
     */
    public static function renderConditional($ini_template, $conditions) {
        $lines = explode("\n", $ini_template);
        $result_lines = [];
        $skip = false;
        
        foreach ($lines as $line) {
            // 条件開始: ; @if condition_name
            if (preg_match('/^;\s*@if\s+(\w+)/', $line, $matches)) {
                $condition = $matches[1];
                $skip = empty($conditions[$condition]);
                continue;
            }
            
            // 条件終了: ; @endif
            if (preg_match('/^;\s*@endif/', $line)) {
                $skip = false;
                continue;
            }
            
            if (!$skip) {
                $result_lines[] = $line;
            }
        }
        
        return implode("\n", $result_lines);
    }
}

// 使用例1: 変数置換
$template = <<<INI

[app]

name = “{{app_name}}” version = “{{app_version}}”

[database]

host = {{db_host}} port = {{db_port}} username = {{db_user}} password = {{db_pass}} INI; $variables = [ ‘app_name’ => ‘Production App’, ‘app_version’ => ‘2.0.0’, ‘db_host’ => ‘prod-db.example.com’, ‘db_port’ => 3306, ‘db_user’ => ‘prod_user’, ‘db_pass’ => ‘secret123’ ]; $rendered = ConfigTemplate::render($template, $variables); echo “=== レンダリング結果 ===\n{$rendered}\n”; // 解析確認 $config = parse_ini_string($rendered, true, INI_SCANNER_TYPED); print_r($config); // 使用例2: 条件付きセクション $conditional_template = <<<INI

[app]

name = “My App” ; @if enable_debug

[debug]

level = verbose log_queries = true ; @endif ; @if enable_cache

[cache]

driver = redis ttl = 3600 ; @endif INI; $conditions = [ ‘enable_debug’ => true, ‘enable_cache’ => false ]; $conditional_result = ConfigTemplate::renderConditional( $conditional_template, $conditions ); echo “\n=== 条件付きレンダリング ===\n{$conditional_result}\n”; $config = parse_ini_string($conditional_result, true); print_r($config); ?>

parse_ini_fileとの使い分け

いつparse_ini_stringを使うか

<?php
// ケース1: データベースから設定を取得
$config_string = $db->fetchConfigString();
$config = parse_ini_string($config_string, true);

// ケース2: APIレスポンスを解析
$response = file_get_contents('https://api.example.com/config');
$config = parse_ini_string($response, true);

// ケース3: 動的に生成された設定
$ini_string = generateConfigString($user_preferences);
$config = parse_ini_string($ini_string, true);

// ケース4: インラインで定義された設定
$config = parse_ini_string(<<<INI

setting1 = value1 setting2 = value2 INI, true); // ケース5: 設定のテスト function testConfig($ini_string) { $config = parse_ini_string($ini_string, true); assert($config[‘app’][‘debug’] === true); } ?>

エラーハンドリング

解析エラーの処理

<?php
function safeParseIniString($ini_string, $process_sections = false) {
    // エラー抑制して実行
    $config = @parse_ini_string($ini_string, $process_sections, INI_SCANNER_TYPED);
    
    if ($config === false) {
        // 詳細なエラー情報を取得
        $error = error_get_last();
        
        throw new RuntimeException(
            "INI文字列の解析に失敗しました: " . 
            ($error['message'] ?? '不明なエラー')
        );
    }
    
    return $config;
}

// 使用例
$invalid_ini = <<<INI
[section
key = value
INI;

try {
    $config = safeParseIniString($invalid_ini);
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
    // エラー: INI文字列の解析に失敗しました: syntax error, unexpected...
}
?>

まとめ

parse_ini_stringは、INI形式の文字列を解析する関数です。

主な特徴:

  • ✅ 文字列から直接設定を解析
  • parse_ini_fileと同じ機能を文字列で提供
  • ✅ 動的な設定処理に最適
  • ✅ データベースやAPIからの設定に対応

使用場面:

  • データベースに保存された設定の読み込み
  • APIレスポンスの解析
  • 動的に生成された設定の処理
  • 設定のテンプレート処理

parse_ini_fileとの使い分け:

  • ファイルから読むparse_ini_file()
  • 文字列から解析parse_ini_string()
  • 動的生成/DB保存parse_ini_string()

関連関数:

  • parse_ini_file() – ファイルからINIを解析
  • get_cfg_var() – PHP設定を取得

ベストプラクティス:

  • 外部入力は必ずバリデーション
  • セキュリティチェックを実装
  • エラーハンドリングを適切に行う
  • テンプレート機能で柔軟性を確保

この関数を理解して、より柔軟な設定管理を実現しましょう!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!

タイトルとURLをコピーしました