セル座標による値の割り当て
スプレッドシート操作において、特定のセルにデータを写入することは基本的な作業です。ワークシートオブジェクトに対して座標を指定し、値を設定するメソッドを用意しています。
// 文字列データを C1 セルに設定
$excelObj->getActiveSheet()->setCellValue('C1', 'Sample Text');
// 数値データを C2 セルに設定
$excelObj->getActiveSheet()->setCellValue('C2', 98765.4321);
// 論理値を C3 セルに設定
$excelObj->getActiveSheet()->setCellValue('C3', FALSE);
// 数式を C4 セルに設定
$excelObj->getActiveSheet()->setCellValue(
'C4',
'=IF(C3, CONCATENATE(C1, " - ", C2), CONCATENATE(C2, " - ", C1))'
);
alternatively、セルオブジェクトを取得してから値を設定することも可能です。
$excelObj->getActiveSheet()
->getCell('D5')
->setValue('Direct Value Set');
セルオブジェクトの生成挙動
存在しない座標に対してgetCell()を呼び出した場合、ライブラリは自動的にそのセルを生成します。もし未存在のセルに対して null を返してほしい場合は、第二引数に false を渡すことで制御できます。
メモリ管理と参照の注意点
PhpSpreadsheet はメモリ効率化のため、セルデータをコレクション内で管理し、必要に応じてキャッシュする仕組みを採用しています。getCell() はセルデータへのポインタを返しますが、別のセルにアクセスすると、之前に取得した変数が保持する参照が無効になる可能性があります。
以下の例では、C1 を取得後に A1 にアクセスすると、C1 の参照リンクが切断される現象を示しています。
$book = new Spreadsheet();
$sheet = $book->getActiveSheet();
$sheet->fromArray(
[10, 20, 30],
null,
'A1'
);
$refC1 = $sheet->getCell('C1');
echo 'Val: ' . $refC1->getValue() . '; Coord: ' . $refC1->getCoordinate() . PHP_EOL;
$refA1 = $sheet->getCell('A1');
echo 'Val: ' . $refA1->getValue() . '; Coord: ' . $refA1->getCoordinate() . PHP_EOL;
// この時点で $refC1 の座標取得はエラーを引き起こす可能性があります
echo 'Val: ' . $refC1->getValue() . '; Coord: ' . $refC1->getCoordinate() . PHP_EOL;
Excel が扱うデータ型
Excel では主に 7 種類のデータ型がサポートされています。
- 文字列
- 数値
- 真偽値
- NULL
- 数式
- エラー値
- インライン文字列
通常、setCellValue() やsetValue() を使用すると、PHP の変数型に基づいて自動的に適切な Excel データ型に変換されます。数値的な文字列は数値へ、先頭が「=」の文字列は数式として解釈されます。
値バインダーの仕組み
この自動変換処理は「値バインダー」によって制御されます。標準のバインダーに加え、より高度な変換を行う「AdvancedValueBinder」を利用することで、以下のような複雑なパターンも自動処理できます。
- 「TRUE」/「FALSE」の文字列を真偽値へ変換
- 科学的記法(指数表記)の数値文字列を数値へ変換
- 分数表記を数値および分数フォーマットへ変換
- パーセント表記を数値化し、パーセント書式を適用
- 日付文字列を Excel のシリアル値へ変換し、日付書式を適用
- 改行を含む文字列に対して折り返し設定を適用
セルへの数式設定
先頭文字が「=」である場合、ライブラリはそれを数式として認識します。計算結果を得るにはgetCalculatedValue() を使用します。一方で、文字列として「=」を保存したい場合は、引用符プレフィックスを設定してエスケープする必要があります。
$excelObj->getActiveSheet()->setCellValue(
'E1',
'=SUM(A1:A5)'
);
// 数式としてではなく文字列として扱わせる
$excelObj->getActiveSheet()->getCell('E1')
->getStyle()->setQuotePrefix(true);
日付および時刻の扱い
Excel 内部では日付も数値(シリアル値)として扱われます。PHP のタイムスタンプから変換し、適切な表示書式を設定する必要があります。
$timestamp = time();
$excelSerial = \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($timestamp);
$excelObj->getActiveSheet()->setCellValue('E2', $excelSerial);
$excelObj->getActiveSheet()->getStyle('E2')
->getNumberFormat()
->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME);
先行ゼロを含む数値
電話番号など先行ゼロが必要な数値は、そのまま設定するとゼロが削除されてしまいます。これを防ぐには、型を明示的に文字列にするか、表示書式でゼロを補完します。
// 方法 1: 型を文字列として明示
$excelObj->getActiveSheet()->setCellValueExplicit(
'E3',
"09012345678",
\PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING
);
// 方法 2: 表示書式でゼロを補完
$excelObj->getActiveSheet()->setCellValue('E4', 9012345678);
$excelObj->getActiveSheet()->getStyle('E4')
->getNumberFormat()
->setFormatCode('00000000000');
配列を用いた範囲データの設定
fromArray() メソッドを使用すると、配列データを一度にセル範囲へ写入できます。2 次元配列は行と列として解釈され、1 次元配列は単一行として扱われます。
$dataSet = [
['Item', 'Jan', 'Feb', 'Mar'],
['Apple', 100, 150, 200],
['Orange', 50, 70, 90],
['Grape', 30, 40, 60],
];
$excelObj->getActiveSheet()->fromArray(
$dataSet,
NULL,
'A1'
);
縦方向にデータを書き込みたい場合は、配列を加工して 2 次元構造にする必要があります。
$list = ['A', 'B', 'C', 'D'];
$verticalData = array_chunk($list, 1);
$excelObj->getActiveSheet()->fromArray($verticalData, NULL, 'A1');
セル値の取得方法
セルの値を取り出すには、まずgetCell() で対象を取得し、その後値を取得するメソッドを呼び出します。
// 生データの取得
$val = $excelObj->getActiveSheet()->getCell('A1')->getValue();
// 数式の計算結果を取得
$calcVal = $excelObj->getActiveSheet()->getCell('B1')->getCalculatedValue();
// 書式適用後の表示値を取得
$fmtVal = $excelObj->getActiveSheet()->getCell('C1')->getFormattedValue();
行列インデックスによる操作
座標文字列(A1 など)ではなく、数値のインデックスを使用してセルにアクセスすることも可能です。列インデックスは 1 から始まります(1=A, 2=B)。
// 列 2、行 5(B5)に値を設定
$excelObj->getActiveSheet()->setCellValueByColumnAndRow(2, 5, 'Indexed Value');
// 値の取得
$val = $excelObj->getActiveSheet()->getCellByColumnAndRow(2, 5)->getValue();
範囲データの配列への展開
特定の範囲またはシート全体のデータを配列として取得するには、toArray() やrangeToArray() が便利です。
$result = $excelObj->getActiveSheet()->rangeToArray(
'A1:C5',
NULL,
TRUE,
TRUE,
TRUE
);
セルの走査と繰り返し処理
イテレーターによる走査
ワークシート内の全セルを巡回するには、行イテレーターとセルイテレーターを組み合わせて使用します。値が設定されていないセルも含めて処理する場合は、フラグを設定します。
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
$book = $reader->load("data.xlsx");
$sheet = $book->getActiveSheet();
echo '<table>';
foreach ($sheet->getRowIterator() as $row) {
echo '<tr>';
$cellIter = $row->getCellIterator();
$cellIter->setIterateOnlyExistingCells(FALSE);
foreach ($cellIter as $cell) {
echo '<td>' . $cell->getValue() . '</td>';
}
echo '</tr>';
}
echo '</table>';
インデックスによるループ
最大行と最大列を取得し、数値ループでセルをアクセスする方法もあります。
$maxRow = $sheet->getHighestRow();
$maxCol = $sheet->getHighestColumn();
$maxColIdx = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($maxCol);
echo '<table>';
for ($r = 1; $r <= $maxRow; ++$r) {
echo '<tr>';
for ($c = 1; $c <= $maxColIdx; ++$c) {
$v = $sheet->getCellByColumnAndRow($c, $r)->getValue();
echo '<td>' . $v . '</td>';
}
echo '</tr>';
}
echo '</table>';
カスタム値バインダーの作成
標準の挙動を変更したい場合、IValueBinder インターフェースを実装するか、既存のバインダークラスを拡張することで独自の値バインダーを作成できます。これにより、データ入力時の自動変換ロジックを自由に定義することが可能です。
\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder(
new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder()
);
$book = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
// 以降、設定されたバインダーが適用されます
$book->getActiveSheet()->setCellValue('A1', '50%');