The HttpFoundation Component

Before diving into the framework creation process, let's first step back and let's take a look at why you would like to use a framework instead of keeping your plain-old PHP applications as is. Why using a framework is actually a good idea, even for the simplest snippet of code and why creating your framework on top of the Symfony components is better than creating a framework from scratch.

フレームワークの作成プロセスに入る前に、まず一歩戻って、単純な古い PHP アプリケーションをそのままにしておくのではなく、フレームワークを使用する理由を見てみましょう。最も単純なコード スニペットであっても、フレームワークを使用することが実際に良い考えである理由と、フレームワークを最初から作成するよりも Symfony コンポーネントの上にフレームワークを作成する方が優れている理由。

Note

ノート

We won't talk about the traditional benefits of using a framework when working on big applications with more than a few developers; the Internet already has plenty of good resources on that topic.

少数の開発者で大規模なアプリケーションに取り組んでいるときにフレームワークを使用する従来の利点については説明しません。インターネットには、そのトピックに関する優れたリソースがすでにたくさんあります。

Even if the "application" we wrote in the previous chapter was simple enough, it suffers from a few problems:

前の章で書いた「アプリケーション」が十分に単純であったとしても、いくつかの問題があります:
1
2
3
4
// framework/index.php
$name = $_GET['name'];

printf('Hello %s', $name);

First, if the name query parameter is not defined in the URL query string, you will get a PHP warning; so let's fix it:

まず、name クエリ パラメータが URL クエリ文字列で定義されていない場合、PHP の警告が表示されます。それでは修正しましょう:
1
2
3
4
// framework/index.php
$name = $_GET['name'] ?? 'World';

printf('Hello %s', $name);

Then, this application is not secure. Can you believe it? Even this simple snippet of PHP code is vulnerable to one of the most widespread Internet security issue, XSS (Cross-Site Scripting). Here is a more secure version:

次に、このアプリケーションは安全ではありません。信じられますか? PHP コードのこの単純なスニペットでさえ、最も蔓延しているインターネット セキュリティ問題の 1 つである XSS (クロスサイト スクリプティング) に対して脆弱です。より安全なバージョンは次のとおりです。
1
2
3
4
5
$name = $_GET['name'] ?? 'World';

header('Content-Type: text/html; charset=utf-8');

printf('Hello %s', htmlspecialchars($name, ENT_QUOTES, 'UTF-8'));

Note

ノート

As you might have noticed, securing your code with htmlspecialchars is tedious and error prone. That's one of the reasons why using a template engine like Twig, where auto-escaping is enabled by default, might be a good idea (and explicit escaping is also less painful with the usage of a simple e filter).

お気づきかもしれませんが、htmlspecialchars を使用してコードを保護するのは面倒で、エラーが発生しやすくなります。これが、自動エスケープがデフォルトで有効になっている Twig のようなテンプレートエンジンを使用することが良い考えである理由の 1 つです (また、単純な e フィルターを使用すると、明示的なエスケープもそれほど苦痛ではありません)。

As you can see for yourself, the simple code we had written first is not that simple anymore if we want to avoid PHP warnings/notices and make the code more secure.

ご覧のとおり、最初に書いた単純なコードは、PHP の警告/通知を回避し、コードをより安全にしたい場合、それほど単純ではなくなります。

Beyond security, this code can be complex to test. Even if there is not much to test, it strikes me that writing unit tests for the simplest possible snippet of PHP code is not natural and feels ugly. Here is a tentative PHPUnit unit test for the above code:

セキュリティを超えて、このコードはテストが複雑になる可能性があります。テストすることはそれほど多くないとしても、可能な限り単純な PHP コードのスニペットに対して単体テストを作成するのは不自然で、見苦しいと感じます。上記のコードの暫定的な PHPUnitunit テストを次に示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// framework/test.php
use PHPUnit\Framework\TestCase;

class IndexTest extends TestCase
{
    public function testHello()
    {
        $_GET['name'] = 'Fabien';

        ob_start();
        include 'index.php';
        $content = ob_get_clean();

        $this->assertEquals('Hello Fabien', $content);
    }
}

Note

ノート

If our application were just slightly bigger, we would have been able to find even more problems. If you are curious about them, read the Symfony versus Flat PHP chapter of the book.

アプリケーションがもう少し大きければ、さらに多くの問題を見つけることができたでしょう。それらについて興味がある場合は、この本の Symfony と Flat PHP の章を読んでください。

At this point, if you are not convinced that security and testing are indeed two very good reasons to stop writing code the old way and adopt a framework instead (whatever adopting a framework means in this context), you can stop reading this book now and go back to whatever code you were working on before.

この時点で、セキュリティとテストが実際に古い方法でコードを書くのをやめて代わりにフレームワークを採用する 2 つの非常に良い理由であると確信できない場合は (このコンテキストでフレームワークを採用することが何を意味するにせよ)、今すぐこの本を読むのをやめて、最初のページに戻ることができます。以前に取り組んでいたコード。

Note

ノート

Using a framework should give you more than just security and testability, but the more important thing to keep in mind is that the framework you choose must allow you to write better code faster.

フレームワークを使用すると、セキュリティとテスト容易性だけでなく、より重要なこととして、選択したフレームワークによって、より優れたコードをより速く記述できるようにする必要があります。

Going OOP with the HttpFoundation Component

Writing web code is about interacting with HTTP. So, the fundamental principles of our framework should be around the HTTP specification.

Web コードを書くことは、HTTP と対話することです。したがって、私たちのフレームワークの基本原則は、HTTP 仕様に沿っている必要があります。

The HTTP specification describes how a client (a browser for instance) interacts with a server (our application via a web server). The dialog between the client and the server is specified by well-defined messages, requests and responses: the client sends a request to the server and based on this request, the server returns a response.

HTTP 仕様では、クライアント (たとえばブラウザー) がサーバー (Web サーバーを介したアプリケーション) と対話する方法が記述されています。クライアントとサーバー間のダイアログは、明確に定義されたメッセージ、要求、および応答によって指定されます。クライアントはサーバーに要求を送信し、この要求に基づいて、サーバーは応答を返します。

In PHP, the request is represented by global variables ($_GET, $_POST, $_FILE, $_COOKIE, $_SESSION...) and the response is generated by functions (echo, header, setcookie, ...).

PHP では、リクエストはグローバル変数 ($_GET、$_POST、$_FILE、$_COOKIE、$_SESSION...) で表され、レスポンスは関数 (echo、header、setcookie、...) によって生成されます。

The first step towards better code is probably to use an Object-Oriented approach; that's the main goal of the Symfony HttpFoundation component: replacing the default PHP global variables and functions by an Object-Oriented layer.

より良いコードへの第一歩は、おそらくオブジェクト指向アプローチを使用することです。これが Symfony HttpFoundation コンポーネントの主な目標です: デフォルトの PHP グローバル変数と関数をオブジェクト指向レイヤーに置き換えることです。

To use this component, add it as a dependency of the project:

このコンポーネントを使用するには、プロジェクトの依存関係として追加します。
1
$ composer require symfony/http-foundation

Running this command will also automatically download the Symfony HttpFoundation component and install it under the vendor/ directory. A composer.json and a composer.lock file will be generated as well, containing the new requirement.

このコマンドを実行すると、SymfonyHttpFoundation コンポーネントも自動的にダウンロードされ、vendor/ ディレクトリにインストールされます。 新しい要件を含む composer.json と composer.lock ファイルも生成されます。
クラスのオートローディング

When installing a new dependency, Composer also generates a vendor/autoload.php file that allows any class to be autoloaded. Without autoloading, you would need to require the file where a class is defined before being able to use it. But thanks to PSR-4, we can just let Composer and PHP do the hard work for us.

新しい依存関係をインストールするとき、Composer は任意のクラスを自動ロードできるようにする avendor/autoload.php ファイルも生成します。しかし、PSR-4 のおかげで、Composer と PHP に面倒な作業を任せることができます。

Now, let's rewrite our application by using the Request and the Response classes:

それでは、Request クラスと theResponse クラスを使用してアプリケーションを書き直しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
// framework/index.php
require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$name = $request->query->get('name', 'World');

$response = new Response(sprintf('Hello %s', htmlspecialchars($name, ENT_QUOTES, 'UTF-8')));

$response->send();

The createFromGlobals() method creates a Request object based on the current PHP global variables.

createFromGlobals() メソッドは、現在の PHP グローバル変数に基づいて Request オブジェクトを作成します。

The send() method sends the Response object back to the client (it first outputs the HTTP headers followed by the content).

send() メソッドは、Response オブジェクトをクライアントに送り返します (最初に HTTP ヘッダーを出力し、その後にコンテンツを出力します)。

Tip

ヒント

Before the send() call, we should have added a call to the prepare() method ($response->prepare($request);) to ensure that our Response were compliant with the HTTP specification. For instance, if we were to call the page with the HEAD method, it would remove the content of the Response.

send() 呼び出しの前に、応答が HTTP 仕様に準拠していることを確認するために、prepare() メソッド ($response->prepare($request);) への呼び出しを追加する必要があります。たとえば、HEAD メソッドでページを呼び出すと、レスポンスのコンテンツが削除されます。

The main difference with the previous code is that you have total control of the HTTP messages. You can create whatever request you want and you are in charge of sending the response whenever you see fit.

前のコードとの主な違いは、HTTP メッセージを完全に制御できることです。必要なリクエストは何でも作成でき、必要に応じていつでもレスポンスを送信できます。

Note

ノート

We haven't explicitly set the Content-Type header in the rewritten code as the charset of the Response object defaults to UTF-8.

Response オブジェクトの文字セットがデフォルトで UTF-8 に設定されているため、書き換えたコードでは Content-Type ヘッダーを明示的に設定していません。

With the Request class, you have all the request information at your fingertips thanks to a nice and simple API:

Request クラスを使用すると、すてきでシンプルな API のおかげで、すべての要求情報をすぐに利用できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// the URI being requested (e.g. /about) minus any query parameters
$request->getPathInfo();

// retrieves GET and POST variables respectively
$request->query->get('foo');
$request->request->get('bar', 'default value if bar does not exist');

// retrieves SERVER variables
$request->server->get('HTTP_HOST');

// retrieves an instance of UploadedFile identified by foo
$request->files->get('foo');

// retrieves a COOKIE value
$request->cookies->get('PHPSESSID');

// retrieves a HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content-type');

$request->getMethod();    // GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // an array of languages the client accepts

You can also simulate a request:

リクエストをシミュレートすることもできます:
1
$request = Request::create('/index.php?name=Fabien');

With the Response class, you can tweak the response:

Response クラスを使用すると、レスポンスを微調整できます。
1
2
3
4
5
6
7
8
$response = new Response();

$response->setContent('Hello world!');
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/html');

// configure the HTTP cache headers
$response->setMaxAge(10);

Tip

ヒント

To debug a response, cast it to a string; it will return the HTTP representation of the response (headers and content).

応答をデバッグするには、応答を文字列にキャストします。応答の HTTP 表現 (ヘッダーとコンテンツ) を返します。

Last but not least, these classes, like every other class in the Symfony code, have been audited for security issues by an independent company. And being an Open-Source project also means that many other developers around the world have read the code and have already fixed potential security problems. When was the last time you ordered a professional security audit for your home-made framework?

最後になりましたが、これらのクラスは、Symfonycode の他のすべてのクラスと同様に、独立した会社によってセキュリティの問題について監査されています。また、オープンソース プロジェクトであるということは、世界中の他の多くの開発者がコードを読み、潜在的なセキュリティ問題を既に修正していることも意味します。自家製のフレームワークの専門的なセキュリティ監査を最後に注文したのはいつですか?

Even something as simple as getting the client IP address can be insecure:

クライアントの IP アドレスを取得するような単純なことでさえ、安全ではない可能性があります。
1
2
3
if ($myIp === $_SERVER['REMOTE_ADDR']) {
    // the client is a known one, so give it some more privilege
}

It works perfectly fine until you add a reverse proxy in front of the production servers; at this point, you will have to change your code to make it work on both your development machine (where you don't have a proxy) and your servers:

本番サーバーの前にリバース プロキシを追加するまでは、問題なく動作します。この時点で、コードを変更して、開発マシン (プロキシがない場所) とサーバーの両方で動作するようにする必要があります。
1
2
3
if ($myIp === $_SERVER['HTTP_X_FORWARDED_FOR'] || $myIp === $_SERVER['REMOTE_ADDR']) {
    // the client is a known one, so give it some more privilege
}

Using the Request::getClientIp() method would have given you the right behavior from day one (and it would have covered the case where you have chained proxies):

Request::getClientIp() メソッドを使用すると、初日から適切な動作が得られます (また、プロキシをチェーンした場合もカバーされます)。
1
2
3
4
5
$request = Request::createFromGlobals();

if ($myIp === $request->getClientIp()) {
    // the client is a known one, so give it some more privilege
}

And there is an added benefit: it is secure by default. What does it mean? The $_SERVER['HTTP_X_FORWARDED_FOR'] value cannot be trusted as it can be manipulated by the end user when there is no proxy. So, if you are using this code in production without a proxy, it becomes trivially easy to abuse your system. That's not the case with the getClientIp() method as you must explicitly trust your reverse proxies by calling setTrustedProxies():

さらに、追加の利点があります。デフォルトで安全です。どういう意味ですか?$_SERVER['HTTP_X_FORWARDED_FOR'] 値は、プロキシがない場合にエンド ユーザーが操作できるため、信頼できません。したがって、このコードをプロキシなしで本番環境で使用すると、システムを悪用するのは簡単になります。 setTrustedProxies() を呼び出してリバース プロキシを明示的に信頼する必要があるため、getClientIp() メソッドには当てはまりません。
1
2
3
4
5
Request::setTrustedProxies(['10.0.0.1']);

if ($myIp === $request->getClientIp()) {
    // the client is a known one, so give it some more privilege
}

So, the getClientIp() method works securely in all circumstances. You can use it in all your projects, whatever the configuration is, it will behave correctly and safely. That's one of the goals of using a framework. If you were to write a framework from scratch, you would have to think about all these cases by yourself. Why not use a technology that already works?

そのため、getClientIp() メソッドはあらゆる状況で安全に機能します。構成が何であれ、すべてのプロジェクトで使用でき、正しく安全に動作します。これは、フレームワークを使用する目的の 1 つです。フレームワークをゼロから作成する場合、これらすべてのケースを自分で考えなければなりません。すでに機能しているテクノロジーを使用してみませんか?

Note

ノート

If you want to learn more about the HttpFoundation component, you can have a look at the Symfony\Component\HttpFoundation API or read its dedicated documentation.

HttpFoundation コンポーネントについて詳しく知りたい場合は、Symfony\Component\HttpFoundation API を参照するか、専用のドキュメントを参照してください。

Believe it or not but we have our first framework. You can stop now if you want. Using just the Symfony HttpFoundation component already allows you to write better and more testable code. It also allows you to write code faster as many day-to-day problems have already been solved for you.

信じられないかもしれませんが、最初のフレームワークがあります。必要に応じて今すぐ停止することもできます。Symfony HttpFoundation コンポーネントだけを使用することで、より優れた、よりテストしやすいコードを書くことができます。また、多くの日常の問題がすでに解決されているため、コードをより速く書くことができます。

As a matter of fact, projects like Drupal have adopted the HttpFoundation component; if it works for them, it will probably work for you. Don't reinvent the wheel.

実際のところ、Drupal のようなプロジェクトは HttpFoundation コンポーネントを採用しています。それが彼らのために働くなら、それはおそらくあなたのために働くでしょう.車輪を再発明しないでください。

I've almost forgotten to talk about one added benefit: using the HttpFoundation component is the start of better interoperability between all frameworks and applications using it (like Symfony, Drupal 8, phpBB 3, Laravel and ezPublish 5, and more).

もう 1 つの利点について話すのをほとんど忘れていました。HttpFoundation コンポーネントを使用すると、それを使用するすべてのフレームワークとアプリケーション (Symfony、Drupal 8、phpBB 3、Laraveland ezPublish 5 など) 間の相互運用性が向上します。