読者です 読者をやめる 読者になる 読者になる

k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

PHPUnit 4.1系で \Symfony\Component\HttpFoundation\File\UploadedFile のモックオブジェクトを作成しようとすると "Erroneous data format for unserializing" のエラーが発生した件

PHPUnit 4.1系で \Symfony\Component\HttpFoundation\File\UploadedFile のモックオブジェクトを作成しようとすると "Erroneous data format for unserializing" のエラーが発生します。

PHP 5.6.1 + PHPUnit 4.1.6 で確認しました。

エラーメッセージで検索してみると、phpunit/phpunit-mock-objects にこんなissueが。

whatthejeff さん曰く

Unfortunately, since Symfony\Component\HttpFoundation\File\UploadedFile extends SplFileInfo, you will not be able to instantiate it without calling its constructor in 5.5.16. This was previously possible with a serialization hack, but it has been deemed unsafe by the PHP core team.

(英語分からないので機械翻訳を元にしたフィーリング訳ですが)

Symfony\Component\HttpFoundation\File\UploadedFile は SplFileInfo を継承していますが、組み込みオブジェクトの多くがシリアライズ不可なため、以前は "serialization hack" と呼ばれる手段で回避していたとのこと。

しかし、PHP 5.5.16 から(?)この方法が安全ではないと判断されて禁止され、コンストラクタを呼ばずにインスタンスを生成できなくなったため、エラーが発生するようになったみたいです。

issueのコメントからリンクされていたコミットを見たところ phpunit/phpunit-mock-objects の 2.3で対処されたみたい?

差分を見たところ ReflectionClass::isInternal() での分岐処理が追加されてます。

ユーザークラスの場合は ReflectionClass::newInstanceWithoutConstructor() で生成されて、内部クラスの場合は従来通りのコード、つまり "serialization hack" で生成しているようです。

クラス名を元に空のオブジェクトをシリアライズした文字列を組み立てて unserialize() すると、インスタンスが生成できるというトリックのようですね。

<?php
// We have to use this dirty trick instead of ReflectionClass::newInstanceWithoutConstructor()
// because of https://github.com/sebastianbergmann/phpunit-mock-objects/issues/154
$object = unserialize(
    sprintf('O:%d:"%s":0:{}', strlen($className), $className)
);

コメントを見たところ、そもそもこのトリックを使い出したきっかけもSymfony2のテストで不具合が発生したためのようです。

ともあれ、現在の phpunit/phpunit-mock-objects のバージョンを調べてみると…。

$ composer global show -i phpunit/phpunit-mock-objects
Changed current directory to C:/Users/k_horii/AppData/Roaming/Composer
name     : phpunit/phpunit-mock-objects
descrip. : Mock Object library for PHPUnit
keywords : mock, xunit
versions : * 2.1.5
type     : library
license  : BSD-3-Clause
source   : [git] https://github.com/sebastianbergmann/phpunit-mock-objects.git 7878b9c41edb3afab92b85edf5f0981014a2713a
dist     : [zip] https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/7878b9c41edb3afab92b85edf5f0981014a2713a 7878b9c41edb3afab92b85edf5f0981014a2713a
names    : phpunit/phpunit-mock-objects

(以下略)

2.1.5らしい。こりゃあかんわ。

とりあえずPHPUnitのバージョンを4.1系から4.2系に上げてみます。

$ composer global require "phpunit/phpunit=4.2.*"
$ composer global update
Changed current directory to C:/Users/k_horii/AppData/Roaming/Composer
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing doctrine/instantiator (1.0.4)
    Downloading: 100%

  - Removing phpunit/phpunit-mock-objects (2.1.5)
  - Installing phpunit/phpunit-mock-objects (2.3.0)
    Downloading: 100%

  - Removing phpunit/phpunit (4.1.6)
  - Installing phpunit/phpunit (4.2.6)
    Downloading: 100%

Writing lock file
Generating autoload files

試してみたところ、これで通るようになりました。同じ問題に引っ掛かった方へのヒントになれば幸いです。