この文書は 2013 年 3 月 8 日に最後にレビューされました。最終変更は 2013 年 3 月 8 日でした。
これは私、Alex Cabal によって管理されています。私は長い間 PHP コードを書いてきました。現在は、本格的なライター向けのオンライン執筆グループである Scribophile、フリーランサー向けのシンプルなオンライン執筆フォルダーである Writerfolio、イラスト出版物である Standard Ebooks、パブリック ドメインの電子書籍である Standard Ebooks を運営しています。デジタル著作権のない書籍。 時々、興味のあるプロジェクトやクライアントを自由に追求することがあります。
私が何かお手伝いできると思われる場合、またはこの記事について提案や修正がある場合は、私に連絡してください。
PHP は、何年にもわたって自分の中でひねったり、曲げたり、伸ばしたり、叩いたりする必要がある複雑な言語です。それは矛盾しており、時にはバグに満ちています。各バージョンには独自の機能、欠陥、癖があり、どのバージョンにどの問題があるかを追跡するのは困難な場合があります。それが時として大きな怒りを生む理由を想像するのは難しくありません。
それにもかかわらず、それは今日ウェブ上で最も人気のある言語です。長い歴史があるため、パスワード ハッシュ (1 回限りの暗号化) やデータベース アクセスなどの基本的なことを行う方法に関するチュートリアルが数多く見つかります。問題は、5 つのチュートリアルから、何かを行うための 5 つのまったく異なる方法が見つかる可能性が高いことです。どの方法が「正しい」方法ですか?他の方法に欠陥や予期せぬ問題はありますか?それを理解するのは非常に難しく、正しい答えを見つけようとしてウェブ中をクリックすることになります。
これが、新人の PHP プログラマーが醜い、時代遅れ、または安全でないコードで批判される理由の 1 つです。最初の Google 検索結果が 5 年前のメソッドを教える 4 年前の記事だった場合、彼らはその状況を変えずにはいられません。
この記事はまさにそれを試みようとしています。これは、PHP でよくある紛らわしい問題に対処するためのベスト プラクティスと考えられる一連の基本的な操作ヒントをまとめることを目的としています。 PHP に複数の紛らわしいメソッドを含む低レベルのタスクがあった場合、それはここに属します。
これは、PHP プログラマーが遭遇する可能性のある一般的な低レベルのタスクに直面した場合に最適なアプローチを示す推奨ガイドです。PHP には多くの選択肢があるため、これらのタスクを理解するのは容易ではありません。例: データベースへの接続は、考えられる多くの PHP ソリューションで一般的なタスクですが、すべてが適切であるわけではありません。そのため、この問題はこの記事に含まれています。
これは一連の短いガイド付きソリューションです。基本的な構成でサンプルを実行するための措置を講じる必要があります。また、自分に適したものを見つけるために独自の調査を行う必要があります。
これは、私たちが理解している PHP の最先端技術を指します。ただし、これは、古いバージョンの PHP を使用している場合、これらのソリューションを実装するために必要な機能の一部が備わっていない可能性があることも意味します。
これは生きたドキュメントであり、PHP が進化し続けるのに合わせて更新し続けるように努めます。
この記事は PHP チュートリアルではありません。基本と文法は別の場所で学ぶ必要があります。
これは、Cookie ストレージ、キャッシュ、コーディング スタイル、ドキュメントなどの一般的な Web アプリケーションの問題についてのガイドではありません。
これはセキュリティウィザードではありません。セキュリティ関連の問題に関しては、PHP アプリケーションを強化する方法について独自に調査する必要があります。特に、ここで挙げた提案は実装する前に注意深く検討する必要があります。コードに対する責任はあなたにあります。
特定のコーディング スタイル、パターン、フレームワークを推奨するものではありません。
ユーザー登録やシステムへのログインなどの高レベルのタスクを実行する方法に関する特定のアプローチを支持するものではありません。 PHP の長い歴史は混乱を招き、または不明瞭になる可能性があるため、この記事は厳密に低レベルのタスクを対象としています。
それは究極の解決策ではありませんし、唯一の解決策でもありません。以下で説明する方法の中には、実際の状況に最適ではないものもあります。また、同じ目的を達成できるさまざまな方法が多数あります。特に、高負荷の Web アプリケーションでは、これらの問題に対するよりステルスなソリューションの恩恵を受ける可能性があります。
PHP は、インターネットの世界で 100 年前のカメのようなものです。その殻には、豊かで不可解で粗野な歴史が刻まれています。共有ホスティング環境では、その構成によってできることが制限される場合があります。
健全性を保つには、PHP の 1 つのバージョンだけに焦点を当てる必要があります。 2013年4月30日現在のバージョンは、Suhosinパッチを適用したPHP5.3.10-1ubuntu3.6です。 apt-get を使用して Ubuntu 12.04 LTS サーバーから PHP をインストールする場合、これが取得されるバージョンです。言い換えれば、多くの人がデフォルトですでに賢く使用しているということです。
この記事の解決策は、別のバージョンまたは古いバージョンの PHP でも機能する場合があります。この場合、これらの古いバージョンの微妙なバグやセキュリティ上の問題の影響を調査するのはあなた次第です。
phpass 0.3でテスト済み。
ハッシュは、ユーザーのパスワードをデータベースに保存する前に保護する標準的な方法です。 MD5 や SHA1 などの一般的なハッシュ アルゴリズムの多くは、ハッカーがこれらのハッシュ アルゴリズムを使用するとパスワードを簡単に解読できるため、パスワードの保存には安全ではありません。
パスワードをハッシュする最も安全な方法は、bcrypt アルゴリズムを使用することです。オープンソースの phpass ライブラリは、この機能を使いやすいクラスで提供します。
01 |
02 | // 包含phpass库 |
03 | require_once('phpass-0.3/PasswordHash.php'); |
04 |
05 | // 初始化散列器为不可移植(这样更安全) |
06 | $hasher = new PasswordHash(8, false); |
07 |
08 | // 计算密码哈希值。$hashedPassword 将会是一长为60个字符的字符串. |
09 | $hashedPassword = $hasher->HashPassword('my super cool password'); |
10 |
11 | // 你现在可以安全地保存$hashedPassword到数据库中! |
12 |
13 | // 通过比较用户输入内容(产生的哈希值)和我们之前计算出的哈希值,来判断用户是否输入了正确的密码 |
14 | $hasher->CheckPassword('the wrong password', $hashedPassword); // 返回假 |
15 |
16 | $hasher->CheckPassword('my super cool password', $hashedPassword); // 返回真 |
17 | ?> |
パスワードを安全に保存する方法
PHP で MySQL データベースに接続するには、さまざまな方法があります。 PDO (PHP Data Objects) は、その中で最新かつ最も堅牢です。 PDO は、さまざまな種類のデータベースに対して一貫したインターフェイスを使用し、オブジェクト指向のアプローチを採用し、新しいデータベースによって提供されるより多くの機能をサポートします。
SQL インジェクション攻撃を防ぐには、PDO プリペアド ステートメント機能を使用する必要があります。 bindValue() 関数を使用して、SQL が一次 SQL インジェクション攻撃から安全であることを確認します (ただし、これは 100% 確実というわけではありません。詳細については「参考文献」を参照してください)。以前は、これはいくつかの「魔法の引用」関数を複雑に組み合わせることによってのみ実現できました。 PDO を使用すると、こうした厄介な問題はすべて不要になります。
01 |
02 | try{ |
03 | // Create a new connection. |
04 | // You'll probably want to replace hostname with localhost in the first parameter. |
05 | // The PDO options we pass do the following: |
06 | // PDO::ATTR_ERRMODE enables exceptions for errors. This is optional but can be handy. |
07 | // PDO::ATTR_PERSISTENT disables persistent connections, which can cause concurrency issues in certain cases. See "Gotchas". |
08 | // PDO::MYSQL_ATTR_INIT_COMMAND alerts the connection that we'll be passing UTF-8 data. This may not be required depending on your configuration, but it'll save you headaches down the road if you're trying to store Unicode strings in your database. See "Gotchas". |
09 | $link = new PDO( 'mysql:host=your-hostname;dbname=your-db', |
10 | 'your-username', |
11 | 'your-password', |
12 | array( |
13 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
14 | PDO::ATTR_PERSISTENT => false, |
15 | PDO::MYSQL_ATTR_INIT_COMMAND => 'set names utf8mb4' |
16 | ) |
17 | ); |
18 |
19 | $handle = $link->prepare('select Username from Users where UserId = ? or Username = ? limit ?'); |
20 |
21 | // PHP bug: if you don't specify PDO::PARAM_INT, PDO may enclose the argument in quotes. This can mess up some MySQL queries that don't expect integers to be quoted. |
22 | // See: https://bugs.php.net/bug.php?id=44639 |
23 | // If you're not sure whether the value you're passing is an integer, use the is_int() function. |
24 | $handle->bindValue(1, 100, PDO::PARAM_INT); |
25 | $handle->bindValue(2, 'Bilbo Baggins'); |
26 | $handle->bindValue(3, 5, PDO::PARAM_INT); |
27 |
28 | $handle->execute(); |
29 |
30 | // Using the fetchAll() method might be too resource-heavy if you're selecting a truly massive amount of rows. |
31 | // If that's the case, you can use the fetch() method and loop through each result row one by one. |
32 | // You can also return arrays and other things instead of objects. See the PDO documentation for details. |
33 | $result = $handle->fetchAll(PDO::FETCH_OBJ); |
34 |
35 | foreach($result as $row){ |
36 | print($row->Username); |
37 | } |
38 | } |
39 | catch(PDOException $ex){ |
40 | print($ex->getMessage()); |
41 | } |
42 | ?> |
整数変数をバインドする場合、PDO::PARAM_INT パラメーターを渡さないと、PDO がデータを引用符で囲む可能性があります。これにより、特定の MySQL クエリが破損する可能性があります。このバグレポートを参照してください。
最初のクエリとして「set names utf8mb4」を使用しないと、構成によっては Unicode データがデータベースに誤って保存される可能性があります。 Unicode でエンコードされたデータに問題がないと確信できる場合は、これを無視してかまいません。
永続的な接続を有効にすると、同時実行に関連した奇妙な問題が発生する可能性があります。これは PHP の問題ではなく、アプリケーション レベルの問題です。結果を注意深く考慮する限り、永続的な接続は通常は安全です。 Stack Overflow でこの質問を確認してください。
`set names utf8mb4` を使用する場合でも、実際のデータベース テーブルが utf8mb4 文字セットを使用していることを確認する必要があります。
複数の SQL ステートメントを 1 回の use() 呼び出しで実行できます。セミコロンを使用してステートメントを区切るだけですが、このバグはこのドキュメントが書かれている PHP バージョンでは修正されていないことに注意してください。
さらに読む
PHP コードのブロックを定義するには、、= ?>、>、<% %> など、いくつかの方法があります。短いメソッドの方が入力は簡単ですが、すべての PHP サーバーで動作することが保証されているのは だけです。構成を制御できないサーバーに PHP プログラムをデプロイする予定がある場合は、常に を使用する必要があります。
PHP ランタイム環境の構成を制御する十分な権限がある場合は、短いタグを使用する方が自然に便利であることがわかります。ただし、> は実際には ASP スタイルであることに注意してください。
どちらを選択する場合でも、一貫性を保つようにしてください。トラップ
この問題を解決する正しい方法は、自動ロード関数に一意の名前を付けて、それを spl_autoload_register() 関数に登録することです。この関数を使用すると、他のコードに含まれる __autoload() 関数をステップ実行しないように、複数の __autoload() 関数を定義できます。
例:
01 |
02 | // 首先,定义你的自动载入的函数 |
03 | function MyAutoload($className){ |
04 | include_once($className . '.php'); |
05 | } |
06 |
07 | // 然后注册它. |
09 |
10 | // 试试让它工作! |
11 | // 因为我们没包含一个定义有MyClass的文件,所以自动加载器会介入并包含MyClass.php. |
12 | // 对本例来说,假定在MyClass.php文件中定义了MyClass类. |
13 | $var = new MyClass(); |
14 | ?> |
スタック オーバーフロー: 効率的な PHP の自動読み込みと命名戦略
文字列を定義するときに一重引用符を使用するか二重引用符を使用するかについては、多くのことが書かれています。一重引用符で囲まれた文字列は解析されないため、文字列に入力した内容はそのまま表示されます。二重引用符で囲まれた文字列が解析され、その文字列内の PHP 変数が評価されます。また、エスケープ文字 (改行 n やタブ t など) についても、一重引用符と二重引用符の違いは同様です。
二重引用符で囲まれた文字列は実行時に解析されるため、理論上、可能であれば一重引用符を使用すると、PHP で一重引用符で囲まれた文字列をさらに解析する必要がなくなるため、パフォーマンスが向上します。これは、特定の規模のアプリケーションには当てはまりますが、一般的な実際のアプリケーションでは、効率の差は非常に小さいため、実際には問題になりません。したがって、一般的なアプリケーションでは、どちらを選択しても問題はありません。将来的には二重引用符に変更してください)。非常に高負荷のアプリケーションでは、多少の影響がある可能性があります。どの方法を使用するかはアプリケーションのニーズによって異なりますが、どの方法を選択しても一貫性を保つ必要があります。
スタック オーバーフロー: PHP では単一引用符と二重引用符にパフォーマンス上の利点がありますか?
従来、PHP では、define() 関数を使用して定数を定義していました。しかし、一部の意見によると、PHP には const キーワードを使用して定数を宣言する機能も追加されました。では、定数を定義するときはどれを使用すべきでしょうか?
答えは、これら 2 つの方法のわずかな違いにあります。
01 |
02 | // 来看看这两种方法如何处理名称空间 |
03 | namespace MiddleEarthCreaturesDwarves; |
04 | const GIMLI_ID = 1; |
05 | define('MiddleEarthCreaturesElvesLEGOLAS_ID', 2); |
06 |
07 | echo(MiddleEarthCreaturesDwarvesGIMLI_ID); // 1 |
08 | echo(MiddleEarthCreaturesElvesLEGOLAS_ID); // 2; 注意,对此常量,我们是用define()定义的,但也能识别空间。 |
09 |
10 | // 现在让我们来声明一些值是位移运算结果的常数来代表进入魔多的方式Mordor. |
11 | define('TRANSPORT_METHOD_SNEAKING', 1 << 0); // OK! |
12 | const TRANSPORT_METHOD_WALKING = 1 << 1; //编译错误! const 不允许使用表达式作为值 |
13 |
14 | // 接下来, 条件常量。 |
15 | define('HOBBITS_FRODO_ID', 1); |
16 |
17 | if($isGoingToMordor){ |
18 | define('TRANSPORT_METHOD', TRANSPORT_METHOD_SNEAKING); // OK! |
19 | const PARTY_LEADER_ID = HOBBITS_FRODO_ID // 编译错误: const 不能用于 if 块中 |
20 | } |
21 |
22 | // 最后, 类常量 |
23 | class OneRing{ |
24 | const MELTING_POINT_DEGREES = 1000000; // OK! |
25 | define('SHOW_ELVISH_DEGREES', 200); // 编译错误: 在类内不能使用define() |
26 | } |
27 | ?> |
最終的にはdefine()の方が柔軟性が高いため、クラス定数が絶対に必要な場合を除き、問題を回避するのはあなたの選択です。 const を使用すると、より読みやすいコードが生成されますが、柔軟性が犠牲になります。
どちらを使用する場合でも、一貫性を保ってください。
スタック オーバーフロー:define() と variable
PHP の標準インストール環境では、各 PHP スクリプトはオペコード (バイトコード) ファイルにコンパイルされ、アクセスされるたびに実行されます。まったく同じスクリプトを何度もコンパイルするのに時間を費やすと、大規模な Web サイトでは必ずパフォーマンスの問題が発生します。
解決策はオペコードのキャッシュです。オペコード キャッシュは、サーバーがスクリプトを何度もコンパイルする無駄な時間を費やす必要がないように、各スクリプトのコンパイル結果を記憶するシステムです。また、通常、スクリプトが変更されたことを検出して再コンパイルするのに十分な機能を備えているため、PHP ソース ファイルを更新するときに手動でキャッシュをクリアする必要がありません。
利用可能な PHP オペコード キャッシュ システムはいくつかありますが、注目に値するのは eaccelerator、xcache、APC であり、PHP プロジェクト チームによって正式にサポートされており、最もアクティブでインストールが簡単です。また、オプションで memcached のような永続的なキーと値のストアも提供します。これらの理由から、これを使用する必要があります。
APC は、ターミナルで次のコマンドを実行することで Ubuntu 12.04 にインストールできます:
sudo apt-get install php-apc
追加の構成は必要ありません。
APC は、memcached のような機能も提供します。これはスクリプトにとっても明らかです。 memcached を使用する場合と比較した最大の利点は、APC が PHP カーネルに統合されているため、可動部分をサーバーに保存する必要がなく、PHP 開発者が積極的に取り組んでいることです。一方、APC は分散キャッシュではありません。この機能が必要な場合は、memcached を使用する必要があります。
01 |
02 | // Store some values in the APC cache. We can optionally pass a time-to-live, but in this example the values will live forever until they're garbage-collected by APC. |
03 | apc_store('username-1532', 'Frodo Baggins'); |
04 | apc_store('username-958', 'Aragorn'); |
05 | apc_store('username-6389', 'Gandalf'); |
06 |
07 | // After storing these values, any PHP script can access them, no matter when it's run! |
08 | $value = apc_fetch('username-958', $success); |
09 | if($success === true) |
10 | print($value); // Aragorn |
11 |
12 | $value = apc_fetch('username-1', $success); // $success will be set to boolean false, because this key doesn't exist. |
13 | if($success !== true) // Note the !==, this checks for true boolean false, not "falsey" values like 0 or empty string. |
14 | print('Key not found'); |
15 |
16 | apc_delete('username-958'); // This key will no longer be available. |
17 | ?> |
PHP マニュアル: APC
キャッシュ システムにより、多くの場合、アプリのパフォーマンスが向上します。 Memcached は人気のある選択肢であり、PHP を含む多くの言語と互換性があります。
ただし、PHP スクリプトから Memcached サーバーにアクセスする場合は、Memcache と Memcached という 2 つの異なる、愚かな名前のクライアント ライブラリ オプションがあります。これらは異なるライブラリですが、名前はほぼ同じで、どちらも Memcached インスタンスにアクセスするために使用されます。
Memcached ライブラリが Memcached プロトコルを実装する最良の方法であることは事実によって証明されています。これには、Memcache ライブラリにはない便利な機能がいくつか含まれており、最も活発に開発されているようです。
ただし、一連の分散サーバーから Memcached インスタンスにアクセスする必要がない場合は、代わりに APC を使用してください。 APC は PHP プロジェクトによってサポートされており、Memcached に似た多くの機能を備えています。さらに驚くべきことに、PHP スクリプトのパフォーマンスを向上させるオペコード キャッシュがあることです。
Memcached サーバーをインストールした後、Memcached クライアント ライブラリをインストールする必要があります。このライブラリがないと、PHP スクリプトは Memcached サーバーと通信できません。
ターミナルで次のコマンドを実行して、Memcached クライアント ライブラリをインストールできます:
sudo apt-get install php5-memcached
Memcached の代替として APC を使用する方法の詳細については、オペコード キャッシュに関するエントリを参照してください。
スタック オーバーフロー: Memcached と APC、どちらを選択すべきですか?
PHP では、正規表現を使用する 2 つの異なる方法があります。PCRE (Perl 互換、preg_*) 関数と POSIX (POSIX 拡張、ereg_*) 関数です。
関数の各ファミリーは、わずかに異なるスタイルの正規表現を使用します。幸いなことに、POSIX 関数は PHP 5.3.0 から非推奨になりました。このため、新しいコードでは POSIX 関数を使用しないでください。これは常に Warrior PRCE 関数、つまり preg_* 関数です。
PHP 正規表現の入門
PHP を提供するように Web サーバーを構成するには、いくつかの方法があります。従来の (そして悪い) 方法は、Apache の mod_php を使用することです。 Mod_php は PHP を Apache 自体にバインドしますが、Apache はこのモジュールの機能を管理するのに非常に不十分です。大量のトラフィックに遭遇すると、深刻なメモリ問題に悩まされることになります。
mod_fastcgi と mod_fcgid という 2 つの新しいオプションがすぐに人気になりました。どちらも一定数の PHP 実行プロセスを維持し、Apache はこれらのポートにリクエストを送信して PHP の実行を処理します。これらのライブラリはアクティブな PHP プロセスの数を制限するため、パフォーマンスに影響を与えることなくメモリ使用量が大幅に削減されます。
一部の賢い人々は、PHP で実際にうまく動作するように特別に設計された fastcgi の実装を作成し、それを PHP-FPM と呼びました。 PHP 5.3.0 より前は、インストールするために多くの困難を乗り越える必要がありましたが、幸いなことに、PHP 5.3.3 にはコアに PHP-FPM が含まれていたため、Ubuntu 12.04 へのインストールは非常に簡単でした。
次の例は Apache 2.2.22 用ですが、PHP-FPM は Nginx
などの他の Web サーバーでも使用できます。ターミナルで次のコマンドを実行して、PHP-FPM と Apache を Ubuntu 12.04 にインストールします:
sudo apt-get install apache2-mpm-worker libapache2-mod-fastcgi php5-fpm sudo a2enmod アクション エイリアス fastcgi
apache2-mpm-prefork や apache2-mpm-threaded ではなく、apache2-mpm-worker を使用する必要があることに注意してください。
次に、PHP リクエストを PHP-FPM 処理にルーティングするように Apache 仮想ホストを構成します。 Apache 構成ファイルに以下を記述します (Ubuntu 12.04 では、デフォルトのパスは /etc/apache2/sites-available/default です)。
1 |
|
2 | AddHandler php5-fcgi .php |
3 | Action php5-fcgi /php5-fcgi |
4 | Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi |
5 | FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -idle-timeout 120 -pass-header Authorization |
6 |
最後に、Apache プロセスと FPM プロセスを再起動します。
1 | sudo service apache2 restart |
2 | sudo service php5-fpm restart |
mod_php がパフォーマンスに悪い理由
PHPMailer 5.1 を使用してテストされました。
PHP は mail() 関数を提供しますが、これは非常にシンプルで簡単に思えます。残念ながら、PHP の多くの機能と同様、その単純さは誤解されやすいため、額面どおりに使用すると、重大なセキュリティ問題が発生しやすくなります。
電子メールは、PHP よりも曲がりくねった、痛みを伴う歴史を持つプロトコルの集合です。これに満足するということは、PHPmail() 関数が与えるべき感覚と同じように、電子メールを送信するときに混乱が多すぎることを意味します。
PHPMailer は、メールを安全に送信するためのシンプルなインターフェイスを提供する、人気のある完全なオープン ソース ライブラリです。疑問が解消されるので、より重要なことに集中できるようになります。
01 |
02 | // Include the PHPMailer library |
03 | require_once('phpmailer-5.1/class.phpmailer.php'); |
04 |
05 | // Passing 'true' enables exceptions. This is optional and defaults to false. |
06 | $mailer = new PHPMailer(true); |
07 |
08 | // Send a mail from Bilbo Baggins to Gandalf the Grey |
09 |
10 | // Set up to, from, and the message body. The body doesn't have to be HTML; check the PHPMailer documentation for details. |
11 | $mailer->Sender = 'bbaggins@example.com'; |
12 | $mailer->AddReplyTo('bbaggins@example.com', 'Bilbo Baggins'); |
13 | $mailer->SetFrom('bbaggins@example.com', 'Bilbo Baggins'); |
14 | $mailer->AddAddress('gandalf@example.com'); |
15 | $mailer->Subject = 'The finest weed in the South Farthing'; |
16 |
$mailer->MsgHTML(' You really must try it, Gandalf! -Bilbo '); |
17 |
18 | // Set up our connection information. |
19 | $mailer->IsSMTP(); |
20 | $mailer->SMTPAuth = true; |
21 | $mailer->SMTPSecure = 'ssl'; |
22 | $mailer->Port = 465; |
23 | $mailer->Host = 'my smpt host'; |
24 | $mailer->Username = 'my smtp username'; |
25 | $mailer->Password = 'my smtp password'; |
26 |
27 | // All done! |
28 | $mailer->Send(); |
29 | ?> |
Web アプリケーションが実行する必要がある一般的なタスク。ユーザーが有効な電子メール アドレスを入力したかどうかを確認するだけです。この問題を解決すると主張する複雑な式がオンラインで大量に見つかることは間違いありませんが、最も簡単な方法は、電子メール アドレスをチェックする PHP の組み込み関数 filter_var() を使用することです。
1 |
2 | filter_var('sgamgee@example.com', FILTER_VALIDATE_EMAIL); // Returns "sgamgee@example.com". This is a valid email address. |
3 | filter_var('sauron@mordor', FILTER_VALIDATE_EMAIL); // Returns boolean false! This is *not* a valid email address. |
4 | ?> |
PHP マニュアル: フィルターの種類
HTML Purifier 4.4.0 でテスト済み
WBE アプリケーションでユーザー出力を表示する場合は、最初に出力を「サニタイズ」して、潜在的に危険な HTML を削除することが重要です。 悪意のあるユーザーが HTML を作成する可能性があり、Web アプリケーションによって直接出力されると、それを閲覧する人にとって危険になります。
正規表現を使用して HTML をサニタイズすることもできますが、それは行わないでください。 HTML は複雑な言語であるため、正規表現を使用して HTML をサニタイズしようとすると、ほとんどの場合失敗します。
strip_tags() 関数を使用することを提案する意見が見つかるかもしれません。 Stripe_tags() は技術的には安全ですが、入力が無効な HTML (終了タグがないなど) の場合、意図したよりも多くのコンテンツを削除する可能性がある「愚かな」関数になります。技術者以外のユーザーは通信で < および > 文字を使用することが多いため、strip_tags() は適切な選択ではありません。
「電子メール アドレスの検証」セクションを読んだ場合は、filter_var() 関数の使用を検討することもできます。ただし、filter_var() 関数には改行が発生した場合に問題があり、htmlentities() 関数の効果を近似するには直感的でない構成が必要になるため、これは良い選択ではありません。
単純なニーズのための浄化
Web アプリケーションが HTML を完全にエスケープする必要がある (つまり、完全に削除する必要はないが無害にレンダリングする) だけが必要な場合は、PHP の組み込み htmlentities() 関数を使用します。 この関数は、HTML の検証を行わず、すべてをエスケープするだけなので、HTML Purifier よりもはるかに高速です。
htmlentities() は、小さなサブセットだけでなく、適用可能なすべての HTML エンティティをエンコードするという点で、同様の関数 htmlspecialchars() とは異なります。
01 |
02 | // 哦哦,用户的提交了一些恶意的html,我们需要将其在web 应用上显示! |
03 |
$evilHtml = ' Mua-ha-ha! Twiddling my evil mustache... '; |
04 |
05 | // 用 ENT_QUOTES 确保单双引号被转义. |
06 | // 用 UTF-8 编码,如果文件被存储为UTF-8格式. |
07 | // 见本文的 UTF-8 小节 |
08 | $safeHtml= htmlentities($evilHtml, ENT_QUOTES, 'UTF-8'); |
09 | // $safeHtml 已经被完全转移你可以放心的输出显示了! |
10 | ?> |
多くの Web アプリケーションでは、HTML をエスケープするだけでは十分ではありません。 HTML を完全に削除することも、HTML の小さなサブセットの存在を許可することもできます。その場合は、HTML Purifier ライブラリを使用してください。
HTML Purifier は十分にテストされていますが、比較的非効率なライブラリです。ニーズが複雑でない場合は、はるかに高速な htmlentities() を使用する必要があるのはこのためです。
HTML Purifier は、HTML を精製する前に検証するため、strip_tags() よりも優れています。これは、ユーザーが無効な HTML を入力した場合、HTML Purifier は、strip_tags() よりも HTML の元の意味を保持する点で優れていることを意味します。 HTML Purifier は高度にカスタマイズ可能で、HTML のサブセットのホワイトリストを作成して、HTML のこのサブセットのエンティティを出力に含めることができます。
欠点は、非常に遅く、セットアップが必要であり、共有ホスティング環境では実現できない可能性があることです。ドキュメントは複雑で理解しにくいことがよくあります。次の例は、基本的な使用構成です。 HTML Purifier が提供するさらに高度な機能については、ドキュメントを参照してください。
例
<?php// Include the HTML Purifier libraryrequire_once('htmlpurifier-4.4.0/HTMLPurifier.auto.php');// Oh no! The user has submitted malicious HTML, and we have to display it in our web app!$evilHtml='<div onclick="xss();">Mua-ha-ha! Twiddling my evil mustache...</div>';// Set up the HTML Purifier object with the default configuration.$purifier=newHTMLPurifier(HTMLPurifier_Config::createDefault());$safeHtml=$purifier->purify($evilHtml);// $safeHtml is now sanitized. You can output $safeHtml to your users without fear!?>
トラップ
さらに読む
PHP の UTF-8 は最悪です。言葉の選択を許してください。
現在、PHP は低レベルで Unicode をサポートしていません。 UTF-8 文字列が正しく処理されることを確認する方法はいくつかありますが、それは簡単ではなく、HTML から SQL、PHP まで、Web アプリケーションのすべてのレベルを詳しく調べる必要があります。簡潔で実践的な概要を提供することを目指しています。
2 つの文字列の連結や変数への文字列の代入などの基本的な文字列操作には、UTF-8 用の特別な操作は必要ありません。ただし、strpos() や strlen などのほとんどの文字列関数では、特別な考慮事項が必要です。これらの各関数には、対応する mb_* 関数があります (mb_strpos() や mb_strlen() など)。これらの対応する関数は、まとめてマルチバイト文字列関数と呼ばれます。これらのマルチバイト文字列関数は、Unicode 文字列を操作するために特別に設計されています。
Unicode 文字列を操作する場合は、mb_* 関数を使用する必要があります。たとえば、substr() を使用して UTF-8 文字列を操作すると、結果に文字化けが含まれる可能性があります。正しい関数は、対応するマルチバイト関数 mb_substr() である必要があります。
難しいのは、mb_* 関数の使用を常に忘れないようにすることです。一度忘れてしまったとしても、その後の処理中に Unicode 文字列が文字化けする可能性があります。
すべての文字列関数に対応する mb_* があるわけではありません。欲しいものが存在しない場合は、運が悪いです。
さらに、すべての PHP スクリプトの先頭 (またはグローバルに含まれるスクリプトの先頭) で mb_internal_encoding 関数を使用し、スクリプトがブラウザに出力する場合は mb_http_output() 関数を使用する必要があります。各スクリプトで文字列エンコーディングを明示的に定義すると、後で多くの問題を回避できます。
最後に、文字列を操作する多くの PHP 関数には、文字エンコーディングを指定できるオプションのパラメーターがあります。このオプションが存在する場合は、常に UTF-8 エンコーディングを明示的に指定する必要があります。たとえば、htmlentities() には文字エンコード オプションがあり、そのような文字列を処理する場合は常に UTF-8 を指定する必要があります。
PHP スクリプトが MySQL にアクセスする場合、上記のすべての考慮事項に従っていたとしても、文字列を非 UTF-8 文字列としてデータベースに保存している可能性があります。
PHP から MySQL まで文字列が UTF-8 形式であることを確認するには、データベースとフォームの両方が utf8mb4 文字セットに設定されていることを確認し、データベース内の他のクエリと議論する前に MySQL クエリ「set names utf8mb4」に注意してください。 。例として、MySQL データベースへの接続とクエリの章を見てください。これは非常に重要です。
UTF-8 サポートを完了するには、「utf8」文字セットではなく、「utf8mb4」文字セットを使用する必要があることに注意してください。その理由については、「続きを読む」を参照してください。
mb_http_output() 関数を使用して、PHP がブラウザに出力するファイルが UTF-8 でエンコードされていることを確認します。 HTMLページファイルの
タグの下に文字コードタグ(charsetタグ)があります。
01 |
02 | // Tell PHP that we're using UTF-8 strings until the end of the script |
03 | mb_internal_encoding('UTF-8'); |
04 |
05 | // Tell PHP that we'll be outputting UTF-8 to the browser |
06 | mb_http_output('UTF-8'); |
07 |
08 | // Our UTF-8 test string |
09 | $string = 'Aš galiu valgyti stiklą ir jis manęs nežeidžia'; |
10 |
11 | // Transform the string in some way with a multibyte function |
12 | $string = mb_substr($string, 0, 10); |
13 |
14 | // Connect to a database to store the transformed string |
15 | // See the PDO example in this document for more information |
16 | // Note the `set names utf8mb4` commmand! |
17 | $link = new PDO( 'mysql:host=your-hostname;dbname=your-db', |
18 | 'your-username', |
19 | 'your-password', |
20 | array( |
21 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
22 | PDO::ATTR_PERSISTENT => false, |
23 | PDO::MYSQL_ATTR_INIT_COMMAND => 'set names utf8mb4' |
24 | ) |
25 | ); |
26 |
27 | // Store our transformed string as UTF-8 in our database |
28 | // Assume our DB and tables are in the utf8mb4 character set and collation |
29 | $handle = $link->prepare('insert into Sentences (Id, Body) values (?, ?)'); |
30 | $handle->bindValue(1, 1, PDO::PARAM_INT); |
31 | $handle->bindValue(2, $string); |
32 | $handle->execute(); |
33 |
34 | // Retrieve the string we just stored to prove it was stored correctly |