Laravel で作ったシステムに Excel をアップロードしたら拡張子が zip になった

## 環境
環境は `ローカルのHomestead` と `本番のVPS` の2つで、どちらでも発生。

* Laravel Framework 5.8.24
* PHP 7.3.4 (PHP 7.2.16)
* Ubuntu 18.04.2 LTS (CentOS Linux release 7.6.1810)
* Homestead (ConoHa VPS Laravelイメージ)

## 何がどうなった？
エクセルをアップロードし、 `$file->guessExtension()` で拡張子を取得したところ `zip` となってしまった。
さらに `$file->getMimeType()` をみたところ `application/zip` となっていた。

クライアントの情報を取得する関数を使うと、それぞれ `$file->guessClientExtension()` が
 `xlsx` となり、 `$file->getClientMimeType()` は `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` となった。
表にまとめると以下。

|  | extension | mime_type |
|---|---|---|
| larval | zip | application/zip | 
| client | xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |

エクセルの mime_type なっが。


## どうして？
`finfo` がうまく判定できないらしい。
ライブラリをガンガン追って行ったところ、Laravel の問題というよりは、内部で使用している Symfony のクラスの内部で使用している `finfo` に行きついた。
実際に `finfo` で mime_type を判定させてみると、 `application/zip` となってしまった。
`application/zip` から拡張子を推測すると `zip` になるという流れである。
[StackOverflow の関連記事] (https://stackoverflow.com/questions/6595183/docx-file-type-in-php-finfo-file-is-application-zip)
上記記事を読むと、ほかの Microsoft の Office 系ファイルもダメらしい。

## どうする？
解決策はいくつかあると思う。

* Symfony の該当クラスは複数の guesser を扱えるので、Laravel で頑張って拡張する
* finfo を修正する
* 素直にクライアントの拡張子を利用する

などなど。

**自分は** 上記のどれも採用せず、拡張子を推測するための関数をつくった(理由は後述)。
作ったと言っても、Laravel/Symfonyが用意したものと、先人の知恵を組み合わせただけだが。


### 基本方針
* 拡張子に関してはクライアント(ファイルアップロード者)を信じるという選択は取りたくない
* Laravel を拡張するのはしんどそう
* finfo を修正するのは移植性がない
* 不具合報告があったのはとりあえず excel だけ

### 参考ソース
```php
public function guessExtension(UploadedFile $file): string
{
    $extension = $file->guessExtension();
    if ($extension !== 'zip') {
        return $extension;
    }

    $guessedMimeType = $file->getMimeType();
    $clientMimeType = $file->getClientMimeType();
    if ($guessedMimeType === $clientMimeType) {
        return $extension;
    }

    $map = [
        'application/vnd.ms-word.document.macroEnabled.12' => 'docm',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',

        'application/vnd.ms-powerpoint.template.macroEnabled.12' => 'potm',
        'application/vnd.ms-powerpoint.addin.macroEnabled.12' => 'ppam',
        'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => 'ppsm',
        'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => 'pptm',
        'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
        'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',

        'application/vnd.ms-excel.template.macroEnabled.12' => 'xltm',
        'application/vnd.ms-excel.addin.macroEnabled.12' => 'xlam',
        'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => 'xlsb',
        'application/vnd.ms-excel.sheet.macroEnabled.12' => 'xlsm',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
    ];

    return $map[$clientMimeType] ?? $extension;
}
```

作った後に「 Symfony の同様の力技を行使する guesser を使う手もあったな」などと思ったけど、いろんな mime_type を検討するのはしんどいので、とりあえずこれで。