POST メソッドで送信されたデータは受取先の $_POST 変数によってすべて文字列、あるいは配列として処理される。
POST メソッドの正体は Content-Type に「application/x-www-form-urlencoded」または「multipart/form-data」を用いた HTTP リクエストであり、$_POST は POST メソッドから現在のページ渡された変数の連想配列である。

Content-Type

Content-Type とは、HTTP 通信において、ファイル種類を表すための情報を設定するためのヘッダー項目のことで、ここで指定する内容はメディアタイプを指定することが多いが、文字エンコードである charset や、データ境界値である boundary も必要に応じて指定もすることができる。

メディアタイプとは、画像、音声、動画、テキストなど、ファイル種類を表すための情報のことで MIME タイプとも呼ばれている。
text/html のように A/B という形式で表現されており、A のことをタイプ、B のことをサブタイプと呼ぶ。タイプではそのデータ型が当てはまる大枠のカテゴリを表し、サブタイプではより正確なデータ型を表す。
OSやそのバージョン、データの保存方法によって MIME タイプが違っていることもあり(CSVなど)、バリデーション実装時は複数の MIME タイプで判断するか、拡張子で判断するかなど工夫しなくてはならない。

Content-Type と MIME タイプの違いは、MIME タイプが情報そのものであるのに対し、Content-type は HTTP 通信のヘッダーにおいて、送信する情報がどのデータ型かを設定する項目名となるため、以下のような指定となる。

Content-type = MIME タイプ + 文字コード + データ境界値


Content-Type「application/x-www-form-urlencoded」は、エンコードされた URL のデータフォーマットであり、たとえば「hoge=foo&bar=baz」をエンコードしたデータは「hoge%3Dfoo%26bar%3Dbaz」のような形式となる。

通常、POST メソッドでフォームデータを送信するとき 「application/x-www-form-urlencoded」 を利用するが、ファイル送信が必要な場合は「multipart/form-data」を指定する必要がある。
その理由として、ファイル送信では、ひとつのフィールドに対して「ファイル名・種類・内容」と複数の情報を送信する必要があるが、application/x-www-form-urlencoded だとひとつのフィールドに対してひとつの情報しか送信できないからである。

ブラウザではレスポンスヘッダーの Content-Type で指定された MIME タイプをもとに挙動を変更するため、image/jpeg などの MIME タイプである場合、ブラウザは画像をブラウザ上に表示する。
ブラウザ表示ではなく、ダウンロードさせたい場合は Content-Disposition を活用することで対応が可能である。

# ウェブページとして表示可能であることを示す。
Content-Disposition: inline

# ダウンロードすべきであることを示す。
Content-Disposition: attachment

ちなみに、XHR2(XMLHttpRequest – Webブラウザなどに実装されている JavaScript API およびオブジェクトのひとつで、スクリプトから HTTP を利用して Web サーバにアクセスする機能を提供するもの)での POST 送信に使用できる FormData はテキストデータだけを送信しても「multipart/form-data」で送信され、URLSearchParams は「application/x-www-form-urlencoded」で送信されるため、FormData はファイル情報を扱うときのみに使用するよう注意する。

const form = new FormData();

// ファイルに見せかけたBLOB(Binary Large Object)
var file = new Blob(['this is my memo'], { type: "text/plain" });

form.append('fileName', 'memo.txt');
form.append('file', file);

xhr.send(form);

※BLOBとは

データベースのフィールド定義などで用いられるデータ型の一つで、テキスト(文字列)や整数のように既存のデータ型としては用意されていない任意のバイナリデータを格納するためのもの。

https://e-words.jp/w/BLOB.html

である、データベースの文脈では以下のように使用される。

データベース管理システム(DBMS)が用意している通常のデータ型のフィールドには格納することができない、画像や音声、動画、実行ファイル、圧縮ファイルなど様々な種類のバイナリデータをレコード中に直接格納することができる。

内部的にはテーブル本体とは別の記憶領域にまとめて保管され、テーブル上にはデータへの参照のみが格納される実装になっていることが多い。このため、他の形式のデータでは可能な検索や並べ替え、キー設定などの操作や機能のほとんどは利用できない。

https://e-words.jp/w/BLOB.html

GET と POST メソッドの違い

GET はリクエスト本体がなく「開始行」と「メッセージヘッダー」のみとなる。
パラメータを送りたい場合は、URLの中にパラメータを記載する(GET パラメーター)。

POSTは、「application/x-www-form-urlencoded」の場合、GET パラメーターと同様の内容が本体部にはいる。
また、GET と異なり、POST の場合はリクエストのメッセージには、「Content-Type」と「Content-Length(送信データのバイト数)」の2つのヘッダーフィールドが送信される。

例)
Content-Type:application/x-www-form-urlencoded
Content-Length:423

「multipart/form-data」の場合のメッセージヘッダーの Content-Type は以下のようになる。
boundaryは、リクエスト本体に設定されるデータを区切るための記号で、値はランダムに設定される。本体の中身はboundaryで指定された区切り文字で、項目ごとに区切られる。

例)foo=barという情報と、file.txtというテキストファイルを POST 送信した例
Content-Type:multipart/form-data; boundary=-----------26841326868719
-------------26841326868719
Content-Disposition: form-data; name="foo"
bar
-------------26841326868719
Content-Disposition: form-data; name="profile"; filename="file.txt"
Content-Type: text/plain
~~ファイルの中身~~
-------------26841326868719--

参考:https://www.ois-yokohama.co.jp/oisblog2018/archives/1012

このように「application/x-www-form-urlencoded」と「multipart/form-data」では内容は全く違うが、PHP の $_POST は同じように使用できるよう隠蔽して使いやすくしてくれている。

参考サイト:
http://ksk2aki.xsrv.jp/wp/programming/form_post_string/
https://note.com/shift_tech/n/n7b4f14e4bbcd
https://www.wakuwakubank.com/posts/799-it-content-type-content-disposition/
https://qiita.com/Jxck_/items/769766853a90b7b435b0
https://www.ois-yokohama.co.jp/oisblog2018/archives/1012