iT邦幫忙

2024 iThome 鐵人賽

DAY 4
0
Modern Web

Laravel 那麼好用還需要自幹框架嗎系列 第 4

Day 04:捕捉使用者請求

  • 分享至 

  • xImage
  •  

能成功接觸到套件核心之後,我們終於可以來實際寫網頁框架的程式了!

首先我們要做的事情,就是接收使用者的請求,並且進行分析

我們來看看原本 Laravel 裡面的 public/index.php 是怎麼做到這件事情的

use Illuminate\Http\Request;

//...

// Bootstrap Laravel and handle the request...
(require_once __DIR__.'/../bootstrap/app.php')
    ->handleRequest(Request::capture());

這邊使用了 Laravel 自定義的 Request 類別,我們看看這邊的 capture() 實作為何:


use Symfony\Component\HttpFoundation\Request as SymfonyRequest;


/**
 * Create a new Illuminate HTTP request from server variables.
 *
 * @return static
 */
public static function capture()
{
	static::enableHttpMethodParameterOverride();

	return static::createFromBase(SymfonyRequest::createFromGlobals());
}

enableHttpMethodParameterOverride() 的實作如下,基本上以註解的內容為主,程式只是簡單的加上一個標記而已:

/**
 * Enables support for the _method request parameter to determine the intended HTTP method.
 *
 * Be warned that enabling this feature might lead to CSRF issues in your code.
 * Check that you are using CSRF tokens when required.
 * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
 * and used to send a "PUT" or "DELETE" request via the _method request parameter.
 * If these methods are not protected against CSRF, this presents a possible vulnerability.
 *
 * The HTTP method can only be overridden when the real HTTP method is POST.
 */
public static function enableHttpMethodParameterOverride(): void
{
	self::$httpMethodParameterOverride = true;
}

看到這麼大量的註解,可能有一些人會擔心這是專案內的壞味道。不過我個人不這麼認為:

雖然這裡的程式只有短短一行,註解遠比程式要多上很多,但是對於需要理解這段邏輯的人來說,這些註解我認為是非常有幫助的!

接著我們繼續往下看,這邊在定義參數的時候,使用了 SymfonyRequest::createFromGlobals() 這個函數。這個就不是 Laravel 所撰寫的類別了,而是使用 Symfony\Component\HttpFoundation\Request

我們來看看這個套件的說明,這個套件的 GitHub Repo 在 https://github.com/symfony/http-foundation ,說明文件則位於 https://symfony.com/doc/current/components/http_foundation.html

在 Symfony 的官方文件裡,我們可以看到 createFromGlobals() 所做的事情幾乎等同於

$request = new Request(
    $_GET,
    $_POST,
    [],
    $_COOKIE,
    $_FILES,
    $_SERVER
);

也就是說,在分析使用者的請求這件事情上,其實 Laravel 並不是自己從無到有的手刻出分析的邏輯出來,而是利用 Symfony 已經撰寫好的請求物件,加上他自己的邏輯,來設計出 Laravel 自己的請求物件!

我們來看看

class Request extends SymfonyRequest implements Arrayable, ArrayAccess

這邊可以看到,Laravel 直接讓客製化的請求類別繼承 Symfony 的類別,這樣就可以省掉很多邏輯了!

那麼,拿到 SymfonyRequest 之後要怎麼轉換成 Laravel 的 Request 物件呢?

我們來看看 createFromBase() 的實作

public static function createFromBase(SymfonyRequest $request)
{
	$newRequest = new static(
		$request->query->all(), $request->request->all(), $request->attributes->all(),
		$request->cookies->all(), (new static)->filterFiles($request->files->all()) ?? [], $request->server->all()
	);

	$newRequest->headers->replace($request->headers->all());

	$newRequest->content = $request->content;

	if ($newRequest->isJson()) {
		$newRequest->request = $newRequest->json();
	}

	return $newRequest;
}

這邊的 new static 會呼叫到 __construct(),由於 Request  裡面沒有定義 __construct(),所以是使用到 SymfonyRequest__construct()

除了建立一個幾乎一樣的請求物件,並且重新覆蓋 headerscontent 以外,有一段很有意思的邏輯:

if ($newRequest->isJson()) {
	$newRequest->request = $newRequest->json();
}

首先,先判斷請求是不是 json,這定義在 InteractsWithContentTypes 這個 trait 裡面:

/**
 * Determine if the request is sending JSON.
 *
 * @return bool
 */
public function isJson()
{
	return Str::contains($this->header('CONTENT_TYPE') ?? '', ['/json', '+json']);
}

另外一提,這個 trait 只有在 Request 這個類別內被使用。可以看出這樣拆分的目的只有一個,就是讓 Request 這個類別不包含那麼多細節而已。

如果 CONTENT_TYPE 裡面包含 json,那麼就將 request 內容做 json 格式的轉換:

public function json($key = null, $default = null)
{
	if (! isset($this->json)) {
		$this->json = new InputBag((array) json_decode($this->getContent() ?: '[]', true));
	}

	if (is_null($key)) {
		return $this->json;
	}

	return data_get($this->json->all(), $key, $default);
}

這邊使用了 PHP 原生的 json_decode 進行轉換。

今天對 Laravel 怎麼實作請求處理的部分,我們先看到這邊。各位明天見!


上一篇
Day 03:串接框架核心
下一篇
Day 05:引用 Symfony 並更新框架核心
系列文
Laravel 那麼好用還需要自幹框架嗎18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言