質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.35%

  • PHP

    21256questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

変数に入った数式の結果を得る方法

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,103

te2ji

score 11832

PHP で、変数に入った数式の結果を得る方法を探しています。

$sum = '1+2*3-4';
$result= eval("return {$sum};");
echo $sum . ' = ' . $result . PHP_EOL;

のようにevalを利用するのが一般的なのでしょうか?
*$sumはサンプルです。変化するものと思って下さい。

変数に入った数式の結果を得る方法を探している中で、eval の使用を回避するようにというアドバイスを幾つかの記事で確認しました。

多分、実務で使うには

  • evalを使用しない他の良い方法がある。
  • $sum の数式を Syntax Error にさせない定石がある。

あたりがあるのではないかと思うのですが、良い方法があれば教えてください。
よろしくお願いします。

 追記

eval-PHPに数式を正規表現で拾う以下の投稿を見つけました。

    $number = '(-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)'; // What is a number

    $functions = '(?:abs|atan|ceil|cos|exp|gmp_fact|intval|log(10)?|rand|round|sin|sqrt|tan)'; // Allowed PHP functions
    $operators = '[\/*\^\+-]'; // Allowed math operators
    $regexp = '/^([+-]?('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?1))?)+$/'; 


結果としては以下となるので、これを加工するのが定石になるんでしょうか?

/^([+-]?((-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)|(?:abs|atan|ceil|cos|exp|gmp_fact|intval|log(10)?|rand|round|sin|sqrt|tan)\s*\((?1)+\)|\((?1)+\))(?:[\/*\^\+-](?1))?)+$/


今回だとこんな感じ?

/^([+-]?((-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[][+\-]?\d+)?)|\s*\((?1)+\)|\((?1)+\))(?:[\/*\+-](?1))?)+$/


**'\^'を削除しました。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • ikedas

    2017/02/20 23:48 編集

    なるほど。では念のためですが、当質問ページで得られた回答を、どこかでさも自分が思いついたかのように受け売りすることはない、と誓えますか。そういうことするつもりが少しでもあるのなら、言っていただければ回答はしないようにします。 もともと情報を共有するために回答するのですから普通はそれでもいいのですが、今までの質問者さんの実績だと、劣化コピーをばら撒かれる可能性もあるので、慎重になっています。

    キャンセル

  • te2ji

    2017/02/20 23:53

    自分が思いついた手段として語るつもりはないですけど、別に誓うつもりもないです^^;これは何のやり取りなんでしょうか。。。

    キャンセル

  • ikedas

    2017/02/20 23:57

    わかりました。当面は、そういうつもりはないと理解しておくことにします。

    キャンセル

回答 5

checkベストアンサー

+3

入力したデータをそのままevalするのは避けます。
プログラム内で生成したデータであればevalしても良いでしょう。

入力したデータをevalするなら、意図した形式であるか十分なチェックが必要。
そこまでやるならevalを使わず、データを構文解析して数式の処理をすればいいのですが、これはちょっと面倒。
楽するためには、計算専用の外部コマンドbcに渡して計算してもらうとか。

$sum = '1+2*3-4';
$desc = array(
   0 => array("pipe", "r"),
   1 => array("pipe", "w"),
   2 => array("pipe", "w")
);
$proc = proc_open("bc", $desc, $pipes);
fwrite($pipes[0], $sum . PHP_EOL);
fclose($pipes[0]);
$result = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($proc);
echo $sum . ' = ' . $result . PHP_EOL;

(簡単のためにエラーチェックなど無視してます)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/21 00:00 編集

    ありがとうございます。

    proc_open は使用したことがなかったので、確認してみます。
    proc_open + bc を eval より推していただいている理由としては、Error 対応を返り値チェックレベルで済ませることが可能であるから。という理解で正しいでしょうか?

    キャンセル

  • 2017/02/21 00:18

    いいえ。文字列を事前検査する必要が無いからです(どんな文字列でも安全=セキュリティホールにならない)。
    エラーかどうかは返値じゃなくて$pipes[2]にエラーメッセージがあるかどうかを見る必要があります。
    (bcは入力が構文エラーでも返値は0です)

    同じbcを使うにしも、
    $result = `echo $sum | bc`
    だと、$sumが外部からの入力値の場合、'$(rm -rf /)'; とかだと困ります。

    まあ、正規表現 [0-9+*/-]+ くらいでチェックすれば、evalでも脆弱性にはならないので、proc_openは面倒すぎかもしれません。

    キャンセル

  • 2017/02/21 01:03

    推し理由、理解できました。

    今回の質問内容であれば、正規表現 + eval はそれほど悪い選択肢ではなさそうですね。

    proc_open はまだよくわからないので、色々試してみます。
    ありがとうございました。

    キャンセル

+2

一般的にどうかは分かりませんが、個人的にはevalかなあ。
他の方法というと、外部(例えば exprやbcコマンドとか)に
丸投げする方法を思いつきますが、どうでしょねえ?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/20 23:21

    ありがとうございます。

    > 一般的にどうかは分かりませんが、個人的にはevalかなあ。

    ということは、与える変数を厳密に精査するってことですかね?
    そのあたりに何か定石的なものはあるのでしょうか?
    フィルタの仕方とかあれば、ぜひ教えてください。

    キャンセル

  • 2017/02/21 21:42

    他の方が書かれているように、自分で組み立てた数式なら
    ことさらチェックする必要はないことでしょう。
    そうでない場合は…
    どの程度の数式が入ってくるかにもよりますし、
    どの程度コストをかけてチェックするかって話しだいかなあ。
    以下、参考までに。
    PHP_LexerGeneratorとPHP_ParserGeneratorを利用して
    PHPで独自の言語を実装する方法
    https://codezine.jp/article/detail/3319

    簡易電卓を、PHPの字句解析器と構文解析器を使って
    つくってるページです。
    あ、読むのに会員登録が必要なようです。(^_^;

    キャンセル

  • 2017/02/22 02:07

    > どの程度の数式が入ってくるかにもよりますし、
    どの程度コストをかけてチェックするかって話しだいかなあ

    特に一般的なものはないようなので、個別に検討するしかないみたいですね。

    ご紹介いただいた記事、興味はあるのですが、codezine は全面広告が嫌いで、会員登録に二の足踏んでしまいます^^;
    公開されたページには目を通したんですけど、面白そうなんですよね。。。悩ましいw

    ありがとうございました。

    キャンセル

+2

token_get_all を使ってトークンを求め、そのうち数式以外のものが入っていたらエラーを出すことでセキュリティー問題は解決可能だと思います。
さらに php_check_syntax を使えばシンタックスエラーをチェックできます。

追記

失礼しました。php_check_syntax は廃止されていますね。私なら正規表現を使うか独自に構文解析を作ります。数式に限定されているので、得られたトークンをもとに構文解析したとしてもそれほど大げさなことにはなりそうもない気もしますが、正規表現の方が楽かもしれませんね。

追記

定石はよく知りませんが、試しに作ってみました。
四則演算、剰余、累乗に対応しています。
$input に適当な文字列を入れて試してみてください。

<?php
$number = "(\\d+(\\.\\d+)?)";
$pow = "(pow\\((?R),(?R)\\))";
$func = "{$pow}";
$factor = "(-?({$number}|\(\\s*(?R)\\s*\)|{$func}))";
$expression = "(?>\\s*{$factor}(\\s*[\\+\\-\\*\\/\\%]\\s*{$factor})*\\s*)";
$pattern = "/{$expression}/";
$input = "-12.5 + ((37+2) * -5) ++ pow(2, 3)";
if (preg_match($pattern, $input, $match) == 1 && $match[0] === $input)
{
    echo "success";
}
else
{
    echo "failed";
}

追記

ちょっと修正を入れました。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/21 09:10

    これは見たことない関数をw
    ちゃんとマニュアル一読してみるのも有用かもしれないですね。こういったモノが用意されているとは考えてもなかったので、調査しませんでした。
    ありがとうございます。

    eval はちゃんと正規表現 or 構文解析を用意することができるのであれば、それほど忌諱しなくても良い感じですね。

    もし数式評価用の定石的な正規表現があれば、教えてください。
    正規表現って、得意でないので、なかなか意図した動作をしてくれません^^;

    キャンセル

  • 2017/02/21 15:37

    eval のマニュアルへの投稿で、発見した正規表現を質問に追記しました。
    今回の質問だと、以下の様なものが定石となるのでしょうか?
    /^([+-]?((-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[][+\-]?\d+)?)|\s*\((?1)+\)|\((?1)+\))(?:[\/*\^\+-](?1))?)+$/

    token_get_all に関してもマニュアルに投稿があったもので理解が進みました。もし回答可能であれば、アドバイスお願いします。

    キャンセル

  • 2017/02/21 16:18

    拝見しましたが、PHP で「^」って指数表現でしたっけ?

    キャンセル

  • 2017/02/21 16:39

    違いますね^^;ビット演算子かな?削除しておきます。
    空白とか全く考慮してませんでした。
    ありがとうございます!

    キャンセル

  • 2017/02/21 16:49

    提示していただいた正規表現に 2++3 を適用したところ、通りました。eval では通らないと思います。

    キャンセル

  • 2017/02/21 16:58

    指摘ありがとうございます。
    ちょっと使えなさそうですね。ということは定石的なものは特に無く、自前で皆さん実装している感じなんでしょうね。。。
    正規表現学習してきますorz

    キャンセル

  • 2017/02/21 17:09

    正規表現を使う場合、一行で書きたくなりますが、そうするとどうしてもメンテナンス性が落ちます。
    このような複雑な正規表現を書く場合、たとえ冗長になろうとも、括弧が過剰になろうとも、分割して書いた方が後の利益になると思います。

    例えば今回の場合、対応している関数は pow() のみですが、このような書き方をすることで後日容易に sin(), deg2rad(), 自作関数などを追加することができます。
    sin() を追加するには、以下のようにします。

    $sin = "(sin\\((?R)\\))";
    $func = "({$pow}|{$sin})";

    キャンセル

  • 2017/02/22 02:09

    ありがとうございます。

    別で使う予定の正規表現も悩んでいたんで、ちょっと教えてもらった方法で対応してみようかと思います。

    メンテとか考えたくないくらい正規表現の作成、嫌いなんですけどね^^;

    キャンセル

+1

evalを使えば簡単にできますが、入力を信用できない (ユーザに文字列を入力させるなど) という条件なら、入力のサニタイズが必要です。

本格的な構文解析をしてもいいのですが、整数の四則演算のみ (括弧は使わない) という条件だとするなら、正規表現によるチェック (数字と演算記号だけを含むかどうか) で用は足りるでしょう。

(追記)

PHP7以降では、evalは構文エラーとなる引数を与えるとParseError例外を投げるので、捕捉してやる必要があります。なお、手元で確認したかぎり、引数に与えたコードを実行中にArithmeticError (ゼロ除算など) が発生しても例外を投げることはなく、NULLを返すようです (確認バージョン: PHP 7.0.8)。

PHP5では、上のような場合はいずれもFALSEを返します。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/21 00:09

    回答ありがとうございます。

    正規表現によるチェックが「数字と演算記号だけを含むかどうか」だと、'1+2*-*3-4' みたいなものが通ってしまうので、そういったものも考慮した正規表現があったりするのではないかと思ってます。定石的なものをご存知だったりしますか?

    多分、そういった正規表現であれば、カッコの使用にも流用が可能なのではないかと考えていますが、ちょっと自分では整理できませんでした。

    キャンセル

  • 2017/02/21 00:20

    数字の間に演算子が挟まれる任意の長さの文字列にマッチする正規表現は、簡単にできるでしょう。任意のレベルの括弧の入れ子をPCREの正規表現で表すこともできると思います。

    evalが例外を投げれば構文エラーだとわかりますから、サニタイズという意味では単純な文字種チェックだけの正規表現でもいいとは思います。

    キャンセル

  • 2017/02/21 00:52 編集

    > 数字の間に演算子が挟まれる任意の長さの文字列にマッチする正規表現は、簡単にできるでしょう。任意のレベルの括弧の入れ子をPCREの正規表現で表すこともできると思います。

    /((\(*|)\d*(\)*|)(\+|\-|\*|\/))+\d*/
    これイメージと合致しますか?なんか考慮漏れしてる気がするのですけど^^;

    > evalが例外を投げれば構文エラー
    evalって構文エラー、うまく処理できなくないですか?try catch でうまくいかず、落ちちゃうんで、イイ方法があれば教えてください。

    キャンセル

  • 2017/02/21 01:05

    派生質問は、ある程度のところで切り上げ、必要なら別質問を立てていただいた方がいいと思います、あとで質問と回答を読むひとのことを考えると、その方が便利でしょう。ご提示の正規表現については、率直に言って表現の設計方針に問題があるので、このコメントだけですますことができるような話ではないと思います。

    evalが落ちる件については、調べてみます。何かわかれば回答に追記します。

    キャンセル

  • 2017/02/21 01:16

    $sum の数式を Syntax Error にさせない定石を探していたんで、派生のつもりはなかったんですけど、数式を認識するための正規表現に関する質問として分けたほうが良いレベルってことですね。

    > evalが落ちる件については、調べてみます。何かわかれば回答に追記します。
    マニュアル見ると落ちるって書いてあるんで、事前のフィルタしかないかなぁと思ってます。何かわかればお願いします。

    キャンセル

  • 2017/02/21 11:29

    「落ちる」とは書いてないです。「ParseError 例外をスローします」と書いてあります。

    もしかすると、実行時エラーが出力されるようになっていて、それを見て「落ちた」と思っているとか。

    キャンセル

  • 2017/02/21 15:58

    > 「落ちる」とは書いてないです。「ParseError 例外をスローします」と書いてあります。

    すみません。私の勘違いです。「致命的なエラー」と混同してしまいました。

    > ParseError例外を投げるので、捕捉してやる必要があります。

    try catch や set_error_handler を試したのですが、うまく拾えません。
    if ( $result === false) かとも思ったのですが、こちらも処理をおこないません。
    どのような手法で補足するのが適切でしょうか?

    キャンセル

  • 2017/02/21 16:09

    try catch の Error で拾えました^^;
    お騒がせしました。。。

    キャンセル

  • 2017/02/21 16:19

    - 質問にPHPのバージョンも明記してないので、一般的なことしか答えようがないです。
    - また、質問に新しい話題や条件をあとから追加していくのはやめてもらえませんか。「派生質問は別質問を立ててほしい」と言ったはずですよ。自分の都合に合わないことは言われても無視するんですかね。

    回答した人に、その後の変化をフォローする義務なんかないんです。最初の質問に不備があれば、不満足な回答しか得られないのはしかたないでしょう。

    ↓ このあとに韜晦のコメントがつく。

    キャンセル

  • 2017/02/21 16:50 編集

    > 質問にPHPのバージョンも明記してないので、一般的なことしか答えようがないです。

    そうですね。ありがとうございます。

    > また、質問に新しい話題や条件をあとから追加していくのはやめてもらえませんか。「派生質問は別質問を立ててほしい」と言ったはずですよ。自分の都合に合わないことは言われても無視するんですかね。

    正規表現の件ですよね?私としては、「$sum の数式を Syntax Error にさせない定石を探していたんで、派生のつもりはない」ため、定石があるのであれば、こちらでクローズしたいと思ってます。ないのであれば、数式を判断するための正規表現の作成の仕方について、改めて質問し直すか、勉強しなおそかと考えています。

    キャンセル

+1

過去に別な方が同じような質問をされていますが、そこではEquation Operating Systemというライブラリが提示されていました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/21 10:07

    EOS ってライブラリ名、すごいですねw

    今、中身見始めたところですが、大変参考になります。
    コメントもかなり丁寧に入っているので、多分理解できそうです。

    構文解析は、方程式の解析を参考にすればよかったんですね。
    その発想がありませんでした。

    ありがとうございました!

    キャンセル

同じタグがついた質問を見る

  • PHP

    21256questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。