【Symfony】Ajax通信の識別方法

リクエストがAjax由来かそれ以外かを識別して、制御をかけたい時ありますよね。

- セキュリティ上、Ajaxリクエストの場合は排他したい
- Webアクセスしてほしくないから、Ajaxリクエストのみを許可したい

Symfonyでは、このような要件を簡単に実現できるよう、Requestオブジェクトが組み込まれています。

今回はAjaxリクエストの識別方法を紹介します。

もちろんデモを用意してきました。

## `Request` について
HTTPリクエストに関する情報を保持しているオブジェクトです。
Cookie操作やパラメータ操作など、扱えるデータがめちゃくちゃ多いです。

今回の、Ajax制御で利用するHeader情報等ももちろん操作することができます。

https://symfony.com/doc/current/components/http_foundation.html

## AJAXリクエストの識別方法

`isXmlHttpRequest()`メソッドが用意されているのでそちらを活用します。

```php
use Symfony\Component\HttpFoundation\Request;

public function myControllerFunction(Request $request)
{
    if ($request->isXmlHttpRequest()) {
        // AJAXリクエストに対する処理
    } else {
        // それ以外のリクエストに対する処理
    }
}

```

こんな感じで結構シンプルですね。

中の実装までの細かい説明は省きますが、リクエストヘッダーに `X-Requested-With: XMLHttpRequest` が含まれているかどうかをチェックする感じっぽいです。

## 実装例
LaravelとDrupalでの実装例を紹介します。

### Drupalだったら…。
#### Routing
```yaml:{module}/{module}.routing.yml
api.only_ajax:
  path: "/api/only_ajax"
  defaults:
    _controller: "\\Drupal\\custom_api\\Controller\\Calendar::onlyAjax"
api.exclude:
  path: "/api/exclude_ajax"
  defaults:
    _controller: "\\Drupal\\custom_api\\Controller\\Calendar::excludeAjax"
```

#### Controller
```php:{module}/src/Controller/ApiController.php
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class ApiController extends ControllerBase {

  /**
   * Ajax通信のみ許可する.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   */
public function onlyAjax(Request $request) {
    // Initialized.
    $res = new JsonResponse();

    if ($request->isXmlHttpRequest()) {
        return $res->setJson(json_encode(['message' => 'Please Ajax!!']));
    }
    $products = [
        ['id' => 1, 'name' => 'Product 1', 'price' => 100],
        ['id' => 2, 'name' => 'Product 2', 'price' => 150],
    ];
    return $res->setJson(json_encode($products));
  }

  /**
   * Ajax通信以外を許可する.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   */
public function excludeAjax(Request $request) {
    // Initialized.
    $res = new JsonResponse();

    if (!$request->isXmlHttpRequest()) {
        return $res->setJson(json_encode(['message' => 'No Ajax!!']));
    }
    $products = [
        ['id' => 1, 'name' => 'Product 1', 'price' => 100],
        ['id' => 2, 'name' => 'Product 2', 'price' => 150],
    ];
    return $res->setJson(json_encode($products));
  }

}
```

### Laravelだったら…。
#### Routing
```php:routes/web.php
Route::get('/api/only_ajax', [ApiController::class, 'onlyAjax'])->name('api.only_ajax');
Route::get('/api/exclude_ajax', [ApiController::class, 'excludeAjax'])->name('api.exclude_ajax');
```
#### Controller
```php:app/Http/Controllers/ApiController.php

class ApiController extends Controller
{
    /**
     * Ajax通信のみ許可する.
     *
     * @return \Symfony\Component\HttpFoundation\JsonResponse
     */
    public function onlyAjax(Request $request)
    {
        // Initialized.
        $res = new JsonResponse();

        if ($request->ajax()) {
            return $res->setJson(json_encode(['message' => 'Please Ajax!!']));
        }
        $products = [
            ['id' => 1, 'name' => 'Product 1', 'price' => 100],
            ['id' => 2, 'name' => 'Product 2', 'price' => 150],
        ];
        return $res->setJson(json_encode($products));
    }

    /**
     * Ajax通信以外を許可する.
     *
     * @return \Symfony\Component\HttpFoundation\JsonResponse
     */
    public function excludeAjax(Request $request)
    {
        // Initialized.
        $res = new JsonResponse();
        if ($request->ajax()) {
            return $res->setJson(json_encode(['message' => 'No Ajax!!']));
        }
        $products = [
            ['id' => 1, 'name' => 'Product 1', 'price' => 100],
            ['id' => 2, 'name' => 'Product 2', 'price' => 150],
        ];
        return $res->setJson(json_encode($products));
    }

}

```

※ Laravelは標準のRequestオブジェクトを拡張して、`ajax`メソッドが用意されているようです。便利 🎉

#### Laravelの`$request→ajax()`について
ここで実装されているようです。
```php:Illuminate/Http/Request.php
    /**
     * Determine if the request is the result of an AJAX call.
     *
     * @return bool
     */
    public function ajax()
    {
        return $this->isXmlHttpRequest();
    }
```
他にも、かなり便利なメソッドが用意されているようです。ぜひのぞいてみてください。

Drupalにも標準でほしい。（あるのかな？）