igo-phpで名字と名前を分割しようとしたら、メモリ不足エラー

Amazon PayのShippingAddress.name

Amazon PayでCheckoutSessionをスタートすると、その返り値のShippingAddressにお届け先住所が入ってきます。そのなかのName がお届け先のお名前で、姓名分割されていない1つのフィールドです。

Amazon Pay FAQ(v2)には、

Amazon Payから取得できるアドレス帳の情報には様々なパターンがあります。テストユーザーを用意していますので、どのパターンのユーザ情報も処理ができることをご確認してください。

氏名ブランクあり(半角)

氏名ブランクあり(全角)
氏名ブランクなし
ミドルネームあり

Amazon Pay FAQ(v2) アカウント情報として何が取得できますか?

とあります。

半角スペースで区切ったケース「東京 太郎」、全角スペースで区切ったケース「東京 太郎」、詰まっているケース「東京太郎」があるそうです。

そのうちの半角スペース区切り、全角スペース区切りは、PHPのexplode関数で分割できます。

$name = '東京 太郎';
$arr = explode(' ', $name, 2);
// $arr[0] '東京'
// $arr[1] '太郎'

$name = '東京 太郎';
$arr = explode(' ', $name, 2);
// $arr[0] '東京'
// $arr[1] '太郎'
Code language: plaintext (plaintext)

igo-php

スペースで区切られていない場合は、igo-phpを使って分割します。

igo-phpは、半角スペース区切り、全角スペース区切りの場合も分割してくれます。

igo-phpを使って、姓名分割する処理は、igo-php-sample を参考にしました。

本家

GitHub - siahr/igo-php: Igo-php : Japanese morphological analyzer
Igo-php : Japanese morphological analyzer. Contribute to siahr/igo-php development by creating an account on GitHub.

本家を使うことができればいいんですが、namespace設定がありません。

ECサイト側にnamespaceのないCategoryクラスがすでにありました。igo-phpでもCategoryクラスを定義していてバッティングしてしまい、使えませんでした。

packagistで、igo-phpを検索すると、いくつかヒットしました。

logue/igo-php 0.2.0

requires php: >= 5.4.0 とありますが、PHP7以降で導入されたメソッドの返り値の型を宣言しています。

ECサイト側の諸事情で、これも使えませんでした。

technote/igo-php v0.3.29

requires php: >= 5.6.0、こちらを使うことができました。

メモリ不足エラー

ところが、実行しみてると、メモリ不足エラーです。

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 72 bytes)  in /xxx/yyy/vendor/technote/igo-php/src/Igo/FileMappedInputStream.php on line 60Code language: plaintext (plaintext)

メモリ上限の設定を調べると、128M でした。

ini_get('memory_limit')Code language: plaintext (plaintext)

メモリ上限を140Mにして、ようやくエラーがなくなりました。

ini_set('memory_limit', '140M');Code language: plaintext (plaintext)

少ししか上乗せしていないけど、140Mでいいの?

姓名分割処理でどれぐらいメモリを使っているのか知りたいわね

姓名分割処理がどれぐらいメモリを使っているのか、調べてみると、

$mem0 = memory_get_usage();

〜姓名分割処理〜

$mem1 = memory_get_peak_usage();
echo $mem1 - $mem1;Code language: plaintext (plaintext)

120Mも使っていました。

つまり、もともとのページ処理では、20M程度しかメモリを使っていませんでした。姓名分割処理が120Mを使おうとして、メモリ上限の128Mに達してしまったんですね。

メモリ上限をいくつにするかは迷うところです。今回の件では、160M〜256Mにしておけばいいと思います。

ここまでは、メモリが例えば500Mも余っているのに、memory_limit=128Mだったので、エラーになった。memory_limit=140Mにして、140Mのメモリを確保できるようになった、という状況です。

ところが、メモリが100Mしか余っていない場合、memory_limit=140Mにしてあっても、メモリ不足エラーになってしまう可能性があります。

問題なのは、PHPのメモリ不足エラーは、error_handling関数でキャッチできないことです。ユーザに500エラーを晒してしまいます。

姓名分割処理がメモリ不足エラーで落ちても、呼んだ側は落ちないようにするには、違うプロセス空間で実行する必要があります。

呼んだ側と姓名分割処理が違うプロセスなら、呼んだ側は姓名分割処理の道連れにならずにすみます。

心配しすぎかもね

違うプロセスで姓名分割を処理する

違うプロセスで実行するには、pcntl_fork、REST API、CLI、の方法があります。

pcntl_fork

呼ぶ側を親プロセス、新しいプロセスを子プロセスと呼びます。

子プロセスの起動時間が最も短そうです。

子プロセスが姓名分割処理の結果を返すには、共有メモリを使います。

親プロセスが子プロセスの姓名分割処理が終わるまで待つには、pcntl_waitpid関数を使います。

注意することは、子プロセス側は姓名分割処理が終わったら、exit()を呼ぶ必要があることです。exit()を呼ばないと、子プロセスがコールスタックを戻りながら、コントローラまで戻り、データベースなどを更新してしまいます。

<?php

class Fuga
{
    public function nameSplit($name)
    {
        $pid = pcntl_fork();
        if ($pid == -1) {
             return [$name, ''];
        } else if ($pid) {
             // 親プロセスの場合
             pcntl_wait($status); // ゾンビプロセスから守る
             $nameArr = 共有メモリから結果を取り出す
             return $nameArr;
        } else {
             // 子プロセスの場合
             姓名分割処理
             結果を共有メモリに書く
             exit();  // ★コレ
        }
}
Code language: plaintext (plaintext)

設定によっては、pcntl_forkが使えないこともあるそうです。

PHPがサポートするプロセス制御機能は、デフォルトでは有効となってい ません。

https://www.php.net/manual/ja/pcntl.installation.php

REST API

呼ぶ側は、curl関数で呼びます。curl関数の呼び出しがゴチャゴチャしがちです。

呼ぶ側のユニットテストをするために、REST API側のWebサーバ起動が必要です。

公開する必要がなければ、REST APIのアクセス制限をして、localhostだけにアクセス許可します。

メモリ不足エラーになると、アクセスログやエラーログに500エラーが残ります。

CLI

実測していませんが、REST APIより遅いかもしれません。

呼ぶ側は、passthru関数で呼びます。

CLI側は、コマンドライン引数を受け取り、STDOUTにjsonを出力します。

呼ぶ側は、ob_start関数とob_get_clean関数でpassthru関数をはさんでおき、結果をキャプチャーします。

ob_start();
passthru($cmd, $result);
$output = ob_get_clean();

if ($result == 0) {
    $vars = json_decode($output, true);
}Code language: plaintext (plaintext)

まとめ

pcntl_forkは、子プロセス側でexit()を書かなければいけないことが少し気になります。

pcntl_forkを使い慣れていないだけでしょ

REST APIは、curl関数の呼び出しがゴチャゴチャしがちなこと、呼ぶ側のユニットテストが難しそうです。

今回は、CLIで実装しました。

タイトルとURLをコピーしました