メモPHP

Phpspreadsheetで書式があるとgetValue()で文字を取得できない

2022.02.28

こんにちはjunです。PHPでエクセルをアップロードして、データを取得するという機能を実装する際にPhpspreadsheetを使用することが多いと思います。アップロードされたエクセルを以下の様にしてセルの値を取得することができます。

use \PhpOffice\PhpSpreadsheet\IOFactory;
use \PhpOffice\PhpSpreadsheet\Shared\Date;

// $fileはエクセルファイル
$spreadsheet = IOFactory::load($file);
$worksheet = $spreadsheet->getSheetByName('Sheet1');

// A1のセルのデータを取得する
$workSheet->getCell("A1")->getValue();

getCell()の引数を指定して再帰的に全ての列の値を取得して配列にするみたいなこともできるので、エクセルを用いてwebアプリへのデータ入力ができます。最初はgetValue()を用いて問題なくセルの文字を取得できていました。しかしある日、お客さんから「このエクセルを読み取らせると変な文字が表示されます」という問い合わせをいただきました。サーバーにアップしたエクセルの内容を上記の様に取得してブラウザに表示するということをやっていたのですが、確かに{}という謎の文字がありました。

デバッグログを見てみると下記の様に一部の値がインスタンスとなっていました。

array(
'name' => 
  PhpOffice\PhpSpreadsheet\RichText\RichText::__set_state(array(
     'richTextElements' => 
    array (
      0 => 
      PhpOffice\PhpSpreadsheet\RichText\TextElement::__set_state(array(
         'text' => 'テスト',
      )),
      1 => 
      PhpOffice\PhpSpreadsheet\RichText\Run::__set_state(array(
         'font' => 
        PhpOffice\PhpSpreadsheet\Style\Font::__set_state(array(
           'name' => '游ゴシック',
           'size' => 12.0,
           'bold' => false,
           'italic' => false,
           'superscript' => false,
           'subscript' => false,
           'underline' => 'none',
           'strikethrough' => false,
           'color' => 
          PhpOffice\PhpSpreadsheet\Style\Color::__set_state(array(
             'argb' => 'FF000000',
             'hasChanged' => false,
             'isSupervisor' => false,
             'parent' => NULL,
             'parentPropertyName' => NULL,
          )),
           'colorIndex' => NULL,
           'isSupervisor' => false,
           'parent' => NULL,
           'parentPropertyName' => NULL,
        )),
         'text' => 'テス',
      )),
      2 => 
      PhpOffice\PhpSpreadsheet\RichText\Run::__set_state(array(
         'font' => 
        PhpOffice\PhpSpreadsheet\Style\Font::__set_state(array(
           'name' => '游ゴシック',
           'size' => 12.0,
           'bold' => false,
           'italic' => false,
           'superscript' => false,
           'subscript' => false,
           'underline' => 'none',
           'strikethrough' => false,
           'color' => 
          PhpOffice\PhpSpreadsheet\Style\Color::__set_state(array(
             'argb' => 'FF000000',
             'hasChanged' => false,
             'isSupervisor' => false,
             'parent' => NULL,
             'parentPropertyName' => NULL,
          )),
           'colorIndex' => NULL,
           'isSupervisor' => false,
           'parent' => NULL,
           'parentPropertyName' => NULL,
        )),
         'text' => 'テスト',
      )),
    ),
  )),
)

対象のエクセルのセルを見てみると文字が赤色になっていたりと装飾が当てられていました。原因がわかったので対処を行います。調査の結果似た様なissueがありました。RichText can not get the correct value, when one part is plain text and the other part is rich text.

方法としてはgetValue()に加えてgetPlainText()というメソッドを通すことで装飾されたセルの値(リッチテキスト)を取得することができます。しかしgetPlainText()には問題があり、なんとリッチテキストでない値に適用すると以下の様エラーが発生します。

Call to a member function getPlainText() on string.

つまり、getValue()した時の値がリッチテキストインスタンスであるかをチェックしてgetPlainText()を適用するかを分岐させる必要があります。リッチテキストは\PhpOffice\PhpSpreadsheet\RichText\RichTextというクラスなので以下の様な関数を作成すれば問題ありません。

use \PhpOffice\PhpSpreadsheet\RichText\RichText;

function getCellValue($workSheet,$cell){
    $val = $workSheet->getCell($cell)->getValue();
    return ($val instanceof RichText)?$val->getPlainText():$val;
}

リッチテキストのインスタンスかをチェックして値を取得することでリッチテキストもプレーンテキストも対応することができました。こう考えるとやっぱりエクセルインポートなんてやめてCSVインポートにしておけばよかったなーと思っています。