返回

PHP 实现 Google Sheets API 跨表格复制工作表

php

PHP Google Sheets API: 跨电子表格复制工作表

在使用 Google Sheets API 处理电子表格时,将一个工作表从一个电子表格复制到另一个电子表格是一个常见的需求。本文将探讨使用 PHP Google Sheets API 实现这一功能的方法,并提供一些解决方案以及示例代码。

一、问题概述:权限和参数

要通过 API 复制工作表,本质上是调用了 Google Sheets API 的 spreadsheets.sheets.copyTo 方法。开发者在应用中创建客户端,并通过客户端执行该操作,常见错误的原因一般为权限问题或参数错误。权限问题很好排查,因此这里只说参数。

开发者在利用 spreadsheets.sheets.copyTo 方法的时候常常不能顺利运行。例如无法找到源表单或者无法读取目标工作表的 ID。因此,正确配置相关参数是确保该功能实现的核心步骤。

主要涉及三个参数:

  • 源电子表格 ID (sourceSpreadsheetFileId) : 包含要复制的工作表的电子表格的 ID。
  • 目标电子表格 ID (destinationSpreadsheetFileId) : 要将工作表复制到的目标电子表格的 ID。
  • 源工作表 ID 或索引(sourceTabRef) : 要复制的工作表的 ID。注意,这个参数必须是工作表的 ID,而不是工作表的名称或索引 。工作表 ID 是一个整数值,通常隐藏在电子表格 URL 中,但可通过 API 获取。直接传入工作表名称会返回 Invalid value at 'sheet_id' (TYPE_INT32) 错误,如果传入 0 或者 1,通常会提示 The sheet (0/1) does not exist.。这是因为表单的 ID 并不一定是 01,除非明确配置,否则该 ID 是一个长整数。

二、解决方案与实施步骤

以下是解决 “PHP Google Sheets copy sheet from one spreadsheet to another” 问题的几种解决方案,并附有相应的代码示例和操作步骤。

方案一: 使用正确的 sheetId

错误的主要原因往往是传入了错误的 sheetId。 通过接口获取工作表的 sheetId 是可靠方式。

步骤:

  1. 获取源电子表格中所有工作表的 sheetId:
    通过 spreadsheets.get 方法获取源电子表格的详细信息,其中包括每个工作表的 sheetId
  2. 选择要复制的工作表的 sheetId:
    从获取的信息中找到要复制的工作表的 sheetId。通常通过迭代得到特定 sheetId
  3. 调用 copyTo 方法:
    使用正确的源电子表格 ID、目标电子表格 ID 和源工作表的 sheetId 调用 copyTo 方法。

代码示例:

public function copySheetFromTo($sourceSpreadsheetFileId, $destinationSpreadsheetFileId, $sourceSheetTitle) {

    // 1. 获取源电子表格中所有工作表的详细信息
    $sourceSpreadsheet = $this->service->spreadsheets->get($sourceSpreadsheetFileId);
    $sheets = $sourceSpreadsheet->getSheets();

    // 2. 找到要复制的工作表的 sheetId
    $sourceSheetId = null;
    foreach ($sheets as $sheet) {
        if ($sheet->getProperties()->getTitle() == $sourceSheetTitle) {
            $sourceSheetId = $sheet->getProperties()->getSheetId();
            break;
        }
    }

    if ($sourceSheetId === null) {
        throw new Exception("Source sheet not found.");
    }

    // 3. 调用 copyTo 方法
    $requestBody = new Google_Service_Sheets_CopySheetToAnotherSpreadsheetRequest();
    $requestBody->setDestinationSpreadsheetId($destinationSpreadsheetFileId);
    $response = $this->service->spreadsheets_sheets->copyTo($sourceSpreadsheetFileId, $sourceSheetId, $requestBody);

    return $response;
}

// 调用示例
try {
    $POtemplateID = 'your_source_spreadsheet_id';
    $newdocument = $gsheets->newSpreadsheetDocument("ABC123456");
    $gsheets->copySheetFromTo($POtemplateID, $newdocument["ID"], 'template_sheet'); // 注意这里传入工作表名称
} catch (Exception $e) {
    echo 'Caught exception: ',  $e->getMessage(), "\n";
}

安全建议:
为了提升程序的健壮性,需要为程序添加一些错误处理。程序中可以捕获 Google_Service_Exception 类型的异常来处理 API 相关的错误。还可以通过传入目标 sheet 的名称来实现整个操作。通过表单名称而非 ID 的好处在于其不会因表单改动而发生编号变化。

方案二: 利用名称获取ID,简化客户端

步骤:

  1. 将功能实现在 wrapper 类:
    修改调用函数,只需传入工作表名称而无需关注 sheetId。这简化了客户端。
  2. 更新客户端调用:
    根据新的接口调整调用方式。

代码示例:
先看 wrapper

  //这个函数在方案一已定义,移动到 wrapper 最外层即可
  public function copySheetFromTo($sourceSpreadsheetFileId, $destinationSpreadsheetFileId, $sourceSheetTitle) {

      // 1. 获取源电子表格中所有工作表的详细信息
      $sourceSpreadsheet = $this->service->spreadsheets->get($sourceSpreadsheetFileId);
      $sheets = $sourceSpreadsheet->getSheets();

      // 2. 找到要复制的工作表的 sheetId
      $sourceSheetId = null;
      foreach ($sheets as $sheet) {
          if ($sheet->getProperties()->getTitle() == $sourceSheetTitle) {
              $sourceSheetId = $sheet->getProperties()->getSheetId();
              break;
          }
      }

      if ($sourceSheetId === null) {
          throw new Exception("Source sheet not found.");
      }

      // 3. 调用 copyTo 方法
      $requestBody = new Google_Service_Sheets_CopySheetToAnotherSpreadsheetRequest();
      $requestBody->setDestinationSpreadsheetId($destinationSpreadsheetFileId);
      $response = $this->service->spreadsheets_sheets->copyTo($sourceSpreadsheetFileId, $sourceSheetId, $requestBody);

      return $response;
  }

  public function newSpreadsheetDocument($title) {
    $newspread = new Google_Service_Sheets_Spreadsheet([
      'properties' => [
          'title' => $title
      ]
    ]);
    $newspread = $this->service->spreadsheets->create($newspread);
    $id = $newspread->getSpreadsheetId();
    return ["ID" => $newspread->getSpreadsheetId(), "url" => $newspread->getSpreadsheetUrl()];
  }

再来更新客户端。

    //更新后客户端的调用
    $POtemplateID = 'your_source_spreadsheet_id'; // 请替换为实际的源电子表格 ID
    $newdocument = $gsheets->newSpreadsheetDocument("ABC123456"); //创建目标表格
    $targetID =  $newdocument["ID"];//获取目标表格 ID
    $result = $gsheets->copySheetFromTo($POtemplateID, $targetID, "template_sheet"); // 请替换为实际的工作表名称
    var_dump($result);//查看结果

安全建议:
需要注意检查执行结果,并进行对应的后续操作。通常复制表单后需要返回新的 sheetID 以便进行下一步操作。
这些操作都应该尽可能地封装在 wrapper 内。wrapper 是整个 Google Sheets API 应用与 Google 服务器交互的核心组件。为了应用的安全,开发者不能轻易地暴露 wrapper 的细节。因此我们只提供了 wrapper 类的部分接口,而不将整个类的定义贴出来。这样做也保护了 token 等重要信息。一个管理良好的 wrapper 应该是高内聚的,这样才能保证整个项目的健壮性与安全性。