目次
パート1
パート2
パート3
パート4
パート5
コードのリファクタリングに入る前に、まずステップを戻して、純粋なPHPでアプリケーションを作成するのではなく、フレームワークを使用する理由を見てみましょう。 フレームワークを使用することは、たとえ最も単純なコードであっても、実際には良い考えであり、Symfony2コンポーネントに基づいて独自のフレームワークを作成することが、フレームワークをゼロから作成するよりも優れている理由です。
- 大規模なアプリケーションや数人以上の開発者と作業するときにフレームワークを使用することの明らかな利点については説明しません。 このトピックに関するネットには、すでに多くの優れたリソースがあります。
前回書いた「アプリケーション」は非常にシンプルでしたが、いくつかの欠点があります。
<?php // framework/index.php $input = $_GET['name']; printf('Hello %s', $input);
まず、クエリ変数で名前変数が設定されていない場合、PHP警告が発行されます。 これを修正しましょう:
<?php // framework/index.php $input = isset($_GET['name']) ? $_GET['name'] : 'World'; printf('Hello %s', $input);
信じられないかもしれませんが、この小さなコードでも、最も一般的な脆弱性の1つであるXSS(クロスサイトスクリプティング)に対して脆弱です。 より安全なバージョンは次のとおりです。
<?php $input = isset($_GET['name']) ? $_GET['name'] : 'World'; header('Content-Type: text/html; charset=utf-8'); printf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));
- お気づきかもしれませんが、 htmlspecialchars()を使用してコードを保護するのは非常に面倒で、簡単にタイプミスにつながる可能性があります。 これは、自動エスケープがデフォルトで有効になっているTwigなどのテンプレートエンジンを使用する理由の1つです。
ご覧のとおり、セキュリティチェックを追加し、PHPの警告/通知を回避すると、最初は単純なコードがより複雑になります。
セキュリティに加えて、このコードはテストするのも簡単ではありません。 そこにテストするものがあまりないとしても、PHPコードの最も単純な部分の単体テストを書くのは自然ではなく、見苦しいように思えます。 上記のコードのPHPUnit単体テストの例を次に示します。
<?php // framework/test.php class IndexTest extends \PHPUnit_Framework_TestCase { public function testHello() { $_GET['name'] = 'Fabien'; ob_start(); include 'index.php'; $content = ob_get_clean(); $this->assertEquals('Hello Fabien', $content); } }
現時点では、セキュリティとテストが実際に古い方法でコードを記述せず、フレームワークに適合させるための2つの非常に良い理由であると確信していない場合、このシリーズの読みをやめてコードに戻ることができます。あなたが以前に取り組んだ。
- もちろん、フレームワークを使用すると、単なるセキュリティとテスト以上のものが得られます。 しかし、もっと重要なことは、フレームワークがより良いコードをより速く書けるようにするべきだということを心に留めておく必要があります。
HttpFoundationコンポーネントを使用してOOPに切り替えます。 (HttpFoundationコンポーネントを使用してOOPに進む)
Webベースのコードの記述は、本質的にHTTPプロトコルで機能します。 したがって、フレームワークの基本原則は、HTTP仕様に基づいている必要があります 。
HTTP仕様には、クライアント(ブラウザなど)がサーバー(Webサーバーを介したアプリケーション)と対話する方法が記述されています。 クライアントとサーバー間の対話は明確に定義されたメッセージ、要求、応答です。クライアントはサーバーに要求を送信し、この要求に基づいてサーバーは応答を返します。
PHPでは、リクエストはグローバル変数( $ _GET、$ _POST、$ _FILE、$ _COOKIE、$ _SESSION ...)として表され、関数(echo、header、setcookie、...)を使用して応答が生成されます。
より良いコードへの最初のステップは、オブジェクト指向のアプローチを使用することです。 HttpFoundationコンポーネントの主な目標は、グローバルPHP変数をオブジェクト指向のレイヤーに置き換えることです。
このコンポーネントを使用するには、
composer.json
ファイルにプロジェクトに応じて追加し、その後コマンドを実行します
composer update
最後に、autoload.phpファイルの下部に、コンポーネントを自動的にダウンロードするために必要なコードを追加します。
<?php // framework/autoload.php $loader->registerNamespace('Symfony\\Component\\HttpFoundation', __DIR__.'/vendor/symfony/http-foundation');
次に、「 Request 」および「 Response 」クラスを使用してアプリケーションを書き直します。
<php // framework/index.php require_once __DIR__.'/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals(); $input = $request->get('name', 'World'); $response = new Response(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'))); $response->send();
createFromGlobals()メソッドは、PHPグローバル変数の現在の値に基づいてRequestクラスのインスタンスを作成します。
send()メソッドは、「 Response 」クラスのオブジェクトのデータをクライアントに送信します(最初に、HTTPヘッダーとそれに続くコンテンツ)。
- ヒント: send()メソッドを呼び出す前に、 prepare()メソッド( $ response-> prepare($ request); )を呼び出して、「 Response 」がHTTP仕様と互換性があることを確認する必要があります。 たとえば、「HEAD」メソッドを使用してページを取得した場合、応答本文からコンテンツを削除する必要があります。
前のコードとの主な違いは、HTTPメッセージを完全に制御できることです。 必要なリクエストを作成でき、適切と思われる場合はいつでもレスポンスを送信する責任があります。
- 「 Response 」オブジェクトのデフォルトのエンコードはUTF-8であるため、書き換えられたコードに「Content-Type」ヘッダーを設定しませんでした。
「 Request 」クラスでは、すてきでシンプルなAPIのおかげで、要求されたすべての情報が常に手元にあります。
<?php // URI (.. /about) $request->getPathInfo(); // GET POST $request->query->get('foo'); $request->request->get('bar', 'default value if bar does not exist'); // SERVER $request->server->get('HTTP_HOST'); // "UploadedFile" ( ) foo $request->files->get('foo'); // COOKIE $request->cookies->get('PHPSESSID'); // HTTP $request->headers->get('host'); $request->headers->get('content_type'); $request->getMethod(); // GET, POST, PUT, DELETE, HEAD $request->getLanguages(); // ,
リクエストをシミュレートすることもできます:
$request = Request::create('/index.php?name=Fabien');
「 Response 」クラスを使用すると、応答を簡単にカスタマイズできます。
<?php $response = new Response(); $response->setContent('Hello world!'); $response->setStatusCode(200); $response->headers->set('Content-Type', 'text/html'); // HTTP $response->setMaxAge(10);
- ヒント:「 Response 」をデバッグするには、オブジェクトを文字列型にキャストします。 これは、応答のHTTP送信を返します(ヘッダーとコンテンツ)
最後になりましたが、これらのクラスは、Symfonyの他のクラスと同様に、セキュリティの問題について独立企業によってテストされています。 また、オープンソースプロジェクトは、世界中の他の多くの開発者がコードを読んでおり、潜在的なセキュリティ問題を既に修正していることも意味します。 自作のフレームワークに対して専門的なセキュリティ監査を注文したのはいつですか?
クライアントのIPアドレスを返すような単純な操作でも安全ではない場合があります。
<?php if ($myIp == $_SERVER['REMOTE_ADDR']) { // , }
これは、本番環境のサーバーの前にリバースプロキシを追加するまで正常に機能します。現時点では、両方のマシンで機能するようにコードを変更する必要があります(開発サーバーにリバースプロキシがない場合)。
<?php if ($myIp == $_SERVER['HTTP_X_FORWARDED_FOR'] || $myIp == $_SERVER['REMOTE_ADDR']) { // , }
Request :: getClientIp()メソッドを使用すると、プロキシの存在に関係なく、初日から機能するコードを取得できます。
<?php $request = Request::createFromGlobals(); if ($myIp == $request->getClientIp()) { // , }
また、追加の利点もあります:デフォルトで安全です。 セキュリティとはどういう意味ですか? エンドユーザーは$ _SERVER ['HTTP_X_FORWARDED_FOR']の値を操作でき、信頼できません。 したがって、プロキシサーバーなしで本番環境でこのコードを使用すると、システムで悪用するのが非常に簡単になります。 これはgetClientIp()メソッドでは発生しません。trustProxyData()を呼び出してこのヘッダーを信頼することを明示的に示す必要があるためです。
<?php Request::trustProxyData(); if ($myIp == $request->getClientIp(true)) { // , }
したがって、 getClientIp()はすべての状況で確実に機能します。 設定に関係なく、すべてのプロジェクトで使用でき、正しく安全に動作します。 これは、フレームワークを使用する目的の1つです。 フレームワークをゼロから作成した場合、これらすべてのケースを自分で考える必要があります。 では、すでに機能しているテクノロジーを使用してみませんか?
信じられないかもしれませんが、これで最初のフレームワークができました。 もちろん、これにこだわることができます。 Symfony2 HttpFoundationコンポーネントを使用すると、より優れた、よりテスト可能なコードを作成できます。 また、毎日の問題のほとんどは既にあなたの場所で解決されているため、コードをより速く書くことができます。
実際、Drupalなどのプロジェクトでは、(次のバージョン8の)HttpFoundationコンポーネントを採用しています。 彼らのために働くなら、おそらくあなたのために働くでしょう。 車輪を再発明しないでください。
HttpFoundationコンポーネントを使用することで、フレームワークとそれを使用するアプリケーション(今日のSymfony2 、 Drupal 8 、 PhpBB 4 、 Silex 、 Midgard CMS 、 Zikula ...)とのより良い相互作用を実現できるHttpFoundationコンポーネントを使用することを忘れました。