PHP5.5→PHP7.3へマイグレーションした話

PHP5.5(!)で動いていた某プログラムを、マイグレーションして7.3で動くようにした話
（最近の実話です）
色々あって相当な期間放置してたんだけど、いよいよまずいから何とかしなきゃねってなった、とかそんなやつ。

# 実行環境の変化

* CentOS6系 → 7系
* Apache2.2系 → 2.4系
* PHP5.5系 → 7.3系　（今回はここをメインに取り扱いたい）
* 事情によりPostgresを使っていたがMysqlに置き換えたい（これは別記事にしようと思う）

# 手入れの方針
* phpenvを使ってバージョンを切り替えられるようにした
* 元々PEAR使ってた、んだけどレガシーなモジュール管理ツールだからか、新しいバージョンに対応したモジュールがなかったりした。
* のでcomposerに置き換えた、当然使えるパッケージが変わるから内部にも手を入れないといけなくなった（さすがに仕方ないけど最低限にしたいよね）
* 例えば、`PEAR::Mail`は7系では使えない、`genkgo/mail`に入れ替えた　**（この記事のメインの話題その1）**
* あと`PEAR::MDB2`も怪しかったので`PDO`に入れ替えた　**（この記事のメインの話題その2）**

# 設定関連でやったこと
## phpenv周り
* php各バージョンをインストールする際に参照されるビルドオプションファイルに、Apacheと連携するための端点になる`libphp(5/7).so`を作るように内容を記載
* composerを利用する際にはintl拡張が必要らしいのでこれを追加するように内容を記載

```/usr/lib/phpenv/plugins/php-build/share/php-build/definitions/[php.ver.sion]
configure_option "--with-apxs2=/usr/bin/apxs"
configure_option "--enable-intl"
```

この際、`libphp(5/7).so`は、`/usr/lib64/httpd/modules/libphp(5/7).so`に生成されるので、`/usr/lib/phpenv/versions/[バージョン]/libexec/libphp(5/7).so`あたりに設置し、このファイルをApacheから参照するとよいかと。

* （参考）PEARを使う場合は、phpenvのinstallコマンド実行の際に、下記のようなコマンドを入力すればよい

```
CONFIGURE_OPTS="--with-pear" phpenv install [バージョン]
```

こうすることで、 `/usr/lib/phpenv/versions/[バージョン]/lib/php` みたいな場所にPEAR.phpが生成される。

## PHP周り
* インクルードパスの設定
* mysqlの設定

``` /usr/lib/phpenv/versions/[php.ver.sion]/etc/php.ini
pdo_mysql.default_socket= /var/lib/mysql/mysql.sock
```
## httpd周り
* libphp(5/7).soのロード

```/etc/httpd/conf/httpd.conf
LoadModule php7_module        /usr/lib/phpenv/versions/[バージョン]/libexec/libphp(5/7).so
``` 

httpd.conf を直接更新するのがアレだったりする場合は、`/etc/httpd/conf.modules.d/10-php.conf`あたりに記載する。このときは、httpd.confの方の設定をコメントアウトすることを忘れないように。

## composer周り
多分ほかの記事を探した方が早いので割愛するが、今回の場合は、プログラムの実体が入っている（あとインクルードパスが通っている）`lib/`あたりで

```
composer require genkgo/mail
```

を実行し、必要なモジュールを導入、`lib/vendor/`が生成される感じにした。
また、必要なら.gitignoreにvendorを追加する。

# プログラムの改修が必要だったところ

## 方針
継承元になる親クラスとか、インターフェースになるようなクラスファイルをコピーして、メソッド名と引数を共通にして、同様の動きをするメソッドを実装する感じ（そりゃそうだって話なんだけど一応）

## DB周り
全体的には、MDB2とPDOにはかなり共通点があるので、意外と直す難易度は低いと感じた。
（修正箇所は多いかもしれないが）

* DSNを渡すときの形式が異なっている<br/>
MDB2ではDSNをArrayで渡すことができたが、PDOではStringにしてやる必要がある。
なのでMDB2の時に使っていたArrayから強引に、

```php
$dsn_str = $DSN["phptype"].":host=".$DSN["hostspec"].";dbname=".$DSN["database"].";charset=utf8";
$db = new PDO($dsn_str,$DSN["username"],$DSN["password"]);
```

みたいな感じ。
あとMDB2ではFactoryを使ってたけど、`new PDO()`でOKっぽいね、簡便でよさげ。

* PreparedStatementと、SQL内の変数の指定が変わった<br />
MDB2では`db->prepare()`の中に、sprintfとかで直接変数を入れたSQLを渡して実行できた（当然変数はエスケープ処理してから代入する）が、PDOではPreparedStatementの中に`:変数`で値を入れておき、`$ps->bindParam()`で値を差し込むようにして実行する必要がある

```php
$ps = $db->prepare("SELECT count(*) AS cnt from table where key = :query_key ;");
$ps->bindParam(":query_key", $query_key, PDO::PARAM_STR);
$ps->execute();
```

みたいな感じ。

* クエリを実行した結果得られるものが違う<br />
ある意味そりゃそうだ。MDB2では、`$result = $ps->execute()`して得られる$resultには、実行結果が入っているが、PDOでは実行結果が取れたかどうか（True/False）が入る。<br />
また、（fetchオプション次第ではあるが）得られた結果へのアクセス方法が異なる

```php:MDB2
$rows = $result->fetchAll()
$cnt = $rows[0]->cnt        //ここ
```

```php:PDO
while($row = $ps->fetch(PDO::FETCH_ASSOC)){
    $rows[] = $row;
}
$cnt = $rows[0]["cnt"]      //ここ
```
こんな感じなので、これに合わせてプログラムを適宜書き換える必要がある。（この修正箇所が今回は多くて大変だった）

* `$db->disconnect()`がない<br />
`unset($db)`に書き換え。

## composerを使うための改修
幸運なことに（？）PEARのモジュールを完全に排除して、composerに移行することができた。
他の記事に親切なものがあると思うが、composerで導入したモジュールをロードするために

```php:common.php
require_once "vendor/autoload.php";
```

みたいな記述を追加した。

### genkgo/mail 周りの話
`PEAR:Mail`がPHP7.1系以降対応していないので代わりのモジュールを探して、
「新しそうなモジュールだしこの辺にしよう！」という適当加減で選んだ結果、
導入した後になって気付く、詳しいことが記載してあるページが、日本語ページはおろか、英語のマニュアルもないという事案。(2019/1/31現在)
[gengko/mailのマニュアル(英語)](https://mail.readthedocs.io/en/latest/)

[PHP7.1未満お断り！最新PHPメーラーライブラリ Genkgo/Mail を試してみる](https://qiita.com/mpyw/items/88161f9a809e063823c6)を参考にしたが、HTML形式のメールをシンプルに送る方法は記載してあるものの

* プレーンテキストのメール送信方法
* 独自ヘッダの追加方法

に関しては情報がなかったのでソースを読んでみた。
どう使うか、サンプルコードを掲載しておく。

```php
use Genkgo\Mail\PlainTextMessage;
use Genkgo\Mail\Header\From;
use Genkgo\Mail\Header\To;
use Genkgo\Mail\Header\Cc;
use Genkgo\Mail\Header\Bcc;
use Genkgo\Mail\Header\Subject;
use Genkgo\Mail\Header\HeaderName;
use Genkgo\Mail\Header\HeaderValue;
use Genkgo\Mail\Header\ParsedHeader;
use Genkgo\Mail\Transport\PhpMailTransport;
use Genkgo\Mail\Transport\EnvelopeFactory;

function send ($mailbody,$from_addr,$from_name,$subject,$to_array,$cc_array,$bcc_array,$originalHeaderValueString) {
    $message = new PlainTextMessage($mailbody);
    $message = $message
        ->withHeader(From::fromAddress($from_addr,$from_name))
        ->withHeader(new Subject($subject))
        ->withHeader(To::fromArray($to_array))
        ->withHeader(Cc::fromArray($cc_array))
        ->withHeader(Bcc::fromArray($bcc_array))
        ->withHeader(new ParsedHeader(
            new HeaderName("OriginalHeader"),new HeaderValue($originalHeaderValueString)));

    $transport = new PhpMailTransport(EnvelopeFactory::useExtractedHeader());
    $transport->send($message);
}
```

昔のコードだとマルチバイトとか文字コードとかいろいろ考慮してやらないと文字化けしまくって大変だったのに、今ではこれで何とかなるんだから便利になったんだなあって思ったりしてる。（老害状態）

### ハマったとこ
* composerを使う上で外せない、use句はtryの中で使えない<br />
プログラム全体をtryで囲ってしまう、みたいなやり方は今風じゃないってことか。
* composerを使う上で外せない、use句はphpファイルの一番上に書く<br />
useを使ったことがなかったんですというか、実はphp自体あんまり触ったことがないの、、（ひでえ暴露）

ひとまずここまで

# 後日取り扱いたい
* PostgresからMysqlに移行した際に手入れした話（かなりシンプルなDBだったので役に立たなさそうだけど、、、）
* Apache2.2から2.4に入れ替えたときにハマった話