こんにちは!今回は、PHPのrunkit7拡張機能で提供されるrunkit7_zval_inspect()関数について詳しく解説していきます。PHPの変数の内部構造(zval)を詳細に調査できる、デバッグやパフォーマンス分析に役立つ関数です!
runkit7_zval_inspect関数とは?
runkit7_zval_inspect()関数は、PHP変数の内部表現(zval構造体)の詳細情報を取得することができる関数です。
PHPの変数は内部的に「zval」という構造体で管理されていますが、この関数を使えばその詳細(型、参照カウント、値など)を確認できます!
基本的な構文
runkit7_zval_inspect(mixed $value): array
- $value: 調査したい変数
- 戻り値: zval構造体の情報を含む連想配列
返される配列には以下のような情報が含まれます:
type: 変数の型refcount: 参照カウントis_ref: 参照変数かどうかvalue: 変数の値
前提条件
runkit7拡張機能のインストール
pecl install runkit7
php.iniに以下を追加:
extension=runkit7.so
インストール確認:
<?php
if (function_exists('runkit7_zval_inspect')) {
echo "runkit7が利用可能です!";
} else {
echo "runkit7がインストールされていません";
}
?>
基本的な使用例
様々な型の調査
// 整数
$int = 42;
print_r(runkit7_zval_inspect($int));
// 文字列
$string = "Hello, World!";
print_r(runkit7_zval_inspect($string));
// 配列
$array = [1, 2, 3];
print_r(runkit7_zval_inspect($array));
// オブジェクト
$object = new stdClass();
$object->name = "Test";
print_r(runkit7_zval_inspect($object));
// NULL
$null = null;
print_r(runkit7_zval_inspect($null));
// ブール値
$bool = true;
print_r(runkit7_zval_inspect($bool));
// 浮動小数点数
$float = 3.14;
print_r(runkit7_zval_inspect($float));
参照カウントの理解
// 単一の変数
$a = "test";
echo "変数 \$a:\n";
print_r(runkit7_zval_inspect($a));
echo "\n";
// 別の変数に代入(コピー)
$b = $a;
echo "コピー後の \$a:\n";
print_r(runkit7_zval_inspect($a));
echo "\n";
// 参照として代入
$c = &$a;
echo "参照代入後の \$a:\n";
print_r(runkit7_zval_inspect($a));
echo "\n";
echo "\$c (参照):\n";
print_r(runkit7_zval_inspect($c));
実践的な使用例
例1: メモリ使用量の分析ツール
class MemoryAnalyzer {
public static function analyze($variable, $name = 'variable') {
$info = runkit7_zval_inspect($variable);
echo "=== 変数分析: {$name} ===\n";
echo "型: " . self::getTypeName($info['type']) . "\n";
echo "参照カウント: {$info['refcount']}\n";
echo "参照変数: " . ($info['is_ref'] ? 'はい' : 'いいえ') . "\n";
// 型に応じた追加情報
if (is_array($variable)) {
echo "配列要素数: " . count($variable) . "\n";
echo "推定メモリ使用量: " . self::estimateArrayMemory($variable) . " bytes\n";
} elseif (is_string($variable)) {
echo "文字列長: " . strlen($variable) . " bytes\n";
} elseif (is_object($variable)) {
echo "クラス名: " . get_class($variable) . "\n";
echo "プロパティ数: " . count(get_object_vars($variable)) . "\n";
}
echo "\n";
}
private static function getTypeName($type) {
$types = [
1 => 'NULL',
2 => 'FALSE',
3 => 'TRUE',
4 => 'LONG (整数)',
5 => 'DOUBLE (浮動小数点)',
6 => 'STRING (文字列)',
7 => 'ARRAY (配列)',
8 => 'OBJECT (オブジェクト)',
9 => 'RESOURCE (リソース)',
10 => 'REFERENCE (参照)'
];
return $types[$type] ?? "UNKNOWN ({$type})";
}
private static function estimateArrayMemory($array) {
// 簡易的な推定
$memory = 0;
foreach ($array as $key => $value) {
// キーのメモリ
$memory += is_string($key) ? strlen($key) : 8;
// 値のメモリ
if (is_string($value)) {
$memory += strlen($value);
} elseif (is_array($value)) {
$memory += self::estimateArrayMemory($value);
} else {
$memory += 8; // 基本型のおおよそのサイズ
}
}
return $memory;
}
}
// 使用例
$smallArray = [1, 2, 3];
MemoryAnalyzer::analyze($smallArray, 'smallArray');
$largeArray = range(1, 1000);
MemoryAnalyzer::analyze($largeArray, 'largeArray');
$string = str_repeat('A', 1000);
MemoryAnalyzer::analyze($string, 'longString');
$object = new stdClass();
$object->name = '田中太郎';
$object->age = 30;
$object->email = 'tanaka@example.com';
MemoryAnalyzer::analyze($object, 'userObject');
例2: 参照の追跡ツール
class ReferenceTracker {
private static $trackingId = 0;
public static function track($variable, $label = null) {
self::$trackingId++;
$id = self::$trackingId;
$info = runkit7_zval_inspect($variable);
$label = $label ?? "Variable #{$id}";
echo "[{$id}] {$label}\n";
echo " 型: " . self::getTypeString($variable) . "\n";
echo " 参照カウント: {$info['refcount']}\n";
echo " 参照: " . ($info['is_ref'] ? 'YES' : 'NO') . "\n";
if (is_scalar($variable)) {
$displayValue = is_bool($variable)
? ($variable ? 'true' : 'false')
: $variable;
echo " 値: {$displayValue}\n";
}
echo "\n";
return $id;
}
private static function getTypeString($var) {
$type = gettype($var);
if ($type === 'object') {
return 'object (' . get_class($var) . ')';
}
return $type;
}
public static function compareReferences($var1, $var2, $label1 = 'Var1', $label2 = 'Var2') {
echo "=== 参照比較: {$label1} vs {$label2} ===\n";
$info1 = runkit7_zval_inspect($var1);
$info2 = runkit7_zval_inspect($var2);
echo "{$label1} - 参照カウント: {$info1['refcount']}, 参照: " .
($info1['is_ref'] ? 'YES' : 'NO') . "\n";
echo "{$label2} - 参照カウント: {$info2['refcount']}, 参照: " .
($info2['is_ref'] ? 'YES' : 'NO') . "\n";
// 同じzvalを指しているか判定(完全な判定ではないが参考値)
if ($info1['is_ref'] && $info2['is_ref'] && $var1 === $var2) {
echo "結果: 同じ参照を共有している可能性が高い\n";
} else {
echo "結果: 異なる変数または参照\n";
}
echo "\n";
}
}
// 使用例
echo "=== 値渡しの場合 ===\n";
$original = "Hello";
ReferenceTracker::track($original, 'original');
$copy = $original;
ReferenceTracker::track($copy, 'copy');
ReferenceTracker::compareReferences($original, $copy, 'original', 'copy');
echo "=== 参照渡しの場合 ===\n";
$var1 = "Test";
ReferenceTracker::track($var1, 'var1');
$var2 = &$var1;
ReferenceTracker::track($var2, 'var2 (参照)');
ReferenceTracker::compareReferences($var1, $var2, 'var1', 'var2');
echo "=== 配列の場合 ===\n";
$arr1 = [1, 2, 3];
ReferenceTracker::track($arr1, 'arr1');
$arr2 = $arr1;
ReferenceTracker::track($arr2, 'arr2 (コピー)');
$arr1[] = 4;
echo "arr1に要素追加後:\n";
ReferenceTracker::track($arr1, 'arr1');
ReferenceTracker::track($arr2, 'arr2');
例3: パフォーマンスデバッガー
class PerformanceDebugger {
private static $snapshots = [];
public static function snapshot($variables, $label = null) {
$label = $label ?? 'Snapshot ' . (count(self::$snapshots) + 1);
$snapshot = [
'label' => $label,
'timestamp' => microtime(true),
'memory' => memory_get_usage(),
'variables' => []
];
foreach ($variables as $name => $var) {
$info = runkit7_zval_inspect($var);
$snapshot['variables'][$name] = [
'type' => gettype($var),
'refcount' => $info['refcount'],
'is_ref' => $info['is_ref'],
'size' => self::estimateSize($var)
];
}
self::$snapshots[] = $snapshot;
echo "スナップショット取得: {$label}\n";
echo " メモリ使用量: " . number_format($snapshot['memory']) . " bytes\n";
echo " 変数数: " . count($variables) . "\n\n";
}
private static function estimateSize($var) {
if (is_string($var)) {
return strlen($var);
} elseif (is_array($var)) {
return count($var);
} elseif (is_object($var)) {
return count(get_object_vars($var));
}
return 0;
}
public static function compare($index1, $index2) {
if (!isset(self::$snapshots[$index1]) || !isset(self::$snapshots[$index2])) {
echo "エラー: 無効なスナップショットインデックス\n";
return;
}
$snap1 = self::$snapshots[$index1];
$snap2 = self::$snapshots[$index2];
echo "=== スナップショット比較 ===\n";
echo "{$snap1['label']} vs {$snap2['label']}\n\n";
$memoryDiff = $snap2['memory'] - $snap1['memory'];
echo "メモリ使用量の差: " . number_format($memoryDiff) . " bytes\n";
$timeDiff = ($snap2['timestamp'] - $snap1['timestamp']) * 1000;
echo "経過時間: " . number_format($timeDiff, 2) . " ms\n\n";
echo "変数の変化:\n";
// 両方に存在する変数を比較
foreach ($snap1['variables'] as $name => $info1) {
if (isset($snap2['variables'][$name])) {
$info2 = $snap2['variables'][$name];
if ($info1['refcount'] !== $info2['refcount'] ||
$info1['size'] !== $info2['size']) {
echo " {$name}:\n";
echo " 参照カウント: {$info1['refcount']} → {$info2['refcount']}\n";
echo " サイズ: {$info1['size']} → {$info2['size']}\n";
}
}
}
echo "\n";
}
public static function report() {
echo "=== パフォーマンスレポート ===\n";
echo "スナップショット数: " . count(self::$snapshots) . "\n\n";
foreach (self::$snapshots as $i => $snapshot) {
echo "[{$i}] {$snapshot['label']}\n";
echo " 時刻: " . date('H:i:s', (int)$snapshot['timestamp']) . "\n";
echo " メモリ: " . number_format($snapshot['memory']) . " bytes\n";
echo " 変数数: " . count($snapshot['variables']) . "\n\n";
}
}
}
// 使用例
$data = [];
PerformanceDebugger::snapshot(['data' => $data], '初期状態');
// データを追加
for ($i = 0; $i < 1000; $i++) {
$data[] = "Item {$i}";
}
PerformanceDebugger::snapshot(['data' => $data], '1000件追加後');
// さらにデータを追加
for ($i = 1000; $i < 10000; $i++) {
$data[] = "Item {$i}";
}
PerformanceDebugger::snapshot(['data' => $data], '10000件追加後');
// 比較
PerformanceDebugger::compare(0, 1);
PerformanceDebugger::compare(1, 2);
// レポート
PerformanceDebugger::report();
例4: Copy-on-Write動作の可視化
class CopyOnWriteVisualizer {
public static function demonstrate() {
echo "=== Copy-on-Write (COW) の動作可視化 ===\n\n";
// ステップ1: 配列を作成
echo "【ステップ1】配列を作成\n";
$original = range(1, 5);
self::showZvalInfo($original, 'original');
// ステップ2: 別の変数に代入
echo "【ステップ2】別の変数に代入(コピー)\n";
$copy = $original;
echo "代入後:\n";
self::showZvalInfo($original, 'original');
self::showZvalInfo($copy, 'copy');
echo "→ まだコピーは発生していない(同じデータを共有)\n\n";
// ステップ3: コピーを変更
echo "【ステップ3】copyを変更\n";
$copy[] = 6;
echo "変更後:\n";
self::showZvalInfo($original, 'original');
self::showZvalInfo($copy, 'copy');
echo "→ Copy-on-Writeが発生!データが分離された\n\n";
// ステップ4: 参照渡し
echo "【ステップ4】参照渡しの場合\n";
$ref = &$original;
echo "参照代入後:\n";
self::showZvalInfo($original, 'original');
self::showZvalInfo($ref, 'ref');
echo "→ 参照なのでCOWは発生しない\n\n";
// ステップ5: 文字列の場合
echo "【ステップ5】文字列のCOW\n";
$str1 = "Hello, World!";
self::showZvalInfo($str1, 'str1');
$str2 = $str1;
echo "代入後:\n";
self::showZvalInfo($str1, 'str1');
self::showZvalInfo($str2, 'str2');
$str2 .= " PHP";
echo "str2を変更後:\n";
self::showZvalInfo($str1, 'str1');
self::showZvalInfo($str2, 'str2');
echo "\n";
}
private static function showZvalInfo($var, $label) {
$info = runkit7_zval_inspect($var);
echo " {$label}: ";
echo "refcount={$info['refcount']}, ";
echo "is_ref=" . ($info['is_ref'] ? 'true' : 'false');
if (is_array($var)) {
echo ", elements=" . count($var);
} elseif (is_string($var)) {
echo ", length=" . strlen($var);
}
echo "\n";
}
}
// 実行
CopyOnWriteVisualizer::demonstrate();
例5: メモリリーク検出ツール
class MemoryLeakDetector {
private static $baseline = [];
public static function setBaseline($variables) {
echo "ベースライン設定中...\n";
self::$baseline = [];
foreach ($variables as $name => $var) {
$info = runkit7_zval_inspect($var);
self::$baseline[$name] = [
'refcount' => $info['refcount'],
'is_ref' => $info['is_ref'],
'type' => gettype($var)
];
}
echo "ベースライン設定完了(" . count(self::$baseline) . "個の変数)\n\n";
}
public static function check($variables) {
echo "=== メモリリークチェック ===\n";
$leaks = [];
foreach ($variables as $name => $var) {
if (!isset(self::$baseline[$name])) {
continue;
}
$info = runkit7_zval_inspect($var);
$baseline = self::$baseline[$name];
// 参照カウントが増加している場合
$refcountIncrease = $info['refcount'] - $baseline['refcount'];
if ($refcountIncrease > 0) {
$leaks[$name] = [
'refcount_increase' => $refcountIncrease,
'current_refcount' => $info['refcount'],
'baseline_refcount' => $baseline['refcount']
];
}
}
if (empty($leaks)) {
echo "✓ メモリリークは検出されませんでした\n";
} else {
echo "⚠ 潜在的なメモリリークを検出:\n";
foreach ($leaks as $name => $info) {
echo " {$name}:\n";
echo " 参照カウント増加: +{$info['refcount_increase']}\n";
echo " 現在: {$info['current_refcount']}, ";
echo "ベースライン: {$info['baseline_refcount']}\n";
}
}
echo "\n";
}
}
// 使用例
class DataContainer {
private $data = [];
public function addData($value) {
$this->data[] = $value;
}
public function getData() {
return $this->data;
}
}
$container = new DataContainer();
$globalData = [];
// ベースライン設定
MemoryLeakDetector::setBaseline([
'container' => $container,
'globalData' => $globalData
]);
// 通常の操作
$container->addData('item1');
$container->addData('item2');
$globalData[] = 'test';
MemoryLeakDetector::check([
'container' => $container,
'globalData' => $globalData
]);
// 参照を作成(リークのシミュレート)
$leak1 = &$globalData;
$leak2 = &$globalData;
MemoryLeakDetector::check([
'container' => $container,
'globalData' => $globalData
]);
例6: 変数ダンプツール
class AdvancedVarDump {
public static function dump($variable, $label = 'Variable', $maxDepth = 3, $currentDepth = 0) {
$indent = str_repeat(' ', $currentDepth);
echo "{$indent}{$label}:\n";
$info = runkit7_zval_inspect($variable);
// 基本情報
echo "{$indent} 型: " . self::getTypeName($variable) . "\n";
echo "{$indent} 参照カウント: {$info['refcount']}\n";
echo "{$indent} 参照: " . ($info['is_ref'] ? 'YES' : 'NO') . "\n";
// 値の詳細
if (is_null($variable)) {
echo "{$indent} 値: NULL\n";
} elseif (is_bool($variable)) {
echo "{$indent} 値: " . ($variable ? 'TRUE' : 'FALSE') . "\n";
} elseif (is_int($variable) || is_float($variable)) {
echo "{$indent} 値: {$variable}\n";
} elseif (is_string($variable)) {
$len = strlen($variable);
$preview = $len > 50 ? substr($variable, 0, 50) . '...' : $variable;
echo "{$indent} 長さ: {$len}\n";
echo "{$indent} 値: \"{$preview}\"\n";
} elseif (is_array($variable)) {
echo "{$indent} 要素数: " . count($variable) . "\n";
if ($currentDepth < $maxDepth && !empty($variable)) {
echo "{$indent} 要素:\n";
$count = 0;
foreach ($variable as $key => $value) {
if ($count >= 5) {
echo "{$indent} ... (残り " . (count($variable) - 5) . " 要素)\n";
break;
}
self::dump($value, "[{$key}]", $maxDepth, $currentDepth + 2);
$count++;
}
}
} elseif (is_object($variable)) {
echo "{$indent} クラス: " . get_class($variable) . "\n";
$props = get_object_vars($variable);
echo "{$indent} プロパティ数: " . count($props) . "\n";
if ($currentDepth < $maxDepth && !empty($props)) {
echo "{$indent} プロパティ:\n";
foreach ($props as $propName => $propValue) {
self::dump($propValue, "\${$propName}", $maxDepth, $currentDepth + 2);
}
}
} elseif (is_resource($variable)) {
echo "{$indent} リソース型: " . get_resource_type($variable) . "\n";
}
echo "\n";
}
private static function getTypeName($var) {
$type = gettype($var);
if ($type === 'integer') {
return 'integer (int)';
} elseif ($type === 'double') {
return 'double (float)';
} elseif ($type === 'boolean') {
return 'boolean (bool)';
} elseif ($type === 'object') {
return 'object (' . get_class($var) . ')';
}
return $type;
}
}
// 使用例
$complexData = [
'user' => [
'id' => 123,
'name' => '田中太郎',
'email' => 'tanaka@example.com',
'roles' => ['admin', 'editor']
],
'settings' => [
'theme' => 'dark',
'language' => 'ja'
],
'metadata' => new stdClass()
];
$complexData['metadata']->created_at = '2024-01-01';
$complexData['metadata']->updated_at = '2024-02-01';
echo "=== 複雑なデータ構造のダンプ ===\n\n";
AdvancedVarDump::dump($complexData, 'complexData');
例7: 循環参照の検出
class CircularReferenceDetector {
public static function detect($variable, $label = 'Root') {
echo "=== 循環参照検出: {$label} ===\n";
$visited = [];
$circular = [];
self::traverse($variable, $label, $visited, $circular);
if (empty($circular)) {
echo "✓ 循環参照は見つかりませんでした\n";
} else {
echo "⚠ 循環参照を検出:\n";
foreach ($circular as $path) {
echo " {$path}\n";
}
}
echo "\n";
}
private static function traverse($var, $path, &$visited, &$circular, $depth = 0) {
if ($depth > 10) {
return; // 深さ制限
}
if (is_array($var)) {
$hash = spl_object_hash((object)$var);
if (isset($visited[$hash])) {
$circular[] = "{$path} → {$visited[$hash]} (循環)";
return;
}
$visited[$hash] = $path;
foreach ($var as $key => $value) {
self::traverse($value, "{$path}[{$key}]", $visited, $circular, $depth + 1);
}
} elseif (is_object($var)) {
$hash = spl_object_hash($var);
if (isset($visited[$hash])) {
$circular[] = "{$path} → {$visited[$hash]} (循環)";
return;
}
$visited[$hash] = $path;
foreach (get_object_vars($var) as $propName => $propValue) {
self::traverse($propValue, "{$path}->{$propName}", $visited, $circular, $depth + 1);
}
}
}
}
// 使用例:循環参照の作成と検出
$obj1 = new stdClass();
$obj1->name = 'Object 1';
$obj2 = new stdClass();
$obj2->name = 'Object 2';
// 循環参照を作成
$obj1->ref = $obj2;
$obj2->ref = $obj1;
CircularReferenceDetector::detect($obj1, 'obj1');
// 配列での循環参照
$arr = ['name' => 'Array'];
$arr['self'] = &$arr;
CircularReferenceDetector::detect($arr, 'arr');
重要な注意点と制限事項
1. 出力フォーマットはバージョンによって異なる可能性
// 出力される情報はPHPバージョンやrunkit7のバージョンによって異なる場合があります
$var = "test";
$info = runkit7_zval_inspect($var);
// 安全に情報を取得
$refcount = $info['refcount'] ?? 'N/A';
$isRef = isset($info['is_ref']) ? ($info['is_ref'] ? 'true' : 'false') : 'N/A';
echo "参照カウント: {$refcount}\n";
echo "参照変数: {$isRef}\n";
2. デバッグ専用
// 本番環境では使用しないこと
if (getenv('APP_ENV') === 'production') {
// runkit7_zval_inspect()は使用しない
echo "本番環境ではzval inspectionは無効です\n";
} else {
// 開発環境でのみ使用
$var = [1, 2, 3];
print_r(runkit7_zval_inspect($var));
}
3. パフォーマンスへの影響
// runkit7_zval_inspect()は比較的重い処理
// 大量に呼び出すとパフォーマンスに影響する
$iterations = 10000;
// 通常の処理
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$var = "test";
$type = gettype($var); // 通常の型チェック
}
$time1 = microtime(true) - $start;
// zval inspection
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$var = "test";
$info = runkit7_zval_inspect($var); // zval inspection
}
$time2 = microtime(true) - $start;
echo "通常の型チェック: {$time1}秒\n";
echo "zval inspection: {$time2}秒\n";
echo "差: " . ($time2 - $time1) . "秒\n";
より安全な代替手段
実際の開発では、以下の方法を検討することをお勧めします:
1. var_dump()とget_debug_type()
$variable = [1, 2, 3];
// 標準的なデバッグ方法
var_dump($variable);
// PHP 8.0以降
echo "型: " . get_debug_type($variable) . "\n";
2. memory_get_usage()
// メモリ使用量の確認
$before = memory_get_usage();
$largeArray = range(1, 100000);
$after = memory_get_usage();
echo "メモリ増加: " . number_format($after - $before) . " bytes\n";
3. Xdebugなどのプロファイラ
// Xdebugを使用したプロファイリング
// php.iniでxdebug.mode=profileを設定
// より詳細な情報を取得可能
まとめ
runkit7_zval_inspect()関数の特徴をまとめると:
できること:
- PHPの変数の内部構造(zval)を調査
- 参照カウントの確認
- 参照変数かどうかの判定
- メモリ使用量の分析
- Copy-on-Write動作の可視化
- メモリリークの検出支援
注意点:
- runkit7拡張機能のインストールが必要
- 開発・デバッグ環境でのみ使用すべき
- 本番環境では使用しない
- パフォーマンスへの影響がある
- 出力フォーマットはバージョンに依存
推奨される使用場面:
- メモリ使用量の最適化
- 参照の動作確認
- Copy-on-Writeの理解
- メモリリークのデバッグ
- PHPの内部動作の学習
より良い代替手段:
- var_dump()やprint_r()
- get_debug_type()(PHP 8.0+)
- memory_get_usage()
- Xdebugなどのプロファイラ
runkit7_zval_inspect()は非常に強力なデバッグツールですが、一般的なアプリケーション開発では標準的なデバッグ関数で十分なケースがほとんどです。PHPの内部動作を深く理解したい場合や、特殊なメモリ問題をデバッグする場合にのみ使用することをお勧めします!
これでrunkit7拡張機能の主要な関数の解説シリーズは完了です。お疲れ様でした!
