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

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

新規登録して質問してみよう
ただいま回答率
85.48%
PHP

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

Q&A

解決済

5回答

8049閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

PHP

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

0グッド

1クリップ

投稿2017/02/20 13:31

編集2017/02/21 07:41

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

php

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

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

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

多分、実務で使うには

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

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

追記

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

php

1 $number = '(-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)'; // What is a number 2 3 $functions = '(?:abs|atan|ceil|cos|exp|gmp_fact|intval|log(10)?|rand|round|sin|sqrt|tan)'; // Allowed PHP functions 4 $operators = '[\/*\^\+-]'; // Allowed math operators 5 $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))?)+$/

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/20 14:09

そうですね。あちらはもう書き込む内容は無いかと。こちらの質問は独立したものとして捉えていただけると幸いです。
ikedas

2017/02/20 14:51 編集

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

退会済みユーザー

2017/02/20 14:53

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

2017/02/20 14:57

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

回答5

0

ベストアンサー

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

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

PHP

1$sum = '1+2*3-4'; 2$desc = array( 3 0 => array("pipe", "r"), 4 1 => array("pipe", "w"), 5 2 => array("pipe", "w") 6); 7$proc = proc_open("bc", $desc, $pipes); 8fwrite($pipes[0], $sum . PHP_EOL); 9fclose($pipes[0]); 10$result = stream_get_contents($pipes[1]); 11fclose($pipes[1]); 12fclose($pipes[2]); 13proc_close($proc); 14echo $sum . ' = ' . $result . PHP_EOL; 15```(簡単のためにエラーチェックなど無視してます)

投稿2017/02/20 14:39

otn

総合スコア84499

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/20 15:11 編集

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

2017/02/20 15:18

いいえ。文字列を事前検査する必要が無いからです(どんな文字列でも安全=セキュリティホールにならない)。 エラーかどうかは返値じゃなくて$pipes[2]にエラーメッセージがあるかどうかを見る必要があります。 (bcは入力が構文エラーでも返値は0です) 同じbcを使うにしも、 $result = `echo $sum | bc` だと、$sumが外部からの入力値の場合、'$(rm -rf /)'; とかだと困ります。 まあ、正規表現 [0-9+*/-]+ くらいでチェックすれば、evalでも脆弱性にはならないので、proc_openは面倒すぎかもしれません。
退会済みユーザー

退会済みユーザー

2017/02/20 16:03

推し理由、理解できました。 今回の質問内容であれば、正規表現 + eval はそれほど悪い選択肢ではなさそうですね。 proc_open はまだよくわからないので、色々試してみます。 ありがとうございました。
guest

0

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

追記

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

追記

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

PHP

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

追記

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

投稿2017/02/20 23:03

編集2017/02/21 07:27
Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/21 00:10

これは見たことない関数をw ちゃんとマニュアル一読してみるのも有用かもしれないですね。こういったモノが用意されているとは考えてもなかったので、調査しませんでした。 ありがとうございます。 eval はちゃんと正規表現 or 構文解析を用意することができるのであれば、それほど忌諱しなくても良い感じですね。 もし数式評価用の定石的な正規表現があれば、教えてください。 正規表現って、得意でないので、なかなか意図した動作をしてくれません^^;
退会済みユーザー

退会済みユーザー

2017/02/21 06:37

eval のマニュアルへの投稿で、発見した正規表現を質問に追記しました。 今回の質問だと、以下の様なものが定石となるのでしょうか? /^([+-]?((-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[][+\-]?\d+)?)|\s*\((?1)+\)|\((?1)+\))(?:[\/*\^\+-](?1))?)+$/ token_get_all に関してもマニュアルに投稿があったもので理解が進みました。もし回答可能であれば、アドバイスお願いします。
Zuishin

2017/02/21 07:18

拝見しましたが、PHP で「^」って指数表現でしたっけ?
退会済みユーザー

退会済みユーザー

2017/02/21 07:39

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

2017/02/21 07:49

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

退会済みユーザー

2017/02/21 07:58

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

2017/02/21 08:09

正規表現を使う場合、一行で書きたくなりますが、そうするとどうしてもメンテナンス性が落ちます。 このような複雑な正規表現を書く場合、たとえ冗長になろうとも、括弧が過剰になろうとも、分割して書いた方が後の利益になると思います。 例えば今回の場合、対応している関数は pow() のみですが、このような書き方をすることで後日容易に sin(), deg2rad(), 自作関数などを追加することができます。 sin() を追加するには、以下のようにします。 $sin = "(sin\\((?R)\\))"; $func = "({$pow}|{$sin})";
退会済みユーザー

退会済みユーザー

2017/02/21 17:09

ありがとうございます。 別で使う予定の正規表現も悩んでいたんで、ちょっと教えてもらった方法で対応してみようかと思います。 メンテとか考えたくないくらい正規表現の作成、嫌いなんですけどね^^;
guest

0

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

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

(追記)

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

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

投稿2017/02/20 14:59

編集2017/02/21 02:27
ikedas

総合スコア4315

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/20 15:09

回答ありがとうございます。 正規表現によるチェックが「数字と演算記号だけを含むかどうか」だと、'1+2*-*3-4' みたいなものが通ってしまうので、そういったものも考慮した正規表現があったりするのではないかと思ってます。定石的なものをご存知だったりしますか? 多分、そういった正規表現であれば、カッコの使用にも流用が可能なのではないかと考えていますが、ちょっと自分では整理できませんでした。
ikedas

2017/02/20 15:20

数字の間に演算子が挟まれる任意の長さの文字列にマッチする正規表現は、簡単にできるでしょう。任意のレベルの括弧の入れ子をPCREの正規表現で表すこともできると思います。 evalが例外を投げれば構文エラーだとわかりますから、サニタイズという意味では単純な文字種チェックだけの正規表現でもいいとは思います。
退会済みユーザー

退会済みユーザー

2017/02/20 15:53 編集

> 数字の間に演算子が挟まれる任意の長さの文字列にマッチする正規表現は、簡単にできるでしょう。任意のレベルの括弧の入れ子をPCREの正規表現で表すこともできると思います。 /((\(*|)\d*(\)*|)(\+|\-|\*|\/))+\d*/ これイメージと合致しますか?なんか考慮漏れしてる気がするのですけど^^; > evalが例外を投げれば構文エラー evalって構文エラー、うまく処理できなくないですか?try catch でうまくいかず、落ちちゃうんで、イイ方法があれば教えてください。
ikedas

2017/02/20 16:05

派生質問は、ある程度のところで切り上げ、必要なら別質問を立てていただいた方がいいと思います、あとで質問と回答を読むひとのことを考えると、その方が便利でしょう。ご提示の正規表現については、率直に言って表現の設計方針に問題があるので、このコメントだけですますことができるような話ではないと思います。 evalが落ちる件については、調べてみます。何かわかれば回答に追記します。
退会済みユーザー

退会済みユーザー

2017/02/20 16:16

$sum の数式を Syntax Error にさせない定石を探していたんで、派生のつもりはなかったんですけど、数式を認識するための正規表現に関する質問として分けたほうが良いレベルってことですね。 > evalが落ちる件については、調べてみます。何かわかれば回答に追記します。 マニュアル見ると落ちるって書いてあるんで、事前のフィルタしかないかなぁと思ってます。何かわかればお願いします。
ikedas

2017/02/21 02:29

「落ちる」とは書いてないです。「ParseError 例外をスローします」と書いてあります。 もしかすると、実行時エラーが出力されるようになっていて、それを見て「落ちた」と思っているとか。
退会済みユーザー

退会済みユーザー

2017/02/21 06:58

> 「落ちる」とは書いてないです。「ParseError 例外をスローします」と書いてあります。 すみません。私の勘違いです。「致命的なエラー」と混同してしまいました。 > ParseError例外を投げるので、捕捉してやる必要があります。 try catch や set_error_handler を試したのですが、うまく拾えません。 if ( $result === false) かとも思ったのですが、こちらも処理をおこないません。 どのような手法で補足するのが適切でしょうか?
退会済みユーザー

退会済みユーザー

2017/02/21 07:09

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

2017/02/21 07:19

- 質問にPHPのバージョンも明記してないので、一般的なことしか答えようがないです。 - また、質問に新しい話題や条件をあとから追加していくのはやめてもらえませんか。「派生質問は別質問を立ててほしい」と言ったはずですよ。自分の都合に合わないことは言われても無視するんですかね。 回答した人に、その後の変化をフォローする義務なんかないんです。最初の質問に不備があれば、不満足な回答しか得られないのはしかたないでしょう。 ↓ このあとに韜晦のコメントがつく。
退会済みユーザー

退会済みユーザー

2017/02/21 07:51 編集

> 質問にPHPのバージョンも明記してないので、一般的なことしか答えようがないです。 そうですね。ありがとうございます。 > また、質問に新しい話題や条件をあとから追加していくのはやめてもらえませんか。「派生質問は別質問を立ててほしい」と言ったはずですよ。自分の都合に合わないことは言われても無視するんですかね。 正規表現の件ですよね?私としては、「$sum の数式を Syntax Error にさせない定石を探していたんで、派生のつもりはない」ため、定石があるのであれば、こちらでクローズしたいと思ってます。ないのであれば、数式を判断するための正規表現の作成の仕方について、改めて質問し直すか、勉強しなおそかと考えています。
guest

0

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

投稿2017/02/20 14:10

takasima20

総合スコア7458

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/20 14:21

ありがとうございます。 > 一般的にどうかは分かりませんが、個人的にはevalかなあ。 ということは、与える変数を厳密に精査するってことですかね? そのあたりに何か定石的なものはあるのでしょうか? フィルタの仕方とかあれば、ぜひ教えてください。
takasima20

2017/02/21 12:42

他の方が書かれているように、自分で組み立てた数式なら ことさらチェックする必要はないことでしょう。 そうでない場合は… どの程度の数式が入ってくるかにもよりますし、 どの程度コストをかけてチェックするかって話しだいかなあ。 以下、参考までに。 PHP_LexerGeneratorとPHP_ParserGeneratorを利用して PHPで独自の言語を実装する方法 https://codezine.jp/article/detail/3319 ↑ 簡易電卓を、PHPの字句解析器と構文解析器を使って つくってるページです。 あ、読むのに会員登録が必要なようです。(^_^;
退会済みユーザー

退会済みユーザー

2017/02/21 17:07

> どの程度の数式が入ってくるかにもよりますし、 どの程度コストをかけてチェックするかって話しだいかなあ 特に一般的なものはないようなので、個別に検討するしかないみたいですね。 ご紹介いただいた記事、興味はあるのですが、codezine は全面広告が嫌いで、会員登録に二の足踏んでしまいます^^; 公開されたページには目を通したんですけど、面白そうなんですよね。。。悩ましいw ありがとうございました。
guest

0

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

投稿2017/02/21 00:42

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/21 01:07

EOS ってライブラリ名、すごいですねw 今、中身見始めたところですが、大変参考になります。 コメントもかなり丁寧に入っているので、多分理解できそうです。 構文解析は、方程式の解析を参考にすればよかったんですね。 その発想がありませんでした。 ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問