【PHP】Apacheのcurl設定につまずきまくった【Windows】

Windowsユーザーの補助になればとの思いで綴りました。

## 環境
* Windows 11
* PHP 8.1.6
* Apache 2.4

## きっかけ
Slack APIの`chat.postMessage`を使ってlocalhostのApacheから送信しようとした。
```
function send_slack(string $text): void
{
    // SlackbotのtokenとchannelIDを取得
    $token = 'xoxb-XXXXXXXXXXXXXXXXXXXXX';
    $channel_id = 'XXXXXXXXXXX';
    // クエリ
    $query = 'channel='.$channel_id.'&text='.$text;
    // APIリクエストのヘッダーを作成
    $headers = [
        'Authorization: Bearer ' . $token
    ];

    // cURLを使用してAPIリクエストを送信
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://slack.com/api/chat.postMessage');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
    // curl_setopt($ch, CURLOPT_POSTFIELDS, "channel=$channel_id&text=$text");
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $response = curl_exec($ch);
    curl_close($ch);

    // APIレスポンスを返す
    // echo $response;
    return;
}
```
実行すると、以下のエラーが発生した。
```
Fatal error: Call to undefined function curl_init() in ...
```
確かにcurlの設定してなかった。やったろやないか、ということで取り掛かったのだが、ヒットした記事にWindowsのものがあまりなく、Linuxが大多数だったので小一時間悩んだ。

## 試したこと
### コマンドプロンプトからモジュール一覧を表示
`php -m`をコマンドプロンプトで実行する。
```
C:\Users\username>php -m
[PHP Modules]
bcmath
calendar
Core
ctype
curl
date
dom
filter
hash
iconv
json
libxml
mbstring
mysqlnd
openssl
...

[Zend Modules]

```
curlが項目にあるが、機能していないということはモジュールの一部が正しく読み込めていないのだろう。
ということで色々試す。

### phpの設定を確認する
phpinfo()でPHPの設定を出力したとき、有効化されていれば下図のようなcurlの項目が出る。下図は有効化した現在だから表示されているが、エラー発生時はこの項目が無かった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2979365/808df7a5-3b38-8228-e6d8-4f76113b0a9f.png)
ということは、読み込みに何らかの支障が出ているのだろう。

### php.iniの確認
確認項目は次の通りである。
* `extension_dir`がPHPの`ext`ディレクトリを指定している。
* `extension=curl`のコメントアウトを解除している。openSSLのモジュールもついでに確認。

### curl、openSSLをインストール
curlはダウンロード済み。openSSLは64bit版（軽量）をダウンロードしたので`C:\Program Files`以下にインストール。詳細は省略します。

### curlモジュールの依存関係を調べる
PHPに同梱されている`deplister.exe`を使う。コマンドプロンプトでPHPを入れているディレクトリに飛んでから、以下のコマンドを実行する。
```
C:\PHP>deplister.exe ext\php_curl.dll
```

結果がこちら。
```
C:\PHP>deplister.exe ext\php_curl.dll
php8ts.dll,OK
libcrypto-1_1-x64.dll,OK
libssl-1_1-x64.dll,OK
CRYPT32.dll,OK
WLDAP32.dll,OK
Normaliz.dll,OK
libssh2.dll,OK
nghttp2.dll,OK
KERNEL32.dll,OK
WS2_32.dll,OK
VCRUNTIME140.dll,OK
api-ms-win-crt-heap-l1-1-0.dll,OK
api-ms-win-crt-stdio-l1-1-0.dll,OK
api-ms-win-crt-string-l1-1-0.dll,OK
api-ms-win-crt-runtime-l1-1-0.dll,OK
api-ms-win-crt-convert-l1-1-0.dll,OK
api-ms-win-crt-filesystem-l1-1-0.dll,OK
api-ms-win-crt-time-l1-1-0.dll,OK
api-ms-win-crt-utility-l1-1-0.dll,OK
```
`OK`でなく`NOT_FOUND`などになっていたらモジュールが無かったり、破損していたりすることがある。openSSLやVisual C++ 再頒布可能パッケージなどを整えるとこれらのファイルは揃うはず。
大体の記事がここまでで解決している（？）ようだったが、私は解決しなかった。ここから色々調べた結果、次の方法で動作するようになった。

### 【結論】httpd.confに追記する
ファイル末尾に以下の記述を追加した。
```
# cURL
LoadFile "C:\PHP\libssh2.dll"
LoadFile "C:\PHP\libcrypto-1_1-x64.dll"
LoadFile "C:\PHP\libssl-1_1-x64.dll"
```
これは正攻法なのだろうか…と思いつつ、追記後Apacheを再起動すると、phpinfo()にもcurlの項目が追加され、Slackにも送信ができるようになった。これにて解決！

## と思いきや、もう一波あった
```
Fatal error: Uncaught GuzzleHttp\Exception\RequestException: cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://oauth2.googleapis.com/token in ...
```
これはSlack APIではなく、Google Sheet APIを叩いたときに起こっているようだ。対処法は次の2つがあるらしい。
1. SSL証明書の検証を無効化する
2. 正しいルート証明書を設定する

1はセキュリティ的によろしくないだったので、2を試した。
### ルート証明書を設定する
今回は提供されている証明書を利用した。自己産の証明書を生成する方法は他の方の記事を参照。以下から`cacert.pem`をダウンロードする。

https://curl.se/docs/caextract.html

cacert.pemを適当な場所に設置し、`php.ini`に証明書のパスを追記する。私はopenSSLのフォルダ内に設置したため、以下のようになる。
```
curl.cainfo = "C:\Program Files\OpenSSL-Win64\cacert.pem"
```