PHP開発者必見!PHPSpreadsheetでExcel処理を効率化


PHP開発者必見!PHPSpreadsheetでExcel処理を効率化

業務システム開発において、Excelファイルとの連携は避けて通れない重要な要件の一つです。データのインポート/エクスポート、帳票出力、既存データの編集など、さまざまな場面でExcel処理が必要となります。しかし、PHPでExcelファイルを扱うのは、適切なライブラリを使わないと非常に困難で非効率になりがちです。

この記事では、PHPでExcel処理を行うためのデファクトスタンダードとも言えるライブラリ「PHPSpreadsheet」に焦点を当て、その導入方法から基本的な使い方、応用的なテクニック、パフォーマンスの最適化、さらにはよくある問題とその解決策まで、詳細かつ網羅的に解説します。この記事を読むことで、PHPSpreadsheetを使いこなし、あなたのExcel処理を劇的に効率化できるようになるはずです。

1. はじめに:なぜPHPでExcelを扱うのか、そしてPHPSpreadsheetの必要性

1.1 業務システムとExcel

多くのビジネスシーンでは、未だにExcelがデータの集計、分析、共有、入力フォームとして広く利用されています。そのため、開発するシステムが外部データを取り込んだり、集計結果をユーザーに提供したりする際に、Excel形式での入出力が求められることが頻繁にあります。

  • データのインポート: 外部システムやユーザーが作成したExcelファイルからデータを読み込み、システムに取り込む。
  • データのエクスポート: システム内のデータを集計・加工し、Excelファイルとして出力する。
  • 帳票出力: システム内の情報(請求書、レポートなど)をExcel形式でレイアウトして出力する。
  • 既存ファイルの編集: テンプレートとなるExcelファイルを読み込み、特定箇所にデータを追記・更新して出力する。

1.2 従来のPHPでのExcel処理の課題

かつて、PHPでExcelファイルを扱うのは容易ではありませんでした。

  • CSV形式との比較: CSVはシンプルなテキスト形式であり、PHP標準の関数(fgetcsv, fputcsvなど)で比較的容易に扱えます。しかし、CSVは書式設定、複数シート、数式、画像など、Excel特有の豊富な機能を表現できません。
  • 生ファイルの操作: Excelファイル形式(XLSXは実質的にXMLとその他ファイルをZIPで圧縮したもの)は複雑な構造をしており、PHPのファイル操作関数だけで正しく読み書きするのは現実的ではありません。
  • 古いライブラリ: 過去にはCOMオブジェクトを使う方法や、特定のExcelファイル形式(特に古いBIFF形式)を解析するライブラリも存在しましたが、環境依存性が高かったり、メンテナンスが停止していたりするなどの問題がありました。

これらの課題を解決し、PHPでExcelファイルを安全かつ柔軟に扱うために開発されたのが、PHPSpreadsheetのような専門的なライブラリです。

1.3 PHPSpreadsheetとは?その優位性

PHPSpreadsheetは、Microsoft Excel (.xlsx), LibreOffice/OpenOffice Calc (.ods), Gnumeric (.gnumeric), CSV (.csv) など、様々なスプレッドシートファイル形式の読み込みと書き込みを可能にする強力なPHPライブラリです。元のプロジェクトであるPHPExcelがメンテナンスモードに入った後、その開発チームによってフォークされ、活発な開発が続けられています。

PHPSpreadsheetの主な優位性は以下の通りです。

  • 幅広いファイル形式のサポート: XLSX (Office Open XML) はもちろん、XLS (古いBinary Format)、ODS、CSV、HTML、PDFなど、多様な形式を扱えます。
  • 豊富な機能: セルデータの読み書き、書式設定(フォント、色、罫線、配置、表示形式)、数式、コメント、画像、グラフ、シート操作、保護、フィルタリングなど、Excelの多くの機能をプログラムから制御できます。
  • Composer対応: 現代的なPHP開発の必須ツールであるComposerで簡単にインストール・管理できます。
  • 活発なメンテナンス: 定期的にアップデートが行われており、新しいPHPバージョンやファイル形式の変更にも対応しています。
  • クロスプラットフォーム: サーバーOSに依存せず動作します(必要なPHP拡張機能があれば)。

1.4 この記事で学べること

この記事では、PHPSpreadsheetを使って以下のことができるようになることを目指します。

  • PHPSpreadsheetをComposerでプロジェクトに導入する。
  • 既存のExcelファイルを読み込み、セルデータを取得する。
  • 新しいExcelファイルを作成し、セルにデータを書き込み、書式を設定する。
  • 数式や画像を扱う。
  • 複数シートを操作する。
  • 大規模データ処理時のパフォーマンスを考慮したコーディングを行う。
  • 一般的なエラーに遭遇した際の対処法を理解する。

これらの知識を習得することで、あなたのPHPアプリケーションにおけるExcel連携機能をより効率的かつ堅牢に実装できるようになります。

2. PHPSpreadsheetの導入

PHPSpreadsheetを使うための最初のステップは、プロジェクトへのインストールです。Composerを使えば非常に簡単に行えます。

2.1 前提条件

PHPSpreadsheetを利用するには、以下の環境が必要です。

  • PHPバージョン: PHP 7.1以降が推奨されています。PHP 7.4, 8.0, 8.1, 8.2, 8.3 でテストされています。
  • 必要なPHP拡張モジュール:
    • ext-zip: XLSXやODSファイル(これらは実質的にZIPアーカイブ)を扱うために必須です。
    • ext-xml: XMLファイルの解析に使用されます。
    • ext-gd または ext-imagick: 画像を扱う場合や、HTML/PDF出力時に必要になることがあります。
    • ext-iconv および ext-mbstring: 文字コードの変換やマルチバイト文字列の処理に使用されます。ほぼ必須と考えて良いでしょう。
    • ext-dom: HTMLリーダー/ライターやXML関連の処理で使用されることがあります。
    • ext-simplexml: XML関連の処理で使用されることがあります。
    • ext-filter: 入力値のフィルタリングに使用されます。
    • ext-zlib: 圧縮/解凍に使用されます。zip拡張機能がこれに依存することがあります。

これらの拡張機能が有効になっているか、phpinfo()関数などで確認しておきましょう。もし不足している場合は、PHPの設定ファイル(php.ini)を編集したり、システムパッケージマネージャー(apt, yumなど)でインストールしたりして有効化する必要があります。

2.2 Composerを使ったインストール

プロジェクトのルートディレクトリで、Composerのrequireコマンドを実行します。

bash
composer require phpoffice/phpspreadsheet

このコマンドを実行すると、ComposerはPHPSpreadsheetとその依存ライブラリをvendorディレクトリにダウンロードし、composer.jsonファイルにエントリを追加します。

インストールが完了したら、PHPスクリプトの冒頭でComposerのオートローダーをインクルードすることで、PHPSpreadsheetのクラスを使えるようになります。

“`php
require ‘vendor/autoload.php’;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

// 以降、PHPSpreadsheetのクラスを使って処理を記述
“`

2.3 簡単な動作確認

インストールが成功したか確認するために、簡単なExcelファイル(XLSX形式)を作成して保存するスクリプトを実行してみましょう。

“`php

getActiveSheet();

// セルA1に文字列を書き込み
$sheet->setCellValue(‘A1’, ‘Hello World!’);

// セルB2に数値を書き込み
$sheet->setCellValue(‘B2’, 12345);

// セルC3に数式を書き込み
$sheet->setCellValue(‘C3’, ‘=A1 & ” ” & B2’); // A1とB2を結合する数式

// セルD4に現在の日時を書き込み(日付形式で表示されるよう設定も可能)
$dateTimeNow = new \DateTime();
$sheet->setCellValue(‘D4’, \PhpOffice\PhpSpreadsheet\Shared\Date::dateTimeToExcel($dateTimeNow));
$sheet->getStyle(‘D4’)->getNumberFormat()->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME);

// ファイルを保存するためのWriterオブジェクトを作成
$writer = new Xlsx($spreadsheet);
$fileName = ‘hello_phpspreadsheet.xlsx’;

try {
// ファイルを保存
$writer->save($fileName);
echo “{$fileName} を作成しました。\n”;
} catch (\PhpOffice\PhpSpreadsheet\Writer\Exception $e) {
echo “ファイルの保存中にエラーが発生しました: ” . $e->getMessage() . “\n”;
} catch (\Exception $e) {
echo “予期せぬエラーが発生しました: ” . $e->getMessage() . “\n”;
}

?>

“`

このスクリプトを実行すると、hello_phpspreadsheet.xlsxという名前のExcelファイルが作成されます。このファイルを開いてみて、セルA1に”Hello World!”、B2に12345、C3に”Hello World! 12345″(数式の結果)、D4に現在の日時が表示されていれば、PHPSpreadsheetは正しくインストールされ、動作しています。

3. 基本的な使い方(読み込み編)

既存のExcelファイルからデータを読み込む方法は、ファイルの形式に応じたReaderを選択し、対象のファイルパスを指定することから始まります。

3.1 ファイルの読み込みとSpreadsheetオブジェクトの取得

まず、読み込みたいファイルの形式に対応したReaderクラスのインスタンスを作成します。よく使われるのはXlsxReaderです。

“`php
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;

$inputFileName = ‘data.xlsx’; // 読み込みたいファイルパス

try {
// IOFactoryを使ってReaderを自動判別するか、明示的に指定
// $reader = IOFactory::createReaderForFile($inputFileName); // 自動判別
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); // XLSXを明示的に指定

// ファイルが存在するかチェック(Optionalだが推奨)
if (!$reader->canRead($inputFileName)) {
    throw new \Exception("ファイル '{$inputFileName}' は読み込みできません。");
}

// ファイルを読み込みSpreadsheetオブジェクトを取得
$spreadsheet = $reader->load($inputFileName);

echo "ファイル '{$inputFileName}' を読み込みました。\n";

} catch (ReaderException $e) {
echo “Readerエラー: ” . $e->getMessage() . “\n”;
exit();
} catch (\Exception $e) {
echo “エラー: ” . $e->getMessage() . “\n”;
exit();
}

// 以降、取得した $spreadsheet オブジェクトを使って処理を続ける
“`

IOFactory::createReaderForFile()を使うと、ファイル拡張子や内容から適切なReaderクラスを自動的に選択してくれるため便利です。ただし、大規模なファイルや複数のReaderを使う場合は、明示的にReaderクラスを指定する方が分かりやすいこともあります。

3.2 アクティブなワークシートの取得

Spreadsheetオブジェクトは複数のワークシートを持つことができます。デフォルトでは最初のシートがアクティブになっていますが、特定名のシートや特定のインデックスのシートを取得することも可能です。

“`php
// アクティブなワークシートを取得 (通常は一番左のシート)
$sheet = $spreadsheet->getActiveSheet();
echo “アクティブシート名: ” . $sheet->getTitle() . “\n”;

// 特定のインデックスのシートを取得 (0から始まる)
// 例: 2番目のシートを取得
try {
$secondSheet = $spreadsheet->getSheet(1); // インデックス1は2番目のシート
echo “2番目のシート名: ” . $secondSheet->getTitle() . “\n”;
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
echo “指定されたインデックスのシートは存在しません: ” . $e->getMessage() . “\n”;
}

// 特定の名前のシートを取得
$sheetName = ‘SalesData’;
try {
$salesSheet = $spreadsheet->getSheetByName($sheetName);
echo “シート名 ‘{$sheetName}’ を取得しました。\n”;
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
echo “シート名 ‘{$sheetName}’ は存在しません: ” . $e->getMessage() . “\n”;
}
“`

以降のセル操作は、取得した$sheet(または$secondSheet, $salesSheetなど)オブジェクトに対して行います。

3.3 特定のセルからのデータ取得

特定のセル(例: ‘A1’, ‘B5’)のデータを取得するには、getCell()メソッドでセルオブジェクトを取得し、getValue()メソッドでそのセルの値を取得します。

“`php
// セルB3の値を取得
$cellB3 = $sheet->getCell(‘B3’);
$valueB3 = $cellB3->getValue();
echo “セル B3 の値: ” . $valueB3 . “\n”;

// セルA1の値を取得 (メソッドチェーンも可能)
$valueA1 = $sheet->getCell(‘A1’)->getValue();
echo “セル A1 の値: ” . $valueA1 . “\n”;

// 存在しないセルの取得を試みると例外が発生する場合があるため、存在チェックも有効
if ($sheet->getCell(‘Z99’)->isMergeCell()) { // isMergeCell() など、何らかのメソッドを呼ぶ前にチェック
// セルは存在しないか、データがない
echo “セル Z99 は存在しないか、データがありません。\n”;
} else {
// セル Z99 の値を取得
$valueZ99 = $sheet->getCell(‘Z99’)->getValue();
echo “セル Z99 の値: ” . $valueZ99 . “\n”; // 値が空文字やnullになる可能性あり
}
“`

getValue()はセルの生の値(数式そのものやシリアル値など)を返します。表示されている書式化された値を取得したい場合は、getFormattedValue()を使います。

php
// 数式の結果や書式化された値を取得
$valueFormattedC3 = $sheet->getCell('C3')->getFormattedValue();
echo "セル C3 の書式化された値: " . $valueFormattedC3 . "\n"; // 例: 'Hello World! 12345'

3.4 範囲指定でのデータ取得

特定の範囲(例: ‘A1:D10’)のデータをまとめて配列として取得したい場合は、rangeToArray()メソッドが便利です。

“`php
// 範囲 A1からD10までを配列として取得
// $rangeData = $sheet->rangeToArray(
// ‘A1:D10’, // 範囲
// NULL, // 空白セルのデフォルト値
// TRUE, // 数式を計算するかどうか (TRUEで計算結果)
// TRUE, // 書式設定された値を取得するかどうか (TRUEでgetFormattedValue)
// TRUE // 最初の行をキーとして使用するかどうか (TRUEで1始まりの行番号)
// );

// 範囲 A1からD10までを配列として取得(一般的な使い方)
// 数式は結果を、空白はNULL、最初の行は行番号(1始まり)
$rangeData = $sheet->rangeToArray(
‘A1:D10’,
NULL,
TRUE,
FALSE, // 通常は生の値を取得し、必要に応じて自分で書式設定を適用
TRUE
);

echo “範囲 A1:D10 のデータ:\n”;
print_r($rangeData);

/
出力例(データによる):
Array
(
[1] => Array
(
[0] => Header Col A
[1] => Header Col B
[2] => Header Col C
[3] => Header Col D
)
[2] => Array
(
[0] => Data A2
[1] => Data B2
[2] => Data C2
[3] => Data D2
)

)
/

// 最初の行をヘッダーとして使い、それ以降をデータとして取得する場合
// rangeToArrayの第5引数をTRUEにすると、結果配列のキーが行番号(1始まり)になります。
// 第5引数をFALSEにすると、結果配列のキーは0から始まるインデックスになります。
// ヘッダー行を除外したい場合は、範囲を ‘A2:D10’ のように開始行をずらします。
$dataWithoutHeader = $sheet->rangeToArray(
‘A2:D10’,
NULL,
TRUE,
FALSE,
TRUE // ヘッダー行を除外したが、行番号をキーにしたい場合
);

echo “\n範囲 A2:D10 のデータ:\n”;
print_r($dataWithoutHeader);
“`

rangeToArray()は非常に便利ですが、メモリを大量に消費する可能性があるため、非常に大きな範囲を指定する場合は注意が必要です(パフォーマンスの考慮セクションで詳しく説明します)。

3.5 ループを使った全データの読み込み

ファイル全体や特定のシート全体を読み込む場合は、ループ処理が一般的です。getHighestRow()getHighestColumn()メソッドを使うと、データが入力されている最後の行と列を取得できます。

“`php
// データの存在する最終行と最終列を取得
$highestRow = $sheet->getHighestRow(); // 例: 10
$highestColumn = $sheet->getHighestColumn(); // 例: ‘D’

// 最終列のインデックスを取得 (例: ‘D’ -> 3)
$highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn);

echo “最終行: {$highestRow}, 最終列: {$highestColumn} (インデックス: {$highestColumnIndex})\n”;

$data = [];
// 1行目から最終行までループ (通常、ヘッダー行はスキップして2行目から始めることが多い)
for ($row = 1; $row <= $highestRow; ++$row) {
$rowData = [];
// A列から最終列までループ
for ($col = 1; $col <= $highestColumnIndex; ++$col) {
// セル座標を取得 (例: ‘A1’, ‘B1’, …)
$cellCoordinate = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col) . $row;

    // セルオブジェクトを取得
    $cell = $sheet->getCell($cellCoordinate);

    // セルの値を取得 (書式化された値が必要なら getFormattedValue())
    $value = $cell->getValue(); // あるいは $cell->getFormattedValue();

    $rowData[] = $value;
}
$data[] = $rowData;

}

echo “\n全データ (ループ処理):\n”;
// ヘッダー行を含めて表示
print_r($data);

// ヘッダー行を取得
$headerRow = array_shift($data);
echo “\nヘッダー行:\n”;
print_r($headerRow);

echo “\nデータ行のみ:\n”;
print_r($data);
“`

このループ処理は、rangeToArray()よりもメモリ効率が良い場合があります。特に、各セルに対して個別の処理(例: データ型のチェック、値の変換)を行いたい場合に適しています。

3.6 データ型の自動判別と取得

PHPSpreadsheetはセルのデータ型(数値、文字列、ブール値など)を自動的に判別しようとしますが、Excel上での表示形式と内部的なデータ型は異なる場合があります。getValue()は基本的に内部的な生の値(文字列、数値、ブール値、数式文字列、エラーなど)を返します。

特定のデータ型として確実に取得したい場合は、getValue()の結果をPHP側でキャストするか、getCell()から取得できるCellオブジェクトの各種メソッドを利用します。

“`php
$cell = $sheet->getCell(‘A1’);

$rawValue = $cell->getValue(); // 生の値

// データ型を判別
echo “A1の型: ” . $cell->getDataType() . “\n”; // 例: ‘s’ (string), ‘n’ (numeric), ‘b’ (boolean), ‘f’ (formula), ‘e’ (error), ‘inlineStr’ (inline string)

// 数値として取得
$numericValue = $cell->getCalculatedValue(); // 数式が評価された数値を取得する場合もある
if (is_numeric($rawValue)) {
$number = (float) $rawValue; // または (int)
}

// 文字列として取得
$stringValue = (string) $rawValue;

// ブール値として取得
$booleanValue = (bool) $rawValue;
“`

getCalculatedValue()は数式の結果を評価した値を返しますが、数式が含まれていない場合はgetValue()と同じ値を返すことが多いです。複雑な数式の評価には、追加の設定やライブラリが必要な場合があります。

3.7 日付/時刻データの扱い

Excelでは日付や時刻は「シリアル値」として内部的に保持されています。これは、1900年1月1日を1とする連番です。PHPSpreadsheetのgetValue()rangeToArray(..., FALSE, ...)で取得される日付/時刻データは、このシリアル値となることが多いです。

PHPのDateTimeオブジェクトとして扱いたい場合は、\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject()メソッドを使って変換します。

“`php
// セルD4にExcelの日付シリアル値が入っているとする
$excelDateValue = $sheet->getCell(‘D4’)->getValue();

// シリアル値が有効な数値であるか確認
if (is_numeric($excelDateValue)) {
try {
// Excelシリアル値をPHPのDateTimeオブジェクトに変換
$dateTimeObject = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($excelDateValue);
echo “セル D4 の日時 (DateTimeオブジェクト): ” . $dateTimeObject->format(‘Y-m-d H:i:s’) . “\n”;
} catch (\PhpOffice\PhpSpreadsheet\Calculation\Exception $e) {
echo “日付変換エラー: ” . $e->getMessage() . “\n”;
}
} else {
echo “セル D4 の値は有効なExcelシリアル値ではありません。\n”;
}

// getFormattedValue() を使えば、Excel上で表示されている日付文字列を取得できる
$formattedDateString = $sheet->getCell(‘D4’)->getFormattedValue();
echo “セル D4 の日時 (書式化された文字列): ” . $formattedDateString . “\n”;
“`

getFormattedValue()で日付文字列を取得することも可能ですが、これはExcel上の表示形式に依存するため、厳密に日付として扱いたい場合はシリアル値を取得し、excelToDateTimeObject()で変換するのがより確実です。

3.8 複数シートの読み込み

Spreadsheetオブジェクトに含まれるすべてのシートを順に処理したい場合は、getSheetCount()getSheet()、またはgetAllSheets()メソッドを使います。

“`php
// シートの総数を取得
$sheetCount = $spreadsheet->getSheetCount();
echo “シート数: {$sheetCount}\n”;

// すべてのシートをループ処理
for ($i = 0; $i < $sheetCount; ++$i) {
$currentSheet = $spreadsheet->getSheet($i);
$sheetName = $currentSheet->getTitle();
echo “\n— シート {$sheetName} ({$i+1}/{$sheetCount}) —\n”;

// このシートからデータを読み込む処理...
$highestRow = $currentSheet->getHighestRow();
$highestColumn = $currentSheet->getHighestColumn();
echo "  最終行: {$highestRow}, 最終列: {$highestColumn}\n";

// 例: 各シートのA1セルの値を取得
$a1Value = $currentSheet->getCell('A1')->getValue();
echo "  セル A1 の値: " . $a1Value . "\n";

// 必要に応じて、このシートの全データをループなどで取得・処理する
// ...

}

// または getAllSheets() を使う
// $allSheets = $spreadsheet->getAllSheets();
// foreach ($allSheets as $sheet) {
// // $sheet オブジェクトを使って処理
// echo “シート名: ” . $sheet->getTitle() . “\n”;
// // …
// }
“`

getAllSheets()を使うと、シートオブジェクトの配列を直接取得でき、foreachループでシンプルに処理できます。

4. 基本的な使い方(書き込み編)

新しいExcelファイルを作成したり、既存ファイルを編集して保存したりする手順を解説します。

4.1 新しいSpreadsheetオブジェクトの作成

新規ファイルを作成する場合、まず空のSpreadsheetオブジェクトを作成します。

“`php
use PhpOffice\PhpSpreadsheet\Spreadsheet;

// 新しいSpreadsheetオブジェクトを作成
$spreadsheet = new Spreadsheet();

// デフォルトでアクティブなワークシートが1つ作成されています
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle(‘My New Sheet’); // シート名を変更
“`

4.2 セルへのデータ書き込み

特定のセルに値を書き込むには、setCellValue()メソッドを使います。

“`php
// セルA1に文字列を書き込み
$sheet->setCellValue(‘A1’, ‘商品リスト’);

// セルA2に数値を書き込み
$sheet->setCellValue(‘A2’, 101);

// セルB2に文字列を書き込み
$sheet->setCellValue(‘B2’, ‘リンゴ’);

// セルC2に数値を書き込み
$sheet->setCellValue(‘C2’, 150); // 単価

// セルA3, B3, C3 にも同様に書き込み
$sheet->setCellValue(‘A3’, 102);
$sheet->setCellValue(‘B3’, ‘バナナ’);
$sheet->setCellValue(‘C3’, 80);

// セルA4に数式を書き込み(合計金額)
$sheet->setCellValue(‘A4’, ‘合計:’);
$sheet->setCellValue(‘B4’, ‘=SUM(C2:C3)’); // C2とC3の合計を計算する数式

// 明示的にデータ型を指定して書き込む場合
// 例: 数値として書き込む (数値文字列の場合など)
$sheet->setCellValueExplicit(‘A5’, ‘007’, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // ‘007’ を文字列として保存
$sheet->setCellValueExplicit(‘B5’, ‘12345’, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC); // ‘12345’ を数値として保存
“`

setCellValue()は値の型を自動判別しようとしますが、意図しない型として保存されることを防ぐために、setCellValueExplicit()でデータ型を明示的に指定することも可能です。特に、数値として解釈されうる文字列(例: 郵便番号、IDなど)を文字列として保存したい場合に役立ちます。

4.3 配列を使った複数セルへの書き込み

fromArray()メソッドを使うと、PHPの多次元配列のデータを指定したセル範囲に一括で書き込むことができます。これは、データベースから取得した結果セットなどをそのままExcelに出力したい場合に非常に便利です。

“`php
$data = [
[‘ヘッダーA’, ‘ヘッダーB’, ‘ヘッダーC’],
[‘データ1A’, 10, TRUE],
[‘データ2A’, 20, FALSE],
[‘データ3A’, 30, ‘=B2+B3’], // 数式もそのまま書き込める
];

// 配列をセルA1から書き込み開始
$sheet->fromArray(
$data, // 書き込むデータ(二次元配列)
NULL, // 空白セルのデフォルト値
‘A1’ // 書き込み開始セル
);

// 別の場所に書き込む例 (D5から開始)
$moreData = [
[‘追加データ1’, 100],
[‘追加データ2’, 200],
];
$sheet->fromArray($moreData, NULL, ‘D5’);
“`

fromArray()を使うと、ループ処理で一つずつセルに書き込むよりもコードがシンプルになり、多くの場合パフォーマンスも向上します。

4.4 セルの書式設定

フォント、色、背景色、配置、罫線、表示形式など、セルの見た目を制御するにはgetStyle()メソッドでスタイルオブジェクトを取得し、そのメソッドを呼び出します。

“`php
// セルA1のスタイルを取得
$styleA1 = $sheet->getStyle(‘A1’);

// フォントを設定
$styleA1->getFont()->setName(‘Arial’);
$styleA1->getFont()->setSize(14);
$styleA1->getFont()->setBold(TRUE);
$styleA1->getFont()->setColor(new \PhpOffice\PhpSpreadsheet\Style\Color(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN)); // フォント色

// セルの背景色を設定
$styleA1->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$styleA1->getFill()->getStartColor()->setARGB(‘FFFFCC00’); // 背景色 (黄色)

// 配置を設定
$styleA1->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER); // 水平中央
$styleA1->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER); // 垂直中央

// 罫線を設定 (例: セルA1の下線)
$styleA1->getBorders()->getBottom()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
$styleA1->getBorders()->getBottom()->setColor(new \PhpOffice\PhpSpreadsheet\Style\Color(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_BLACK));

// 複数セル範囲にまとめてスタイルを適用
$styleRangeB2C3 = $sheet->getStyle(‘B2:C3’);

// 罫線を範囲全体に適用 (内側と外側)
$styleRangeB2C3->getBorders()->getAllBorders()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);

// 数値の表示形式を設定 (例: 通貨形式)
$sheet->getStyle(‘C2:C3’)->getNumberFormat()->setFormatCode(‘#,##0’); // カンマ区切り整数

// 日付の表示形式を設定 (例: YYYY/MM/DD形式)
$sheet->getStyle(‘D4’)->getNumberFormat()->setFormatCode(‘YYYY/MM/DD’);
“`

スタイル設定は非常に多くのオプションがあります。PHPSpreadsheetのスタイル定数(\PhpOffice\PhpSpreadsheet\Style\...::)を参照すると、利用可能な値を確認できます。

4.5 列幅・行高の自動調整/手動設定

データの量に合わせて列幅や行高を調整すると、見やすいExcelファイルになります。

“`php
// セルA1を含む列 (A列) の幅を自動調整
$sheet->getColumnDimension(‘A’)->setAutoSize(TRUE);

// 複数の列の幅を自動調整
foreach (range(‘A’, ‘D’) as $col) {
$sheet->getColumnDimension($col)->setAutoSize(TRUE);
}

// 特定の列の幅を手動で設定 (単位は文字幅)
$sheet->getColumnDimension(‘B’)->setWidth(20); // B列の幅を20文字幅に設定

// 特定の行の高さを手動で設定 (単位はポイント)
$sheet->getRowDimension(1)->setRowHeight(25); // 1行目の高さを25ポイントに設定
“`

setAutoSize(TRUE)は非常に便利ですが、すべての列に適用すると処理に時間がかかる場合があるため注意が必要です。

4.6 コメントの追加

特定のセルにコメントを付加することも可能です。

“`php
use PhpOffice\PhpSpreadsheet\Comment;
use PhpOffice\PhpSpreadsheet\RichText\RichText;

// セルB2にコメントを追加
$comment = new Comment();
$comment->setAuthor(‘PHPSpreadsheet’);

// コメントのテキストを作成
$textRun = new RichText();
$textRun->createText(‘これは’);
$textRun->createTextRun(‘コメントです!’)->getFont()->setBold(TRUE);
$textRun->createText(“\n改行もできます。”);

$comment->setText($textRun);
$comment->setVisible(FALSE); // コメントをデフォルトで非表示にする

$sheet->setComment(‘B2’, $comment);
“`

4.7 画像の埋め込み

Excelファイルに画像を埋め込むことも可能です。GDまたはImagick拡張機能が必要です。

“`php
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;

// 画像ファイルへのパス
$imagePath = ‘path/to/your/image.png’; // 実際の画像ファイルパスに置き換えてください

if (file_exists($imagePath)) {
$drawing = new Drawing();
$drawing->setName(‘Logo’); // 画像の名前
$drawing->setDescription(‘Company Logo’); // 画像の説明
$drawing->setPath($imagePath); // 画像ファイルのパス
$drawing->setHeight(50); // 画像の高さを設定 (幅は自動調整される)
// $drawing->setWidth(100); // 幅も設定可能 (高さは自動調整される)
// $drawing->setResizeProportional(false); // 縦横比を維持しない場合

// 画像を配置するセルの座標 (例: E1)
$drawing->setCoordinates('E1');

// セルE1の左上隅からのオフセット (ピクセル単位)
$drawing->setOffsetX(10);
$drawing->setOffsetY(10);

// 画像をワークシートに追加
$drawing->setWorksheet($sheet);

echo "画像をセル E1 に追加しました。\n";

} else {
echo “画像ファイルが見つかりません: {$imagePath}\n”;
}
“`

4.8 シート操作(追加、コピー、削除、名前変更)

新しいシートを追加したり、既存のシートを操作したりすることも可能です。

“`php
// 新しいワークシートを追加
$newSheet = new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, ‘Summary’);
$spreadsheet->addSheet($newSheet, 0); // 最初のシートとして追加 (インデックス0)

// 追加したシートをアクティブにする
$spreadsheet->setActiveSheetIndexByName(‘Summary’);
$activeSheet = $spreadsheet->getActiveSheet(); // これで Summary シートが取得される

// シート名を変更
$activeSheet->setTitle(‘集計データ’);

// 既存シートをコピー
try {
$clonedSheet = clone $spreadsheet->getSheetByName(‘My New Sheet’); // 元のシート名を指定
$clonedSheet->setTitle(‘My New Sheet Copy’);
$spreadsheet->addSheet($clonedSheet); // 末尾に追加
echo “シート ‘My New Sheet’ をコピーして ‘My New Sheet Copy’ を作成しました。\n”;
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
echo “シートのコピーに失敗しました: ” . $e->getMessage() . “\n”;
}

// 特定のシートを削除
try {
$sheetIndexToDelete = $spreadsheet->getIndex($spreadsheet->getSheetByName(‘Sheet1’)); // デフォルトで作成されるSheet1を削除する場合
$spreadsheet->removeSheetByIndex($sheetIndexToDelete);
echo “シート ‘Sheet1’ を削除しました。\n”;
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
echo “シートの削除に失敗しました: ” . $e->getMessage() . “\n”;
}
“`

シートの操作は、Spreadsheetオブジェクトに対して行います。

4.9 ファイルの保存

データを書き込んだSpreadsheetオブジェクトをファイルとして保存するには、形式に応じたWriterクラスのインスタンスを作成し、save()メソッドを呼び出します。

“`php
use PhpOffice\PhpSpreadsheet\Writer\Xlsx; // XLSX形式で保存する場合
use PhpOffice\PhpSpreadsheet\Writer\Csv; // CSV形式で保存する場合
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf; // PDF形式で保存する場合 (別途PDFライブラリが必要)
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;

// 保存ファイルパス
$outputFileName = ‘output_document.xlsx’;

// Writerオブジェクトを作成 (例: XLSX形式)
$writer = new Xlsx($spreadsheet);

try {
// ファイルを保存
$writer->save($outputFileName);
echo “ファイル ‘{$outputFileName}’ を保存しました。\n”;
} catch (WriterException $e) {
echo “Writerエラー: ” . $e->getMessage() . “\n”;
} catch (\Exception $e) {
echo “予期せぬエラー: ” . $e->getMessage() . “\n”;
}

// CSV形式で保存する場合
// $writer = new Csv($spreadsheet);
// $writer->setDelimiter(‘,’); // 区切り文字
// $writer->setEnclosure(‘”‘); // 囲み文字
// $writer->setLineEnding(“\r\n”); // 行末文字
// $writer->setSheetIndex(0); // どのシートを保存するか (CSVは1シートのみ)
// $writer->save(‘output_document.csv’);

// PDF形式で保存する場合 (別途TCPDF, mPDF, DompdfなどのPDFライブラリをComposerでインストールが必要)
// 例: mPDFを使う場合 (composer require mpdf/mpdf)
// \PhpOffice\PhpSpreadsheet\Settings::setPdfRendererName(\PhpOffice\PhpSpreadsheet\Settings::PDF_RENDERER_MPDF);
// \PhpOffice\PhpSpreadsheet\Settings::setPdfRendererPath(‘vendor/mpdf/mpdf’); // mpdfインストールパス
// $writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf($spreadsheet);
// $writer->save(‘output_document.pdf’);
“`

Writerの種類は、Xlsx, Xls, Csv, Ods, Html, Pdf, Baseなどがあります。PDF出力には、PHPSpreadsheetとは別にPDFレンダリングライブラリ(TCPDF, mPDF, Dompdfなど)が必要です。

4.10 ブラウザへの直接ダウンロード

生成したExcelファイルをサーバーに保存せず、ユーザーのブラウザに直接ダウンロードさせることもよくあります。これは、HTTPヘッダーを適切に設定することで実現できます。

“`php
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;

$outputFileName = ‘download_report.xlsx’;

// HTTPヘッダーを設定
header(‘Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’);
header(‘Content-Disposition: attachment;filename=”‘ . $outputFileName . ‘”‘);
header(‘Cache-Control: max-age=0’);
// If you’re serving to IE 9, use:
header(‘Cache-Control: max-age=1’);

// If you’re serving to IE 6, 7 and 8, use:
header(‘Expires: Sat, 01 Jan 2000 00:00:00 GMT’); // Date in the past
header(‘Last-Modified: ‘ . gmdate(‘D, d M Y H:i:s’) . ‘ GMT’); // always modified
header(‘Cache-Control: cache, must-revalidate’); // HTTP/1.1
header(‘Pragma: public’); // HTTP/1.0

// Writerオブジェクトを作成
$writer = new Xlsx($spreadsheet);

try {
// 標準出力に書き出し (ファイルではなくブラウザへ)
$writer->save(‘php://output’);
exit; // 処理終了
} catch (WriterException $e) {
echo “Writerエラー: ” . $e->getMessage() . “\n”;
} catch (\Exception $e) {
echo “予期せぬエラー: ” . $e->getMessage() . “\n”;
}
“`

ブラウザに直接出力する場合、スクリプト内でこれより前に余計な出力(HTMLタグ、スペース、改行など)があると、ファイルが破損する原因となるため注意が必要です。通常、この処理はスクリプトの最後で行い、exitで終了します。

5. 応用的な使い方

PHPSpreadsheetは基本的な読み書きだけでなく、Excelの様々な高度な機能もサポートしています。

5.1 データのフィルタリングとソート

ワークシートにオートフィルターを設定できます。

“`php
// データ範囲 ‘A1:D10’ にオートフィルターを設定
$sheet->setAutoFilter(‘A1:D10’);

// フィルタリング条件を設定する例 (例: B列で ‘リンゴ’ の行のみ表示)
// オートフィルターを設定した後に、フィルタリング条件を適用するには、
// Worksheet\AutoFilter\Column クラスと Filter\Rule クラスを使用します。
// ただし、これはフィルターの設定であり、PHP側でデータを絞り込むものではありません。
// 読み込み時にデータを絞り込む場合は、ループ処理中に条件判定を行う必要があります。
// ここでは、書き込み時にフィルターが設定された状態で保存する例を示します。

// 例として、B列 (インデックス1) にフィルターを設定
$autoFilter = $sheet->getAutoFilter();
$columnFilter = $autoFilter->getColumn(‘B’);

// フィルタータイプをフィルタリングに設定
$columnFilter->setFilterType(\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTER);

// フィルタリング条件を追加 (例: 値が ‘リンゴ’ と等しい)
$columnFilter->createRule()
->setRule(
\PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULE_EQUAL,
‘リンゴ’ // 条件値
);
// 複数の条件を設定することも可能
// $columnFilter->createRule()
// ->setRule(
// \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULE_EQUAL,
// [‘リンゴ’, ‘バナナ’] // 配列で複数指定
// );
“`

ソートも同様に設定できますが、これもExcelファイルにソート設定を保存するものであり、PHPSpreadsheetがデータを実際にソートするわけではありません。PHP側でデータをソートする場合は、読み込んだ配列をusortなどでソートする必要があります。

5.2 条件付き書式設定

特定の条件を満たすセルに自動的に書式を適用する設定も可能です。

“`php
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;

// 範囲 C2:C100 に対して条件付き書式を設定
$conditionalStyles = $sheet->getStyle(‘C2:C100’)->getConditionalStyles();

// 条件1: 値が 100 より大きい場合に背景色を緑にする
$condition1 = new Conditional();
$condition1->setConditionType(Conditional::CONDITION_CELLIS) // セルの値に基づく条件
->setOperatorType(Conditional::OPERATOR_GREATERTHAN) // より大きい
->setText(100); // 条件値(テキストとして指定)
$condition1->getStyle()->getFill()->setFillType(Fill::FILL_SOLID)->getEndColor()->setARGB(‘FFC6EFCE’); // 薄い緑色

// 条件2: 値が 50 より小さい場合に背景色を赤にする
$condition2 = new Conditional();
$condition2->setConditionType(Conditional::CONDITION_CELLIS)
->setOperatorType(Conditional::OPERATOR_LESSTHAN) // より小さい
->setText(50);
$condition2->getStyle()->getFill()->setFillType(Fill::FILL_SOLID)->getEndColor()->setARGB(‘FFFFC7CE’); // 薄い赤色

// 条件をスタイル配列に追加 (条件1, 条件2 の順で評価される)
$conditionalStyles[] = $condition1;
$conditionalStyles[] = $condition2;

// スタイルをセル範囲に設定し直す
$sheet->getStyle(‘C2:C100’)->setConditionalStyles($conditionalStyles);
“`

条件付き書式は、Conditionalオブジェクトを作成し、条件タイプ、演算子、条件値を設定し、適用したいスタイルを指定します。そして、対象セル範囲のスタイルオブジェクトに条件付きスタイルの配列として追加します。

5.3 入力規則(データ検証)

セルの入力値に制限を設定できます。

“`php
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;

// 範囲 B2:B10 に入力規則を設定
$dataValidation = $sheet->getCell(‘B2’)->getDataValidation(); // 最初のセルでDataValidationオブジェクトを取得
// 範囲全体に適用されるため、範囲指定は不要(最初のセルに設定するとその設定が範囲全体にコピーされる)

$dataValidation->setType(DataValidation::TYPE_LIST); // リストからの選択
$dataValidation->setErrorStyle(DataValidation::STYLE_INFORMATION); // 無効な値が入力されたときのエラースタイル
$dataValidation->setAllowBlank(TRUE); // 空白を許可するかどうか
$dataValidation->setShowInputMessage(TRUE); // 入力時にメッセージを表示するか
$dataValidation->setShowErrorMessage(TRUE); // 無効な値入力時にエラーメッセージを表示するか
$dataValidation->setShowDropDown(TRUE); // ドロップダウンリストを表示するか (リストタイプの場合)
$dataValidation->setErrorTitle(‘入力エラー’); // エラーメッセージのタイトル
$dataValidation->setError(‘リストから選択してください。’); // エラーメッセージ本文
$dataValidation->setPromptTitle(‘入力方法’); // 入力時メッセージのタイトル
$dataValidation->setPrompt(‘ここに商品名を入力’); // 入力時メッセージ本文
$dataValidation->setFormula1(‘”リンゴ,バナナ,みかん,ぶどう”‘); // リストの元データ (カンマ区切り文字列)

// 複数のセルに同じ入力規則を適用するには、対象範囲のすべてのセルに同じ設定を行う必要があります。
// 例えば、ループで B2 から B100 までの各セルに対して dataValidation を設定します。
for ($row = 2; $row <= 100; ++$row) {
$dataValidation = $sheet->getCell(‘B’ . $row)->getDataValidation();
$dataValidation->setType(DataValidation::TYPE_LIST);
$dataValidation->setAllowBlank(TRUE);
$dataValidation->setShowDropDown(TRUE);
$dataValidation->setFormula1(‘”リンゴ,バナナ,みかん,ぶどう”‘);
// 他のオプションも同様に設定
$dataValidation->setErrorTitle(‘入力エラー’);
$dataValidation->setError(‘リストから選択してください。’);
}

// 別の入力規則の例: 数値の範囲制限 (C2:C100)
for ($row = 2; $row <= 100; ++$row) {
$dataValidation = $sheet->getCell(‘C’ . $row)->getDataValidation();
$dataValidation->setType(DataValidation::TYPE_WHOLE); // 整数
$dataValidation->setOperator(DataValidation::OPERATOR_BETWEEN); // 範囲内
$dataValidation->setFormula1(1); // 最小値
$dataValidation->setFormula2(999); // 最大値
$dataValidation->setErrorTitle(‘入力エラー’);
$dataValidation->setError(‘単価は1~999の整数で入力してください。’);
$dataValidation->setShowErrorMessage(TRUE);
}
“`

入力規則は、DataValidationオブジェクトを生成し、タイプ(リスト、整数、小数点、日付、時刻、文字列長、カスタム)、演算子、条件値(setFormula1, setFormula2)などを設定します。そして、対象セルのgetDataValidation()で取得したオブジェクトにその設定を適用します。対象範囲が広い場合は、ループで各セルに設定する必要があります。

5.4 テンプレートファイルの利用

既存のExcelファイルをテンプレートとして読み込み、特定箇所にデータを挿入して出力するという方法は、帳票出力などで非常に効率的です。

  1. テンプレートExcelファイルを作成: 予め、ヘッダー、固定テキスト、書式設定などを定義した.xlsxファイルを作成します。データを入れたいセルは空白にしておくか、目印となるようなテキストを入れておきます(例: {{商品名}})。
  2. PHPでテンプレートを読み込む: 通常のファイル読み込みと同じ方法でテンプレートファイルをloadします。
  3. データをセルに書き込む: テンプレートの目印となるセル座標(または、テンプレートで名前を定義したセル)に、プログラムから動的にデータを書き込みます。setCellValuefromArrayが使えます。
  4. ファイルを保存/出力: 編集したSpreadsheetオブジェクトを新しいファイルとして保存したり、ブラウザにダウンロードさせたりします。

“`php
// テンプレートファイルパス
$templatePath = ‘path/to/your/template.xlsx’; // 実際のテンプレートファイルパスに置き換えてください

if (!file_exists($templatePath)) {
die(“エラー: テンプレートファイル ‘{$templatePath}’ が見つかりません。”);
}

try {
// テンプレートファイルを読み込み
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$spreadsheet = $reader->load($templatePath);

// データを書き込むシートを選択
$sheet = $spreadsheet->getSheetByName('Invoice'); // 例: 'Invoice' という名前のシート

if (!$sheet) {
    die("エラー: シート 'Invoice' がテンプレートファイルに見つかりません。");
}

// テンプレートの特定箇所にデータを書き込み
$sheet->setCellValue('B5', '株式会社 〇〇商事'); // 顧客名
$sheet->setCellValue('B6', '〒100-0001 東京都千代田区...'); // 住所
$sheet->setCellValue('F2', date('Y/m/d')); // 発行日

// 明細データを配列で準備
$invoiceItems = [
    ['商品A', 2, 1500, '=B10*C10'], // 商品名, 数量, 単価, 金額(数式)
    ['商品B', 5, 800, '=B11*C11'],
    // ... 続く明細行
];

// 明細データの書き込み開始行 (例: 10行目から)
$startRow = 10;
$sheet->fromArray($invoiceItems, NULL, 'A' . $startRow);

// 合計金額の数式を調整 (明細行の数に応じてSUM範囲を変更)
$endRow = $startRow + count($invoiceItems) - 1;
$sheet->setCellValue('D' . ($endRow + 1), '=SUM(D' . $startRow . ':D' . $endRow . ')'); // 明細行の最後の次の行に合計を記述

// ファイルを保存/ダウンロード
$outputFileName = 'invoice_' . date('YmdHis') . '.xlsx';
$writer = new Xlsx($spreadsheet);

header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="' . $outputFileName . '"');
header('Cache-Control: max-age=0');

$writer->save('php://output');
exit;

} catch (\Exception $e) {
die(“エラー: ” . $e->getMessage());
}
“`

テンプレート方式は、複雑なレイアウトや書式をExcel側で定義しておけるため、PHPコードがデータの挿入に集中でき、保守性が向上します。

6. パフォーマンスの考慮

PHPSpreadsheetはメモリ消費が大きいライブラリであり、特に大きなExcelファイルを扱う際にはパフォーマンスが問題になることがあります。数万行、数十万行といったデータを処理する場合、以下の点を考慮する必要があります。

6.1 大規模データ処理時のメモリ使用量

PHPSpreadsheetは、デフォルトではExcelファイル全体の内容をPHPのメモリ上に展開します。ファイルサイズやセル数が多いほど、必要なメモリ量が増大し、PHPのメモリ制限(memory_limit設定)を超えてエラーになることがあります。

6.2 セルキャッシュの設定

PHPSpreadsheetには、メモリ上のセルデータをファイルシステムやデータベースに一時的に退避させる「セルキャッシュ」の仕組みがあります。これにより、物理メモリの使用量を抑えることができます。

“`php
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Spreadsheet;

// セルキャッシュの設定 (ファイルシステムを使用する例)
// 一時ファイル用のディレクトリを指定
$cacheMethod = Settings::PCLZIP; // または Settings::ZIP (zip拡張機能)
$cacheStorageDirectory = sys_get_temp_dir(); // システムの一時ディレクトリを使用

if ($cacheMethod === Settings::PCLZIP && !class_exists(\PclZip::class)) {
echo “PclZip not available. Falling back to Array cache.\n”;
$cacheMethod = Settings::CACHE_IMPLICIT; // PclZipがない場合は配列キャッシュにフォールバック
}
if ($cacheMethod === Settings::ZIP && !extension_loaded(‘zip’)) {
echo “ZIP extension not available. Falling back to Array cache.\n”;
$cacheMethod = Settings::CACHE_IMPLICIT; // zip拡張機能がない場合は配列キャッシュにフォールバック
}

// キャッシュの設定を適用
Settings::setCacheStorageMethod($cacheMethod, [‘cache_path’ => $cacheStorageDirectory]);

echo “セルキャッシュ方式: ” . Settings::getCacheStorageMethod() . “\n”;

// これ以降のSpreadsheetオブジェクトの操作にキャッシュ設定が適用される
$spreadsheet = new Spreadsheet();
// … データの読み書き …
“`

利用できるキャッシュ方式はいくつかありますが、ファイルシステムを使う方法(Settings::PCLZIP または Settings::ZIP)が一般的です。PCLZIPはライブラリが内蔵されていますが、ZIPはPHPのzip拡張機能に依存します。大規模なデータには、ファイルシステムキャッシュが最も効果的です。

6.3 不要なオブジェクトの解放

PHPのガベージコレクションに頼るだけでなく、処理済みの大きな配列変数やオブジェクトは、不要になった時点でunset()で解放することで、より積極的にメモリ使用量を抑えることができます。

“`php
// 読み込んだ大きな配列データ
$largeData = $sheet->rangeToArray(‘A1:Z10000’, NULL, TRUE, FALSE, TRUE);

// $largeData を使って必要な処理を行う
// …

// 処理が終わったらメモリから解放
unset($largeData);
“`

6.4 Writerの最適化

ファイルを書き出す際にもパフォーマンスチューニングが可能です。特に計算結果が必要ない場合や、書式設定がシンプルな場合は、計算を無効化することで高速化できます。

“`php
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$writer = new Xlsx($spreadsheet);

// 数式の計算を無効にする (ファイルの書き出しが速くなるが、ファイルを開いたときに再計算が必要になる)
$writer->setPreCalculateFormulas(false);

// 一部の機能を無効化してパフォーマンスを向上させるオプション (XLSX Writer)
$writer->setIncludeCharts(false); // グラフを含めない
$writer->setIncludeImages(false); // 画像を含めない
$writer->setIncludeDataTables(false); // データテーブルを含めない

$writer->save(‘output.xlsx’);
“`

setPreCalculateFormulas(false)は書き出し速度に大きく影響します。ただし、保存されるファイルには数式の結果ではなく数式そのものが含まれるため、Excelでファイルを開いた際に再計算が必要になります。

6.5 Chunk Reading(読み込み時)

非常に巨大なファイルを読み込む際、load()メソッドはファイル全体を一度に読み込もうとするためメモリ不足に陥りやすいです。このような場合は、Chunk Readingという機能を使って、ファイルを分割して(チャンク単位で)読み込むことができます。

Chunk Readingでは、IReadFilterインターフェースを実装したクラスを使って、どの行を読み込むかを定義します。

“`php
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
use PhpOffice\PhpSpreadsheet\IOFactory;

// チャンク読み込み用のフィルタークラス
class ChunkReadFilter implements IReadFilter
{
private $startRow = 0;
private $endRow = 0;
private $columns;

/**
 * Set the list of rows that we want to read.
 *
 * @param int $startRow
 * @param int $endRow
 * @param array $columns
 */
public function setRows($startRow, $endRow, $columns = null) {
    $this->startRow = $startRow;
    $this->endRow = $endRow;
    $this->columns = $columns; // 読みたい列も指定可能
}

public function readCell($column, $row, $worksheetName = '') : bool
{
    // 指定した行の範囲内であれば true を返す
    if (($row >= $this->startRow && $row <= $this->endRow)) {
        // 特定の列のみを読みたい場合はここに列チェックを追加
        // if ($this->columns !== null) {
        //     if (!in_array($column, $this->columns)) {
        //         return false;
        //     }
        // }
        return true;
    }
    return false;
}

}

$inputFileName = ‘large_data.xlsx’;
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();

// Read filter のインスタンスを作成
$chunkFilter = new ChunkReadFilter();

// Readerにフィルターを設定
$reader->setReadFilter($chunkFilter);

$spreadsheet = null; // Spreadsheet オブジェクトを格納する変数

// チャンクサイズを定義 (例: 1000行ずつ)
$chunkSize = 1000;

// 最終行を取得するために、まずフィルターなしでヘッダーと最初の数行を読み込む
// または、ファイルの最終行を事前に知っている(例: DBに保存している)場合、このステップは不要
$reader->setReadFilter(null); // フィルターを一時的に解除
$preSpreadsheet = $reader->load($inputFileName);
$sheet = $preSpreadsheet->getSheet(0);
$highestRow = $sheet->getHighestRow();
unset($preSpreadsheet); // メモリ解放

echo “最終行: ” . $highestRow . “\n”;

$allData = []; // 全データを格納する配列

// 最終行までチャンク単位でループ
for ($startRow = 1; $startRow <= $highestRow; $startRow += $chunkSize) {
$endRow = min($startRow + $chunkSize – 1, $highestRow);

echo "チャンク読み込み: 行 {$startRow} から {$endRow} まで...\n";

// フィルターに行の範囲を設定
$chunkFilter->setRows($startRow, $endRow);

// Readerにフィルターを再設定
$reader->setReadFilter($chunkFilter);

// チャンクを読み込む
// 最初のチャンク以外は、既存のSpreadsheetオブジェクトに読み込むのではなく、
// 新しいSpreadsheetオブジェクトとして読み込み、必要なデータを抽出・処理します。
// ここではシンプルに各チャンクを個別に読み込み、データを収集する例を示します。
try {
    $chunkSpreadsheet = $reader->load($inputFileName);
    $chunkSheet = $chunkSpreadsheet->getSheet(0); // 最初のシート

    // ヘッダー行 ($startRow=1 の場合) をスキップしたい場合はここで調整
    $currentRow = $startRow;
    if ($startRow === 1) {
        // 1行目がヘッダーの場合、データは2行目から
        $currentRow = 2;
    }

    // 読み込んだチャンクのデータを行ごとに処理
    for ($row = $currentRow; $row <= $endRow; ++$row) {
        $rowData = $chunkSheet->rangeToArray(
            'A' . $row . ':' . $chunkSheet->getHighestColumn() . $row,
            NULL,
            TRUE,
            FALSE,
            TRUE // 行番号をキーにする
        );
        // rangeToArrayの結果は多次元配列 [[ ... ]] となるので、最初の要素を取り出す
        if (isset($rowData[$row])) {
             $allData[] = $rowData[$row];
        }
    }

    // チャンク用のSpreadsheetオブジェクトを解放
    unset($chunkSpreadsheet);
    unset($chunkSheet);

} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
    echo "チャンク読み込みエラー (行 {$startRow}-{$endRow}): " . $e->getMessage() . "\n";
}

}

echo “\nチャンク読み込み完了。読み込んだデータ数: ” . count($allData) . “\n”;
// 収集した $allData を処理する
// print_r($allData);
“`

Chunk Readingを使うことで、メモリに一度にロードするデータ量を減らし、巨大なファイルの読み込みを可能にします。ただし、実装は少し複雑になります。また、これは読み込み専用の手法です。書き込みはWriterオブジェクトが自動的に最適化(内部的にチャンク処理している)してくれることが多いです。

7. よくある問題と解決策

PHPSpreadsheetを使っていると、環境やファイルの内容によって様々な問題に遭遇することがあります。ここでは、特によくある問題とその解決策を紹介します。

7.1 Composerインストール時の依存関係エラー

Composerでcomposer require phpoffice/phpspreadsheetを実行した際に、依存関係の競合やPHP拡張機能の不足に関するエラーが表示されることがあります。

  • 依存関係の競合: composer updatecomposer require --no-updateを試したり、他のライブラリとのバージョン依存関係を確認したりします。場合によっては、composer.jsonを手動で編集して特定のライブラリのバージョン指定を調整する必要があります。
  • PHP拡張機能の不足: エラーメッセージに不足している拡張機能名が表示されるはずです(例: phpoffice/phpspreadsheet requires ext-zip * but it is missing)。サーバー環境に応じて、PHPの設定(php.ini)を編集して該当拡張機能を有効にするか、OSのパッケージマネージャーでインストールします(例: sudo apt-get install php-zip)。設定変更後はWebサーバーの再起動が必要です。

7.2 メモリ不足エラー (Allowed memory size of ... bytes exhausted)

PHPSpreadsheetで大規模なファイルを扱う際、PHPのメモリ制限を超過してこのエラーが発生することが最も多い問題の一つです。

  • PHPのメモリ制限を増やす: php.inimemory_limit設定値を増やします(例: memory_limit = 512M)。ただし、これは根本的な解決策ではなく、サーバーのリソースを過剰に消費する可能性があります。
  • セルキャッシュを利用する: 前述の「6.2 セルキャッシュの設定」を参考に、ファイルシステムキャッシュなどを利用してメモリ使用量を抑えます。
  • Chunk Readingを利用する: 巨大なファイルの読み込み時には、「6.5 Chunk Reading」を使ってファイルを分割して読み込み、メモリにロードするデータ量を制限します。
  • 不要なオブジェクトを解放する: $spreadsheetオブジェクト自体や、読み込んだ大きなデータ配列などは、処理が完了次第unset()で解放します。
  • よりメモリ効率の良いライブラリを検討する: 超巨大なCSVファイルの読み書きが主目的であれば、PHPSpreadsheetよりもSpoutのような軽量で高速なライブラリが適している場合もあります。

7.3 日付/時刻データの読み込みがシリアル値になる

「3.7 日付/時刻データの扱い」で解説した通り、Excelの日付はシリアル値として保存されています。getValue()はこれをそのまま返します。

  • \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject()を使ってPHPのDateTimeオブジェクトに変換します。
  • 単に文字列として表示したい場合は、getFormattedValue()を使いますが、Excel上の表示形式に依存します。

7.4 数式の評価が期待通りにならない

getValue()は数式そのもの(例: =SUM(A1:A10))を返すことがあります。getCalculatedValue()は数式を評価した結果を返そうとしますが、PHPSpreadsheetの数式計算エンジンはExcelのすべての数式を完全にサポートしているわけではありません。

  • 複雑な数式の結果が必要な場合は、Excelファイルを開いて手動で再計算するか、別の方法で計算ロジックを実装することを検討します。
  • 書き込み時に数式を埋め込む場合は、ファイルを開いた際にExcelが自動的に計算することが期待できますが、WriterのsetPreCalculateFormulas(true)(デフォルト)で書き出すことも可能です。

7.5 特殊文字やエンコーディングの問題

Excelファイル内の一部の特殊文字(例: タブ文字、改行コード、特定の記号)が正しく表示されない、または読み書き時に化ける場合があります。

  • PHPのmbstring拡張機能が有効になっていることを確認します。
  • ファイルの読み込み時に、使用されている文字エンコーディングを考慮する必要がある場合があります。特にCSVファイルではエンコーディングが重要です。PHPSpreadsheetはUTF-8を推奨しています。他のエンコーディングのCSVを読み込む場合は、CsvReaderのsetInputEncoding()メソッドを使用するか、PHP側でmb_convert_encoding()などを使って変換します。

7.6 特定のExcel機能がサポートされていない

PHPSpreadsheetは多くの機能をサポートしていますが、Excelのすべてのマイナーな機能(例: マクロ、Sparklines、高度なグラフタイプの一部など)に対応しているわけではありません。

  • PHPSpreadsheetの公式ドキュメントやGitHubリポジトリで、サポートされている機能リストを確認します。
  • サポートされていない機能が必要な場合は、代替策を検討するか、別のライブラリやツール(例: COMオブジェクト、外部コマンドラインツール)の使用を検討する必要があります。ただし、環境依存性や実装の複雑さが増す可能性があります。

8. 他のライブラリとの比較(簡単な触れ)

8.1 PHPExcel

PHPSpreadsheetはPHPExcelの直接的な後継プロジェクトです。PHPExcelは既にメンテナンスモードに入っており、新しい機能追加やPHPの新バージョンへの対応は期待できません。これから新規で開発する場合は、迷わずPHPSpreadsheetを選択すべきです。PHPSpreadsheetはPHPExcelのAPIと高い互換性を持っていますが、一部変更点や非推奨になったクラスもあります。

8.2 Spout

Spout(The Stream Processor for Office Open XML and ODS)は、PHPSpreadsheetよりも「大規模データの高速なストリーム処理」に特化したライブラリです。メモリ効率が非常に高く、巨大なExcel/CSVファイルを高速に読み書きできます。ただし、サポートしている機能はPHPSpreadsheetに比べて限定的です(書式設定や数式など高度な機能は非サポートまたは部分的サポート)。シンプルに大量のデータを読み書きするだけであれば、Spoutが優れた選択肢となる場合があります。複雑な書式設定やExcelの機能が必要な場合は、PHPSpreadsheetが適しています。

9. まとめ

この記事では、PHPSpreadsheetライブラリを使ったExcelファイルの読み込みと書き込みについて、基本的な使い方から応用的なテクニック、パフォーマンス最適化、そしてよくある問題の解決策まで、詳細に解説しました。

PHPSpreadsheetは、Composerで簡単に導入でき、XLSXをはじめとする様々な形式のスプレッドシートファイルを、PHPからプログラム的に生成・操作するための強力で柔軟なツールです。セルデータの取得・設定はもちろん、フォントや色、罫線といった書式設定、数式、コメント、画像、さらには条件付き書式や入力規則といった高度な機能までサポートしています。

特に大規模データを扱う際には、メモリ使用量が課題となりがちですが、セルキャッシュやChunk Readingといった機能を利用することで、この問題を克服し、効率的な処理を実現できます。

業務システム開発でExcel連携が必要になった際には、この記事を参考にPHPSpreadsheetを活用してみてください。Excel処理のコード記述がシンプルになり、開発効率が大幅に向上することを実感できるはずです。

さらに詳細な情報や、この記事で触れられなかった機能(グラフ作成、ピボットテーブルのより詳細な操作など)については、PHPSpreadsheetの公式ドキュメント(https://phpspreadsheet.readthedocs.io/)を参照することをお勧めします。公式ドキュメントは最新の情報が提供されており、より深い理解と応用を可能にします。

10. 付録:主要なクラス/メソッド リファレンス(抜粋)

クラス/オブジェクト 主なメソッド 説明
PhpOffice\PhpSpreadsheet\Spreadsheet getActiveSheet(), getSheetByName(), getSheet(index), getSheetCount(), addSheet(), removeSheetByIndex(), setActiveSheetIndexByName() スプレッドシート全体を表すオブジェクト。ワークシートの管理を行う。
PhpSpreadsheet\Worksheet\Worksheet getTitle(), setTitle(), getCell(coordinate), setCellValue(coordinate, value), fromArray(data, nullValue, startCell), getHighestRow(), getHighestColumn(), getStyle(coordinate), getColumnDimension(column), getRowDimension(row), setAutoFilter(), setComment(), setMergeCells() ワークシート(シート)を表すオブジェクト。セルデータの読み書きやシートレベルの設定を行う。
PhpSpreadsheet\Cell\Cell getValue(), getFormattedValue(), getCalculatedValue(), getDataType(), getCoordinate() 個々のセルを表すオブジェクト。セルの値や型、座標を取得する。
PhpSpreadsheet\Style\Style getFont(), getFill(), getBorders(), getAlignment(), getNumberFormat(), getConditionalStyles() セルの書式設定(フォント、背景色、罫線、配置、表示形式など)を行うためのスタイルオブジェクト。
PhpOffice\PhpSpreadsheet\IOFactory createReaderForFile(filename), createWriter(spreadsheet, writerType) ファイル形式に応じたReaderやWriterオブジェクトを生成するファクトリクラス。
PhpOffice\PhpSpreadsheet\Reader\Xlsx (他Reader) load(filename), canRead(filename), setReadFilter() Excelファイルなどを読み込むためのReaderクラス。
PhpOffice\PhpSpreadsheet\Writer\Xlsx (他Writer) save(filename or 'php://output'), setPreCalculateFormulas() Spreadsheetオブジェクトをファイルとして書き出すためのWriterクラス。
PhpOffice\PhpSpreadsheet\Shared\Date excelToDateTimeObject(excelTimestamp), dateTimeToExcel(\DateTime object) Excelのシリアル値とPHPのDateTimeオブジェクトを相互変換するためのヘルパークラス。
PhpOffice\PhpSpreadsheet\Cell\Coordinate columnIndexFromString(columnString), stringFromColumnIndex(columnIndex) 列番号(A, B, …)とインデックス(0, 1, …)を相互変換するためのヘルパークラス。
PhpOffice\PhpSpreadsheet\Settings setCacheStorageMethod(), getCacheStorageMethod() PHPSpreadsheet全体の動作設定を行うクラス(例: セルキャッシュ方式)。
PhpOffice\PhpSpreadsheet\Worksheet\Drawing setPath(), setCoordinates(), setHeight(), setWidth(), setWorksheet() 画像をワークシートに描画(埋め込み)するためのオブジェクト。
PhpOffice\PhpSpreadsheet\Cell\DataValidation setType(), setOperator(), setFormula1(), setErrorTitle(), setError() セルの入力規則(データ検証)を設定するためのオブジェクト。
PhpOffice\PhpSpreadsheet\Style\Conditional setConditionType(), setOperatorType(), setText(), getStyle() 条件付き書式設定の個々の条件を表すオブジェクト。

この付録は、記事中で紹介された主要なクラスとメソッドの一部を抜粋したものです。これらのクラス/メソッドを組み合わせることで、PHPSpreadsheetの様々な機能を利用できます。


以上で、PHPSpreadsheetに関する約5000語の詳細な記事は完了です。この情報が、あなたのPHP開発におけるExcel処理の効率化に役立つことを願っています。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール