PHP を使ってボタンを実装してブラウザから押して,何らかの処理を実行しようとしてエラーを吐かれた経験がある人はかなり多いのではないかと思います.
その中でいくつか特に詰まった点を解説していきたいと思います.
主にパスが通っていないことが問題であることが多いので,パスを確認するのがいいと思います.
1. ログの出し方
まず,デバッグをするにはエラーメッセージを出さないと始まりません.test.php
にアクセスした時にhoge
命令を実行し,そのエラーメッセージを確認したい時は以下のようにします.
exec("hoge 2>&1",$dum,$rtn); // 2>&1 で標準エラー出力も標準出力へ吐き出す
echo('<pre>'); // フォーマットを整える
var_dump($dum); // 標準出力 (標準エラー出力を含む) をブラウザに出力
echo('<pre>');
echo("return: $rtn"); // hoge 命令の返り値を "return: 返り値" の形で出力
これで,ボタンを押した後にブラウザにエラーメッセージが表示されるはずです.
基本的にこのエラーを見て,ググれば大体は解決するはずですが,少し詰まりどころを解説していきます.
2. Not Found
エラーメッセージが例えば以下のようなものだったとします.
array(1) {
[0]=>
string(23) "sh: 1: hoge: not found"
}
return: 127
not found の意味通り, hoge 命令が見つからないと言っています.
まず,terminal から hoge
が実行されるか確認します.
$ hoge
2.1. これで実行できない場合
hoge
命令がどこにあるか調べてパスを通してください.
見つからない場合は最悪,以下のように全ファイルを調べれば見つかると思います.
この命令は管理者権限で (sudo) ,ルート (/
) から全てのファイル(-type f) のうち,ファイル名が hoge のもの (-name “hoge”) を探す (find) という意味になります.
$ sudo find / -type f -name "hoge"
ほとんどの場合 /path/to/bin/hoge
のような形だと思うので,以下のようにすればパスが通ると思います.
$ export PATH=$PATH:/path/to/bin
毎回これをするのは面倒なので,.bashrc
や .zshrc
などに追記しておきましょう.
$ echo 'export PATH="$PATH:/path/to/bin"' >> ~/.bashrc
これで問題が解決することはないと思うので,次の 2.2. 実行できた場合
に進みましょう.
2.2. 実行できた場合
ユーザの環境では hoge
が見つかっているが,PHP は見つけられないということになります.
つまり,PHP が使っている環境ではパスが通っていないということになります.
では,PHP のパスを確認してみましょう.
getenv に PATH という引数を与えることで,パスを取得してきてくれます.
echo(getenv("PATH"));
このようにして,再度ブラウザでボタンを押すと筆者と全く同じではないかもしれませんが,以下のようにパスが表示されているはずです.
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
この中には /path/to/bin
はないはずなので,これを足してあげれば動くはずです.
ここでは putenv を使ってパスを追加しています.
echo(getenv("PATH"));
echo('<br>');
$PATH = getenv("PATH");
putenv("PATH=$PATH:/path/to/bin");
echo(getenv("PATH"));
このようにしてみると以下のように表示されてパスが変更されたのがわかると思います.
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/path/to/bin
再度試してみるとエラー出力がなくなって,0 が返ってきている (正常終了している) ことがわかると思います.
array(0) {
}
return: 0
後は,余計な出力を消せば完成です.
3. ModuleNotFoundError
test.php
にアクセスした時に Python の関数を実行しようとした時に起きる可能性のあるエラーです./path/to/hoge.py
を実行することを考えてみます.
exec("python /path/to/hoge.py 2>&1",$dum,$rtn);
普通にローカル環境では動くのに,以下のようなエラーが出ることがあります.
array(4) {
[0]=>
string(34) "Traceback (most recent call last):"
[1]=>
string(70) " File "/path/to/hoge.py", line 13, in "
[2]=>
string(34) " import selenium"
[3]=>
string(47) "ModuleNotFoundError: No module named 'selenium'"
}
return: 1
つまり,selenium
というモジュールは見つからないと言われているわけです.
3.1 実行されている Python を確認
まずは実行されている Python のパス確認しましょう.
exec("which python",$dum,$rtn);
ログの出力は以下のようになると思いますが,ローカルで which python
を実効した結果と一致しているか確認してみてください.
array(1) {
[0]=>
string(16) "/usr/bin/python"
}
return: 0
一致していなければ(というかそもそも),実行する時に以下のようにフルパスで書いておくとよいです.
exec("/usr/bin/python /path/to/hoge.py 2>&1",$dum,$rtn);
3.2. Python が同じでも動かない
これで,同じ Python を実行しても動かない場合に集中できます.
ローカルではモジュールはあるのに,PHP で動かす時にはモジュールがないということなので,PHP からはアクセスできない場所にある,つまりパスが通っていないということになるはずです.
3.2.A. グローバルにモジュールをインストールする方法
簡単に解決する方法は,このモジュールをグローバルにインストールすることです.
$ sudo pip install selenium
これで,グローバルな環境にモジュールがインストールされたはずなので,PHP からモジュールが読み込めるはずです.
3.2.B. モジュールのパスを通す方法
一方,モジュールをグローバルにインストールするのは躊躇われるという場合もあるかと思います.
その場合はパスを通しましょう (この方法は筆者の場合は失敗しました…).
まずはモジュールの場所を調べないといけません.
ここで,ユーザのホームディレクトリを /path/to/home/
としておきます.
$ python
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import selenium
>>> print(selenium.__path__)
['/path/to/home/.local/lib/python3.7/site-packages/selenium']
>>> import sys
>>> print(sys.path)
['', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/path/to/home/.local/lib/python3.7/site-packages', '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages']
>>> exit()
このようになっていたので,筆者の場合は /path/to/home/.local/lib/python3.7/site-packages
にパスが通っていればいいということになり,実際 sys.path
の結果に含まれていることがわかります.
一方,PHP でのパスを確認するために,path_test.py
を作成して,実行します.
import sys
print(sys.path)
import selenium
exec("/usr/bin/python /path/to/path_test.py 2>&1",$dum,$rtn);
実行結果は以下のようになりました.
array(1) {
[0]=>
string(169) "['/path/to/home', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages']"
[1]=>
string(34) "Traceback (most recent call last):"
[2]=>
string(51) " File "/path/to/path_test.py", line 3, in "
[3]=>
string(19) " import selenium"
[4]=>
string(47) "ModuleNotFoundError: No module named 'selenium'"
}
return: 0
確かに,/path/to/home/.local/lib/python3.7/site-packages
は含まれておらず,selenium
も import できていません.
そこで,path_test.py
を以下のように変更してみます.
import sys
# パスを追加
sys.path.append("/path/to/home/.local/lib/python3.7/site-packages")
print(sys.path)
import selenium
これでいける!!!!と思ったら無理でした…
array(5) {
[0]=>
string(216) "['/path/to/home', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages', '/path/to/home/.local/lib/python3.7/site-packages']"
[1]=>
string(34) "Traceback (most recent call last):"
[2]=>
string(52) " File "/path/to/path_test.py", line 10, in "
[3]=>
string(19) " import selenium"
[4]=>
string(47) "ModuleNotFoundError: No module named 'selenium'"
}
return: 1
しっかりとパスも追加されていますが,import には失敗しました.
原因を探っていると以下のことがわかりました.
$ cd /path/to/home
$ ls -la
drwx------ 5 user user 4096 Jul 2 2020 .local
つまり,.local
ディレクトリにアクセス権がありませんでした.
無理やり以下のようにアクセス権を与えてもいいのですが,セキュリティ上あまりよろしくなさそうなので,諦めました.
$ chmod -R 744 .local # これは少し危険です
3.2.C. モジュールをコピーしてくる方法
先程惜しいところまで行ったので諦めきれずに,path_test.py
ファイルがあるディレクトリ /path/to/home
にはパスが通っていることを利用して,そのディレクトリに selenium
のディレクトリを持ってきました (強引).
$ cp -r /path/to/home/.local/lib/python3.7/site-packages/selenium /path/to/home
$ chmod -R 744 selenium
これで,めでたくアクセスできるようになりました!
import sys
print(sys.path)
import selenium
array(1) {
[0]=>
string(216) "['/path/to/home', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages']"
}
return: 0
権限のところで詰まってしまいましたね…
まだまだ修行が必要です.
4. まとめ
PHP の exec 関数を使って実行する際に起こりうるエラーの内詰まったものをいくつか解説しました.
環境などをいじるのは難しいですが,少しずつ慣れていきたいところですね.
PHP で sudo 命令を実行するというのもやってみたので,時間があればまた記事を書きたいと思います.
ぜひお楽しみに