使用 PHP 進行背景處理

許多應用程式都需要在網頁要求內容之外執行背景處理。本教學課程會建立一個網頁應用程式,讓使用者輸入要翻譯的文字,然後顯示先前翻譯的清單。翻譯是在背景程序中完成,以避免妨礙使用者的要求。

下圖說明要求翻譯的過程。

架構圖。

這是教學課程應用程式運作的事件順序:

  1. 前往網頁以查看 Firestore 儲存的先前翻譯清單。
  2. 輸入 HTML 表單以要求翻譯文字。
  3. 翻譯要求會發布至 Pub/Sub。
  4. Cloud Run 應用程式會收到 Pub/Sub 訊息。
  5. Cloud Run 應用程式使用 Cloud Translation 來翻譯文字。
  6. Cloud Run 應用程式會將結果儲存在 Firestore 中。

本教學課程的適用對象為有興趣瞭解如何使用 Google Cloud進行背景處理的人員。您無須具備使用 Pub/Sub、Firestore、App Engine 或 Cloud Run 的經驗。不過,如要瞭解所有程式碼,具備一些 PHP、JavaScript 和 HTML 的使用經驗會有所幫助。

目標

  • 瞭解及部署 Cloud Run 服務。
  • 瞭解並部署 App Engine 應用程式。
  • 試用應用程式。

費用

在本文件中,您會使用下列 Google Cloud的計費元件:

如要根據預測用量估算費用,請使用 Pricing Calculator

初次使用 Google Cloud 的使用者可能符合免費試用期資格。

完成本文所述工作後,您可以刪除建立的資源,避免繼續計費,詳情請參閱「清除所用資源」。

事前準備

  1. 登入 Google Cloud 帳戶。如果您是 Google Cloud新手,歡迎 建立帳戶,親自評估產品在實際工作環境中的成效。新客戶還能獲得價值 $300 美元的免費抵免額,可用於執行、測試及部署工作負載。
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the Firestore, Cloud Run, Pub/Sub, and Cloud Translation APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the Firestore, Cloud Run, Pub/Sub, and Cloud Translation APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  8. 前往 Google Cloud 控制台,並在 Cloud Shell 中開啟應用程式。

    前往 Cloud Shell

    Cloud Shell 可讓您直接在瀏覽器中使用指令列工具存取雲端資源。在瀏覽器中開啟 Cloud Shell 並按一下 [Proceed] (繼續),將程式碼範例和變更內容下載至應用程式目錄。

  9. 在 Cloud Shell 中,將 gcloud 工具設定為使用您的 Google Cloud 專案:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

瞭解 Cloud Run 後端

您定義單一 PHP 函式 translateString,並設定 Cloud Run 服務,透過叫用這個函式來回應 Pub/Sub 訊息。

use Google\Cloud\Firestore\FirestoreClient;
use Google\Cloud\Firestore\Transaction;
use Google\Cloud\Translate\TranslateClient;


/**
 * @param array $data {
 *     The PubSub message data containing text and target language.
 *
 *     @type string $text
 *           The full text to translate.
 *     @type string $language
 *           The target language for the translation.
 * }
 */
function translateString(array $data)
{
    if (empty($data['language']) || empty($data['text'])) {
        throw new Exception('Error parsing translation data');
    }

    $firestore = new FirestoreClient();
    $translate = new TranslateClient();

    $translation = [
        'original' => $data['text'],
        'lang' => $data['language'],
    ];

    $docId = sprintf('%s:%s', $data['language'], base64_encode($data['text']));
    $docRef = $firestore->collection('translations')->document($docId);

    $firestore->runTransaction(
        function (Transaction $transaction) use ($translate, $translation, $docRef) {
            $snapshot = $transaction->snapshot($docRef);
            if ($snapshot->exists()) {
                return; // Do nothing if the document already exists
            }

            $result = $translate->translate($translation['original'], [
                'target' => $translation['lang'],
            ]);
            $transaction->set($docRef, $translation + [
                'translated' => $result['text'],
                'originalLang' => $result['source'],
            ]);
        }
    );

    echo "Done.";
}
  1. 函式必須匯入數個依附元件,才能連線至 Firestore 和 Translation。

    use Google\Cloud\Firestore\FirestoreClient;
    use Google\Cloud\Firestore\Transaction;
    use Google\Cloud\Translate\TranslateClient;
    
  2. Cloud Run 會先初始化 Firestore 和 Pub/Sub 用戶端。接著,剖析 Pub/Sub 訊息資料,取得要翻譯的文字和所需的目標語言。

    $firestore = new FirestoreClient();
    $translate = new TranslateClient();
    
    $translation = [
        'original' => $data['text'],
        'lang' => $data['language'],
    ];
  3. Translation API 會將字串翻譯成所需語言。

    $result = $translate->translate($translation['original'], [
        'target' => $translation['lang'],
    ]);
  4. 這項函式會為翻譯要求產生不重複的名稱,確保我們不會儲存任何重複的翻譯內容。接著,系統會在 Firestore 交易中進行翻譯,確保並行執行作業不會意外重複翻譯。

    $docId = sprintf('%s:%s', $data['language'], base64_encode($data['text']));
    $docRef = $firestore->collection('translations')->document($docId);
    
    $firestore->runTransaction(
        function (Transaction $transaction) use ($translate, $translation, $docRef) {
            $snapshot = $transaction->snapshot($docRef);
            if ($snapshot->exists()) {
                return; // Do nothing if the document already exists
            }
    
            $result = $translate->translate($translation['original'], [
                'target' => $translation['lang'],
            ]);
            $transaction->set($docRef, $translation + [
                'translated' => $result['text'],
                'originalLang' => $result['source'],
            ]);
        }
    );

建構及部署 Cloud Run 後端

  • backend 目錄中建構 Cloud Run 應用程式:

    gcloud builds submit backend/ \
      --tag gcr.io/PROJECT_ID/background-function
  • 使用上一個步驟中的映像檔標記,部署 Cloud Run 應用程式:

    gcloud run deploy background-processing-function --platform managed \
      --image gcr.io/PROJECT_ID/background-function --region REGION

    其中 REGIONGoogle Cloud 區域

  • 部署完成後,您會在指令輸出內容中看到已部署應用程式的網址。例如:

    Service [background-processing-function] revision [default-00002-vav] has been deployed and is serving 100 percent of traffic at https://default-c457u4v2ma-uc.a.run.app

    複製這個網址,以供下一個步驟使用。

設定 Pub/Sub 訂閱項目

每當訊息發布至 translate 主題時,Cloud Run 應用程式就會收到 Pub/Sub 傳送的訊息。

內建的驗證檢查機制可確保 Pub/Sub 訊息包含來自服務帳戶的有效授權權杖,該帳戶有權叫用 Cloud Run 後端。

接下來的步驟會逐步說明如何設定 Pub/Sub 主題、訂閱項目和服務帳戶,以便對 Cloud Run 後端進行已驗證的呼叫。如要進一步瞭解這項整合功能,請參閱「服務對服務驗證」。

  1. 建立主題 translate,用於發布新的翻譯要求:

    gcloud pubsub topics create translate
    
  2. 啟用專案,建立 Pub/Sub 驗證權杖:

    gcloud projects add-iam-policy-binding PROJECT_ID \
         --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
         --role=roles/iam.serviceAccountTokenCreator

    其中 PROJECT_NUMBER 是您的 Google Cloud 專案編號,可透過執行 gcloud projects describe PROJECT_ID | grep projectNumber 找出。

  3. 建立或選取服務帳戶,代表 Pub/Sub 訂閱身分。

    gcloud iam service-accounts create cloud-run-pubsub-invoker \
         --display-name "Cloud Run Pub/Sub Invoker"

    注意:您可以使用 cloud-run-pubsub-invoker,或將其改成不同於 Google Cloud 專案中其他主題的名稱。

  4. 授予叫用端服務帳戶權限,以叫用您的 background-processing-function 服務:

    gcloud run services add-iam-policy-binding background-processing-function \
       --member=serviceAccount:cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com \
       --role=roles/run.invoker  --platform managed --region REGION

    系統可能需要數分鐘的時間才能傳達 Identity and Access Management 的變更,同時您可能會在服務記錄中看到 HTTP 403 錯誤。

  5. 使用服務帳戶建立 Pub/Sub 訂閱項目:

    gcloud pubsub subscriptions create run-translate-string --topic translate \
       --push-endpoint=CLOUD_RUN_URL \
       --push-auth-service-account=cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com

    其中 CLOUD_RUN_URL 是您在建構及部署後端後複製的 HTTPS 網址。

    --push-account-service-account 標記的作用是啟動 Pub/Sub 推送功能,以進行驗證和授權

    系統會自動註冊 Cloud Run 服務網域,以搭配 Pub/Sub 訂閱使用。

瞭解應用程式

網頁應用程式有兩個主要元件:

  • 處理網路要求的 PHP HTTP 伺服器。伺服器具有以下兩個端點:
    • /:列出所有現有譯文,並顯示使用者可用來提交新翻譯要求的表單。
    • /request-translation:系統會將提交的表單傳送到這個端點,並將要求發布到 Pub/Sub 以進行非同步翻譯。
  • 由 PHP 伺服器填入現有譯文的 HTML 範本。

HTTP 伺服器

  • app 目錄中,index.php 會先設定 Lumen 應用程式並註冊 HTTP 處理常式:

    $app = new Laravel\Lumen\Application(__DIR__);
    $app->router->group([
    ], function ($router) {
        require __DIR__ . '/routes/web.php';
    });
    $app->run();
  • 索引處理常式 (/) 可從 Firestore 取得所有現有的譯文,並使用清單算繪範本:

    /**
     * Homepage listing all requested translations and their results.
     */
    $router->get('/', function (Request $request) use ($projectId) {
        $firestore = new FirestoreClient([
            'projectId' => $projectId,
        ]);
        $translations = $firestore->collection('translations')->documents();
        return view('home', ['translations' => $translations]);
    });
  • /request-translation 註冊的翻譯要求處理常式會剖析提交的 HTML 表單、驗證要求,並向 Pub/Sub 發布訊息:

    /**
     * Endpoint which publishes a PubSub request for a new translation.
     */
    $router->post('/request-translation', function (Request $request) use ($projectId) {
        $acceptableLanguages = ['de', 'en', 'es', 'fr', 'ja', 'sw'];
        if (!in_array($lang = $request->get('lang'), $acceptableLanguages)) {
            throw new Exception('Unsupported Language: ' . $lang);
        }
        if (!$text = $request->get('v')) {
            throw new Exception('No text to translate');
        }
        $pubsub = new PubSubClient([
            'projectId' => $projectId,
        ]);
        $topic = $pubsub->topic('translate');
        $topic->publish(['data' => json_encode([
            'language' => $lang,
            'text' => $text,
        ])]);
    
        return '';
    });

HTML 範本

HTML 範本是向使用者顯示 HTML 網頁的基礎,可讓使用者查看先前的翻譯內容並要求新的內容。範本是由 HTTP 伺服器填入現有的翻譯清單。

  • HTML 範本的 <head> 元素包括頁面的中繼資料、樣式表和 JavaScript:

    該頁面提取 Material Design Lite (MDL) CSS 和 JavaScript 資產。MDL 可讓您為網站加入質感設計外觀和風格。

    該頁面使用 JQuery 等待文件完成載入並設定表單提交處理常式。每次提交要求翻譯的表單時,頁面都會進行最低限度的表單驗證以檢查其中的值並非空白,然後將非同步要求發至 /request-translation 端點。

    最後會出現 MDL Snackbar,以表示要求為成功或發生錯誤。

  • 網頁的 HTML 內文會使用 MDL 版面配置和多個 MDL 元件來顯示翻譯清單,以及要求其他翻譯的表單:
    <body>
      <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
        <header class="mdl-layout__header">
          <div class="mdl-layout__header-row">
            <!-- Title -->
            <span class="mdl-layout-title">Translate with Background Processing</span>
          </div>
        </header>
        <main class="mdl-layout__content">
          <div class="page-content">
            <div class="mdl-grid">
              <div class="mdl-cell mdl-cell--1-col"></div>
              <div class="mdl-cell mdl-cell--3-col">
                <form id="translate-form" class="translate-form">
                  <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                    <input class="mdl-textfield__input" type="text" id="v" name="v">
                    <label class="mdl-textfield__label" for="v">Text to translate...</label>
                  </div>
                  <select class="mdl-textfield__input lang" name="lang">
                    <option value="de">de</option>
                    <option value="en">en</option>
                    <option value="es">es</option>
                    <option value="fr">fr</option>
                    <option value="ja">ja</option>
                    <option value="sw">sw</option>
                  </select>
                  <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent" type="submit"
                      name="submit">Submit</button>
                </form>
              </div>
              <div class="mdl-cell mdl-cell--8-col">
                <table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
                  <thead>
                    <tr>
                      <th class="mdl-data-table__cell--non-numeric"><strong>Original</strong></th>
                      <th class="mdl-data-table__cell--non-numeric"><strong>Translation</strong></th>
                    </tr>
                  </thead>
                  <tbody>
                  <?php foreach ($translations as $translation): ?>
                    <tr>
                      <td class="mdl-data-table__cell--non-numeric">
                        <span class="mdl-chip mdl-color--primary">
                          <span class="mdl-chip__text mdl-color-text--white"><?= $translation['originalLang'] ?></span>
                        </span>
                      <?= $translation['original'] ?>
                      </td>
                      <td class="mdl-data-table__cell--non-numeric">
                        <span class="mdl-chip mdl-color--accent">
                          <span class="mdl-chip__text mdl-color-text--white"><?= $translation['lang'] ?></span>
                        </span>
                        <?= $translation['translated'] ?>
                      </td>
                    </tr>
                  <?php endforeach ?>
                  </tbody>
                </table>
                <br/>
                <button class="mdl-button mdl-js-button mdl-button--raised" type="button" onClick="window.location.reload();">Refresh</button>
              </div>
            </div>
          </div>
          <div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar" id="snackbar">
            <div class="mdl-snackbar__text mdl-color-text--black"></div>
            <button type="button" class="mdl-snackbar__action"></button>
          </div>
        </main>
      </div>
    </body>
    </html>
    

在 Cloud Shell 中執行應用程式

請先安裝依附元件並在本機運作執行網頁應用程式,再嘗試部署。

  1. 首先,請使用 Composer 安裝依附元件。您必須安裝 PHP 適用的 gRPC 擴充功能,而 Cloud Shell 已預先安裝這項擴充功能。

    composer install -d app
    
  2. 接著,執行 PHP 內建的網路伺服器,提供應用程式服務:

    APP_DEBUG=true php -S localhost:8080 -t app
    

    APP_DEBUG=true 旗標會顯示發生的任何例外狀況。

  3. 按一下 Cloud Shell 中的 [Web preview] (網頁預覽),然後選取 [Preview on port 8080] (透過以下通訊埠預覽:8080)。這會開啟新視窗,顯示執行中的應用程式。

部署網頁應用程式

您可以使用 App Engine 標準環境以建構及部署應用程式,即使是處在高負載和大量資料的情況下,應用程式仍會穩定可靠地執行。

本教學課程使用 App Engine 標準環境來部署 HTTP 前端。

app.yaml 會設定 App Engine 應用程式:

runtime: php73

env_variables:
  APP_DEBUG: true
  LOG_CHANNEL: stderr
  APP_STORAGE: /tmp
  • app.yaml 檔案所在的相同目錄中,將您的應用程式部署至 App Engine 標準環境:
    gcloud app deploy

測試應用程式

部署 Cloud Run 函式和 App Engine 應用程式後,請試著要求翻譯。

  1. 如要在瀏覽器中查看應用程式,請輸入下列網址:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    更改下列內容:

    網頁上會包含空白翻譯清單及要求新翻譯的表單。

  2. 要翻譯的文字欄位中,輸入一些要翻譯的文字,例如 Hello, World
  3. 從下拉式清單中選取翻譯文字的目標語言。
  4. 按一下 [Submit] (提交)
  5. 如要重新整理頁面,請按一下「重新整理」圖示 。翻譯清單中會新增一列。如果未看到譯文,請等待幾秒鐘,然後再試一次。如果您還是沒看到譯文,請參閱下一節說明如何除錯應用程式。

應用程式偵錯

如果您無法連線至您的 App Engine 應用程式,或是沒有看到新的譯文,請檢查下列事項:

  1. 檢查 gcloud 部署指令是否順利完成,且未輸出任何錯誤。如果發生錯誤 (例如 message=Build failed),請修正錯誤,並再次嘗試建構及部署 Cloud Run 應用程式,以及部署 App Engine 應用程式
  2. 前往 Google Cloud 控制台的「Logs Explorer」頁面。

    前往「Logs Explorer」(記錄檔探索工具) 頁面

    1. 在「Recently selected resources」(最近選取的資源) 下拉式清單中,按一下「GAE Application」(GAE 應用程式),然後按一下「All module_id」(所有 module_id)。系統隨即會顯示您造訪應用程式時的要求清單。如果您沒看見要求清單,請確認您已從下拉式清單中選取 [All module_id] (所有 module_id)。如果您在 Google Cloud 控制台上看到錯誤訊息,請檢查應用程式的程式碼是否與「瞭解網頁應用程式」一節中的程式碼相符。
    2. 在「Recently selected resources」(最近選取的資源) 下拉式清單中,按一下「Cloud Run Revision」(Cloud Run 修訂版本),然後按一下「All logs」(所有記錄)。您應該會看到傳送至已部署應用程式網址的 POST 要求。如果沒有列出,請檢查 Cloud Run 和 App Engine 應用程式是否使用相同的 Pub/Sub 主題,以及是否存在要推送至 Cloud Run 端點的 Pub/Sub 訂閱項目。

清除所用資源

為避免因為本教學課程所用資源,導致系統向 Google Cloud 帳戶收取費用,請刪除含有相關資源的專案,或者保留專案但刪除個別資源。

刪除 Google Cloud 專案

  1. 前往 Google Cloud 控制台的「Manage resources」(管理資源) 頁面。

    前往「Manage resources」(管理資源)

  2. 在專案清單中選取要刪除的專案,然後點選「Delete」(刪除)
  3. 在對話方塊中輸入專案 ID,然後按一下 [Shut down] (關閉) 以刪除專案。

刪除教學課程資源

  1. 刪除您在本教學課程中建立的 App Engine 應用程式:

    1. 前往 Google Cloud 控制台的 App Engine「Versions」(版本) 頁面。

      前往「版本」

    2. 勾選您要刪除的非預設應用程式版本的核取方塊。
    3. 如要刪除應用程式版本,請按一下 「刪除」

  2. 刪除您在本教學課程中部署的 Cloud Run 服務:

    gcloud run services delete background-processing-function

    您也可以從Google Cloud 控制台刪除 Cloud Run 服務。

  3. 刪除在本教學課程中建立的其他 Google Cloud 資源:

後續步驟