PHP 噴錯『 SSL certificate error: unable to get local issuer certificate』解決方法

在寫 Laravel 的時候,有時候會需要請求外部的 request ,如果請求的是 https ,就有可能會噴出 SSL certificate error: unable to get local issuer certificate

閱讀全文 PHP 噴錯『 SSL certificate error: unable to get local issuer certificate』解決方法

在 formData 表單送出 PUT、PATCH 或 DELETE 方法

HTML 表單再送請求的時候,是沒有支援 PUTPATCH 或 DELETE 這三種 method 的。所以在定義由 HTML 表單所呼叫的 PUTPATCH 或 DELETE 路由時,會需要在表單中增加一個隱藏的欄位: _method 

閱讀全文 在 formData 表單送出 PUT、PATCH 或 DELETE 方法

在 Mac OS 上面安裝 Laradock 筆記

之前在虛擬機上面開發 Laravel ,但是虛擬機得去要完整模擬一台主機,所以整個映像檔非常大,也因為這樣很多人都開始轉用 Docker。

在這篇文章中,我會紀錄如何使用 Docker 這項技術來建置 PHP/Laravel 的開發環境,未來要開發 Laravel 的時候,只要把 Laradock 運行起來就可以了。

閱讀全文 在 Mac OS 上面安裝 Laradock 筆記

Laravel 如何清理快取

有時候更新了程式碼,但是沒辦法即時看到結果,這是由於該應用程式被快取所導致。這裡記錄了一些清理快取的方法

  1. 清理應用程式快取  :php artisan cache:clear
  1. 清理路由快取:php artisan route:clear
  1. 清理設定快取:php artisan config:clear
  1. 清理編譯的視圖文件:php artisan view:clear
 

Laravel 使用 env 函式讀取環境變數為 null 的問題

在 Laravel 專案中,如果執行了 php artisan config:cache 命令把配置檔案快取起來後,在使用 env 函式讀取環境變數的值,會變成 null,但是執行 php artisan config:clear,清除配置快取後,又可以讀取了,就覺得很奇怪

看了一下,得知在 Laravel 中,如果執行 php aritisan config:cache 命令後,Laravel 就會把 app/config 目錄下的所有配置檔案快取到 bootstrap/cache/config.php 裡面。正因為有了這個快取配置檔案,在其他地方使用 env 函式,就會讀取不到環境變數,所以返回 null.

接著看一下 Illuminate/Foundation/Bootstrap/DetectEnvironment.php 的這段程式碼:

 

public function bootstrap(Application $app) {
    if (! $app->configurationIsCached()) {
        $this->checkForSpecificEnvironmentFile($app);
        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
        } catch (InvalidPathException $e) {
            //
        }
    }
}

 

這個方法在框架啟動後就會執行,這段程式碼說明了如果存在快取配置檔案,就不會去設定環境變數了

,配置都讀快取配置檔案,而不會再讀環境變數了。因此,如果做快取,一旦執行 php artisan config:cache 之後,env 函式就不起作用了

如何解決 PHP cURL error code 60

最近在開發的時候用到 AWS 的 PHP SDK ,然後就會踢到這個錯誤:

1Fatal error: Uncaught exception ‘cURL_Exception’ with message ‘cURL resource: Resource id #10; cURL error: SSL certificate problem: unable to get local issuer certificate (cURL error code 60).

其實就是 SSL 憑證出了點問題,所以沒辦法 cURL,大概爬了一下文,其實解決的方法其實很簡單,這邊簡單的紀錄一下:

  1. 先下載這份使用證書:
https://curl.haxx.se/ca/cacert.pem
  1. 然後去 php.ini 設定
1curl.cainfo = “path_to_cert\cacert.pem” // 剛剛那份證書的路徑

這樣就可以正常使用了

LINE 聊天機器人: Hello world!

前面講了這麼多,終於要進入重點了。現在來寫一個會回應的 LINE 聊天機器人吧!

無論你是透過 Laravel 安裝工具 的 laravel new 或是 composer create-project 的方式 都可以,反正就是新安裝一份 Laravel 專案吧!

在你安裝完 Laravel 後,首先需要做的事情就是設定一個隨機字串到應用程式金鑰。如果這金鑰沒有被設定的話,你的使用者 sessions 和其他的加密資料都會不安全,因此我們需要來設定一下應用程式金鑰。

假設你是透過 Composer 安裝 Laravel,這個金鑰有可能已經透過 key:generate 指令幫你設定完成,如果沒有的話,試著輸入 php artisan key:generate 產生應用程式金鑰吧

首先,我們來設定 `route` 的部分,讓 LINE 可以打入我們設定好的 webhook ,打開 routes/web.php

插入一行

Route::post('/callback', '[email protected]');

此時我們只要去 LINE Developer 後台的 webhooks 網址添加 /callback (如下圖),每當使用者跟機器人講話的時候,就會觸發 LineController 裡面的 webhook 這個方法

img

剛剛我們有提到 LineController 裡面的 webhook 方法,所以我們來建立一個,輸入 php artisan make:controller LineController ,此時 app/Http/Controllers/ 裡面會多了一新的 controller,我們在裡面增加一點東西,讓他可以接受 LINE 的訊息,然後回應

接著我們需要安裝 LINE 提供的 SDK ,所以下個指令 composer require linecorp/line-bot-sdk ,來安裝 line-bot-sdk ,安裝完成後就可以開始寫 code 囉!

我們先建立一個 app\Http\Controller\LineController

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use LINE\LINEBot;
use LINE\LINEBot\Event\MessageEvent;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;

class LineController extends Controller
{
    private $client;
    private $bot;
    private $channel_access_token;
    private $channel_secret;

    public function __construct()
    {
        $this->channel_access_token = env('CHANNEL_ACCESS_TOKEN');
        $this->channel_secret = env('CHANNEL_SECRET');

        $httpClient = new CurlHTTPClient($this->channel_access_token);
        $this->bot = new LINEBot($httpClient, ['channelSecret' => $this->channel_secret]);
        $this->client = $httpClient;
    }

    public function webhook(Request $request)
    {
        $bot = $this->bot;
        $signature = $request->header(\LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE);
        $body = $request->getContent();

        try
        {
            $events = $bot->parseEventRequest($body, $signature);
        }
        catch(\Exception $e)
        {
            Log::error($e->getMessage());
        }

        foreach ($events as $event)
        {
            $replyToken = $event->getReplyToken();
            if ($event instanceof MessageEvent)
            {
                $message_type = $event->getMessageType();
                $text = $event->getText();
                switch ($message_type)
                {
                    case 'text':
                        $bot->replyText($replyToken, 'Hello world!');
                    break;
                }
            }
        }
    }
}

 

先來講講 __construct 。這個其實很簡單,就是拿著你的 .env 裡面的 Channel secret 和 Channel access token 去建立一個 LINEBot 物件。

緊接著是 函式 webhook 中

$bot = $this->bot;
$signature = $request->header(\LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE);
$body = $request->getContent();

try {
    $events = $bot->parseEventRequest($body, $signature);
} catch (\Exception $e) {
    Log::error($e->getMessage());
}

 

我們先取除剛剛建構的 LINEBot 物件,然後去接收 $request ,接著 透過 try... catch 去看看這個 $request 能不能夠正確被處理,如果可以的話,就可以開始做事情啦!

接著是最後一段,也是最核心的部分

foreach ($events as $event) {
    $replyToken = $event->getReplyToken();
    if ($event instanceof MessageEvent) {
        $message_type = $event->getMessageType();
        $text = $event->getText();
        switch ($message_type) {
            case 'text':
            $bot->replyText($replyToken, 'Hello world!');
            break;
        }
    }
}

 

如果能夠正常處理請求,過來的東西會是一個陣列,裡頭有許許多多的 event ,而這個 event 裡頭有許多資訊供我們使用,首先我透過 $event->getReplyToken() 去取得 ReplyToken ,等等回覆給使用者的時候要用這個 token ,這組 token 存活時間非常短暫,所以我們接收到使用者的訊息,必須馬上把它用掉

接著我用 if ($event instanceof MessageEvent) 去確認事件類型是 MessageEvent 。至於這是什麼東西呢?先別急,之後我會介紹更多的事件類型,緊接著 if 裡面的第一行,我們先透過 getMessageType 取得訊息類型,還有 getText() 取得文字內容,再透過 switch 的方式判斷 訊息類型 是不是 text

最後,我們透過剛剛建立的 $bot 這個 LINEBot 物件中的 replyMessage 來回覆訊息給使用者吧!這樣核心的程式就完成的差不多了

然後剛剛有提到 env ,所以我們來去 .env 添加兩個環境變數吧!其中一個是 CHANNEL_ACCESS_TOKEN 另一個是 CHANNEL_SECRET ,還記得我在 先有一個 Line Messaging API account 這個章節一直提到的 Channel secret 和 Channel access token 嗎?這時候終於派得上用場囉!把他填上去就對了

CHANNEL_ACCESS_TOKEN={{ YOUR_CHANNEL_ACCESS_TOKEN }}
CHANNEL_SECRET={{ YOUR_CHANNEL_SECRET }}

感覺好像都可以了,試著去和 chatbot 聊天的時候,會發現他還是都不理人!!!

沒錯!因為 Laravel 這邊有個小小的坑,就是 Laravel 很貼心地做了 Csrf Token 驗證,所以 LINE 打過來的資料被視為非法的,所以被拒絕了!因此我們需要讓 LINE 打過來的這個 webhook 是暢通的。

先打開 app/Middleware/VerifyCsrfToken.php ,修改內容

<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
    /**
     * Indicates whether the XSRF-TOKEN cookie should be set on the response.
     *
     * @var bool
     */
    protected $addHttpCookie = true;
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = ['/callback'
    // 添加這行
    ];
}

 

這樣就會告訴 Laravel 說 /callback 這個路徑就不需要做驗證囉!這樣再去找聊天機器人聊天,應該就會 Hello world 了喔!

Webhook 資料格式

看到這邊,應該會好奇,當使用者每次和機器人聊天的時候,LINE 也會打到我們提供的 webhook URL ,那 LINE 究竟給了我們什麼呢?

我們先試著在上一篇建立的 app\Http\Controller\LineController 中修改一些東西,印出來看看

try {
    $events = $bot->parseEventRequest($body, $signature);
    Log::info($events); // 增加這個 Log,將收到的訊息印出來
} catch (\Exception $e) {
    Log::error($e->getMessage());
}

 

然後對著聊天機器人傳送一則訊息看看,就可以看到這個 $events 裡面到底放了什麼?

array (
0 =>
LINE\LINEBot\Event\MessageEvent\TextMessage::__set_state(
array(
'message' =>
array (
'type' => 'text',
'id' => '10629983864837',
'text' => '這是一段文字',
),
'event' =>
array (
'type' => 'message',
'replyToken' => '773xxxxxx10044c9b52ac0b185145023',
'source' =>
array (
'userId' => 'Ubefxxxxxxbdc59b024344f4ce81e7911',
'type' => 'user',
)
)
'timestamp' => 1569403340191
)
),
)

 

此時 LINE 收到使用者的文字訊息後,會把這則文字訊息傳到我們剛剛給的 webhook 位址。我們把它印出來後,可以看到這個 JSON 是一個 webhook event object,此物件(events)包含 1 ~ n 個 event。先拿一個 event 來說明,從第一個先看:

'message' =>
array (
    'type' => 'text',
    'id' => '10629983864837',
    'text' => '這是一段文字',
),

 

message 這個區塊主要是描述使用者傳來的訊息,我們可以從 type 看出這段訊息的類型; id 可以看出這則訊息的唯一識別碼; text 會有使用者傳過來的訊息。

接著是第二個區塊:

'event' =>
array (
    'type' => 'message',
    'replyToken' => '773xxxxxx10044c9b52ac0b185145023',
    'source' =>
    array (
        'userId' => 'Ubefxxxxxxbdc59b024344f4ce81e7911',
        'type' => 'user',
    ),
)

 

這裡描述著使用者傳過來的事件資訊。 type 是這則訊息的類型。 replyToken 則是在回應此訊息使用,當程式要回應此訊息時須使用此 token,這個 token 的有效時間不一定,但是相當的短,所以要回覆的話要儘快。

再來是, source 的這個部分。這裡會描述 哪種人? 和 誰? 傳過來的訊息。 type 可以得知是群組傳的訊息?還是使用者傳的訊息?所以可能是 user 或者是 group ; userId 則是傳過來的使用者的 id ,假設是群組傳過來的話,將會是 groupId (若使用者有同意 official accounts terms of use ,則會再多一個屬性 userId)。

最後一個 :

'timestamp' => 1569403340191

 

這個很單純就是這則訊息的時間戳,採用 UNIX 時間,也就是從 UTC 1970 年 1 月 1 日 0 時 0 分 0 秒起到現在不考慮閏秒的總秒數。