在MS Excel和其他电子表格格式中,有两种命名范围:命名范围(Named Ranges)和命名公式(Named Formulae)。它们可以为您的电子表格添加很多强大的功能,但需要正确使用。

所有在此文档中显示的代码示例都可以在/samples/DefinedNames文件夹中找到。

命名范围

命名范围为单元格或单元格范围提供一个名称引用。然后,您可以在公式中通过该名称引用该单元格或单元格范围。

作为示例,我将创建一个简单的计算器,将税加到价格上。

  1. // 设置一些基本数据
  2. $worksheet->setCellValue('A1', 'Tax Rate:')->setCellValue('B1', '=19%')->setCellValue('A3', 'Net Price:')->setCellValue('B3', 12.99)->setCellValue('A4', 'Tax:')->setCellValue('A5', 'Price including Tax:');
  3. // 定义命名范围
  4. $spreadsheet->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange('TAX_RATE', $worksheet, '=$B$1'));
  5. $spreadsheet->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange('PRICE', $worksheet, '=$B$3'));
  6. // 在公式中引用定义的名称
  7. $worksheet->setCellValue('B4', '=PRICE*TAX_RATE')->setCellValue('B5', '=PRICE*(1+TAX_RATE)');
  8. echo sprintf(
  9. 'With a Tax Rate of %.2f and a net price of %.2f, Tax is %.2f and the gross price is %.2f',
  10. $worksheet->getCell('B1')->getCalculatedValue(),
  11. $worksheet->getCell('B3')->getValue(),
  12. $worksheet->getCell('B4')->getCalculatedValue(),
  13. $worksheet->getCell('B5')->getCalculatedValue()
  14. ), PHP_EOL;

/samples/DefinedNames/SimpleNamedRange.php

这使得生成的电子表格中的公式在查看时更易于理解。使用这些命名范围(为单元格提供有意义的人类可读名称)可以明确公式的目的。我们无需查找单元格B2,就可以知道它是什么。如果税率更改为16%,则我们只需更改单元格B1的值为新的税率(=16%),或者如果我们想要计算不同净价下的税收费用,那么这将立即反映在引用那些命名范围的所有计算中。无论我在工作表中使用哪个命名范围,它始终引用单元格B1。实际上,由于我们在定义名称时指定了一个工作表,因此该名称在电子表格中的任何工作表内都是可用的,并且始终表示此工作表内的单元格B2(但请参阅命名范围范围以下的注释)。

绝对命名范围

在上面的示例中,当我定义命名范围的值(例如='=$B$1'),我在行和列两侧使用了美元符号。这使命名范围成为绝对引用。

另一个示例:

  1. // 为时间表设置一些基本数据
  2. $worksheet->setCellValue('A1', 'Charge Rate/hour:')->setCellValue('B1', '7.50')->setCellValue('A3', 'Date')->setCellValue('B3', 'Hours')->setCellValue('C3', 'Charge');
  3. // 定义命名范围使用绝对单元格引用
  4. $spreadsheet->addNamedRange(new NamedRange('CHARGE_RATE', $worksheet, '=$B$1'));
  5. $workHours = [
  6. '2020-0-06' => 7.5,
  7. '2020-0-07' => 7.25,
  8. '2020-0-08' => 6.5,
  9. '2020-0-09' => 7.0,
  10. '2020-0-10' => 5.5,
  11. ];
  12. // 填充时间表
  13. $startRow = 4;
  14. $row = $startRow;
  15. foreach ($workHours as $date => $hours) {
  16. $worksheet->setCellValue("A{$row}", $date)->setCellValue("B{$row}", $hours)->setCellValue("C{$row}", "=B{$row}*CHARGE_RATE");
  17. $row++;
  18. }
  19. $endRow = $row - 1;
  20. ++$row;
  21. $worksheet->setCellValue("B{$row}", "=SUM(B{$startRow}:B{$endRow})")->setCellValue("C{$row}", "=SUM(C{$startRow}:C{$endRow})"); // 注意这里的语法错误!应该使用 `=>` 而不是 `:`。这个例子仅用于演示目的。实际应用中请不要使用这种语法。以下是修复后的代码:
  22. ```php
  23. // ...省略其他代码...

相对命名范围

相对命名范围(Relative Named Ranges)在处理时间表时非常有用。在之前的示例中,我们使用了绝对引用(Absolute Reference)来表示每小时的费用,用于计算向客户收取的费用。使用B{$row}这样的公式需要一定的心理敏捷性,以记住列B是我们那天的工时。为什么不使用另一个名为HOURS_PER_DAY的命名范围,这样公式就更容易阅读和有意义呢?

但是,如果我们为HOURS_PER_DAY使用一个绝对命名范围,那么我们需要为每一天(如MONDAY_HOURS_PER_DAY,TUESDAY_HOURS_PER_DAY等)定义一个不同的命名范围;如果我们保持一个月度时间表,我们将不得不为每个月的每一天定义一个不同的命名范围…这比值得麻烦,很快就会变得难以管理。

这就是相对命名范围非常有用的地方。

以下是一个使用相对命名范围的时间表示例:

  1. // 设置一些基本数据
  2. $worksheet
  3. ->setCellValue('A1', '计费率/小时:')
  4. ->setCellValue('B1', '7.50')
  5. ->setCellValue('A3', '日期')
  6. ->setCellValue('B3', '工时')
  7. ->setCellValue('C3', '费用');
  8. // 定义命名范围
  9. // CHARGE_RATE 是始终指向单元格 B1 的绝对单元格引用
  10. $spreadsheet->addNamedRange(new NamedRange('CHARGE_RATE', $worksheet, '=$B$1'));
  11. // HOURS_PER_DAY 是相对于列 B,但指向该行使用的单元格的相对单元格引用
  12. $spreadsheet->addNamedRange(new NamedRange('HOURS_PER_DAY', $worksheet, '=$B1'));
  13. $workHours = [
  14. '2020-0-06' => 7.5,
  15. '2020-0-07' => 7.25,
  16. '2020-0-08' => 6.5,
  17. '2020-0-09' => 7.0,
  18. '2020-0-10' => 5.5,
  19. ];
  20. // 填充时间表
  21. $startRow = 4;
  22. $row = $startRow;
  23. foreach ($workHours as $date => $hours) {
  24. $worksheet
  25. ->setCellValue("A{$row}", $date)
  26. ->setCellValue("B{$row}", $hours)
  27. ->setCellValue("C{$row}", "=HOURS_PER_DAY*CHARGE_RATE");
  28. $row++;
  29. }
  30. $endRow = $row - 1;
  31. ++$row;
  32. $worksheet
  33. ->setCellValue("B{$row}", "=SUM(B{$startRow}:B{$endRow})")
  34. ->setCellValue("C{$row}", "=SUM(C{$startRow}:C{$endRow})");

命名范围的作用域

当我们定义一个命名范围时,需要指定一个工作表,然后该名称就可以在电子表格中的任何工作表中使用,并且始终表示指定工作表中的单元格或单元格范围。

  1. // 为时间表设置一些基本数据
  2. $worksheet
  3. ->setCellValue('A1', '每小时收费:')
  4. ->setCellValue('B1', '7.50');
  5. // 在第一个工作表上为我们的收费率定义一个全局命名范围
  6. // CHARGE_RATE是一个绝对单元格引用,总是指向单元格B1
  7. // 由于它是全局定义的,因此仍然可以从电子表格中的任何工作表中使用它
  8. $spreadsheet->addNamedRange( new NamedRange('CHARGE_RATE', $worksheet, '=$B$1') );
  9. // 创建第二个工作表作为我们的客户时间表
  10. $worksheet = $spreadsheet->addSheet(new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, 'Client Timesheet'));
  11. // 定义命名范围
  12. // HOURS_PER_DAY是一个相对单元格引用,总是指向列B,但指向使用的行中的单元格
  13. $spreadsheet->addNamedRange( new NamedRange('HOURS_PER_DAY', $worksheet, '=$B1') );
  14. // 为时间表设置一些基本数据
  15. $worksheet
  16. ->setCellValue('A1', '日期')
  17. ->setCellValue('B1', '工时')
  18. ->setCellValue('C1', '费用');
  19. $workHours = [
  20. '2020-01-06' => 7.5,
  21. '2020-01-07' => 7.25,
  22. '2020-01-08' => 6.5,
  23. '2020-01-09' => 7.0,
  24. '2020-01-10' => 5.5,
  25. ];
  26. // 填充时间表
  27. $startRow = 2;
  28. $row = $startRow;
  29. foreach ($workHours as $date => $hours) {
  30. $worksheet
  31. ->setCellValue("A{$row}", $date)
  32. ->setCellValue("B{$row}", $hours)
  33. ->setCellValue("C{$row}", "=HOURS_PER_DAY*CHARGE_RATE");
  34. $row++;
  35. }
  36. $endRow = $row - 1;
  37. // COLUMN_TOTAL是另一个相对单元格引用,总是指向相同的行范围,但指向使用的列中的单元格
  38. $spreadsheet->addNamedRange( new NamedRange('COLUMN_DATA_VALUES', $worksheet, "=A\${$startRow}:A\${$endRow}") );
  39. ++$row;
  40. $worksheet
  41. ->setCellValue("B{$row}", "=SUM(COLUMN_DATA_VALUES)")
  42. ->setCellValue("C{$row}", "=SUM(COLUMN_DATA_VALUES)");
  43. echo sprintf(
  44. '在每小时收费为 %.2f 的情况下工作了 %.2f 小时,客户应付 %.2f。',
  45. $worksheet->getCell("B{$row}")->getCalculatedValue(),
  46. $chargeRateCellValue = $spreadsheet
  47. ->getSheetByName($spreadsheet->getNamedRange('CHARGE_RATE')->getWorksheet()->getTitle())
  48. ->getCell($spreadsheet->getNamedRange('CHARGE_RATE')->getCellsInRange()[0])->getValue(),
  49. $worksheet->getCell("C{$row}")->getCalculatedValue()
  50. ), PHP_EOL;

在 Excel 中,命名范围(Named Range)是一种可以局部或全局作用域化的单元格范围,这样可以在不同的工作表上使用相同的命名范围名称,并在不同的工作表上具有不同的值。这意味着你可以在不同的工作表上使用相同的命名范围名称,但具有不同的值。

例如,我们有一个时间表,可能为每个客户使用一个不同的工作表,而对于大多数客户,我们使用相同的每小时费率;但是对于一个特定的客户(可能是为朋友做的工作),我们使用较低的费率。

  1. $clients = [
  2. 'Client #1 - Full Hourly Rate' => [
  3. '2020-0-06' => 2.5,
  4. '2020-0-07' => 2.25,
  5. '2020-0-08' => 6.0,
  6. '2020-0-09' => 3.0,
  7. '2020-0-10' => 2.25,
  8. ],
  9. 'Client #2 - Full Hourly Rate' => [
  10. '2020-0-06' => 1.5,
  11. '2020-0-07' => 2.75,
  12. '2020-0-08' => 0.0,
  13. '2020-0-09' => 4.5,
  14. '2020-0-10' => 3.5,
  15. ],
  16. 'Client #3 - Reduced Hourly Rate' => [
  17. '2020-0-06' => 3.5,
  18. '2020-0-07' => 2.5,
  19. '2020-0-08' => 1.5,
  20. '2020-0-09' => 0.0,
  21. '2020-0-10' => 1.25,
  22. ],
  23. ];
  24. foreach ($clients as $clientName => $workHours) {
  25. $worksheet = $spreadsheet->addSheet(new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, $clientName));
  26. // Set up some basic data for a timesheet
  27. $worksheet
  28. ->setCellValue('A1', 'Charge Rate/hour:')
  29. ->setCellValue('B1', '7.50')
  30. ->setCellValue('A3', 'Date')
  31. ->setCellValue('B3', 'Hours')
  32. ->setCellValue('C3', 'Charge');
  33. ;
  34. // Define named ranges
  35. // CHARGE_RATE is an absolute cell reference that always points to cell B1
  36. $spreadsheet->addNamedRange(new NamedRange('CHARGE_RATE', $worksheet, '=$B$1', true));
  37. // HOURS_PER_DAY is a relative cell reference that always points to column B, but to a cell in the row where it is used
  38. $spreadsheet->addNamedRange(new NamedRange('HOURS_PER_DAY', $worksheet, '=$B1', true));
  39. // Populate the Timesheet
  40. $startRow = 4;
  41. $row = $startRow;
  42. foreach ($workHours as $date => $hours) {
  43. $worksheet
  44. ->setCellValue("A{$row}", $date)
  45. ->setCellValue("B{$row}", $hours)
  46. ->setCellValue("C{$row}", "=HOURS_PER_DAY*CHARGE_RATE");
  47. $row++;
  48. }
  49. $endRow = $row - 1;
  50. // COLUMN_TOTAL is another relative cell reference that always points to the same range of rows but to cell in the column where it is used
  51. $spreadsheet->addNamedRange(new NamedRange('COLUMN_TOTAL', $worksheet, "=A\${$startRow}:A\${$endRow}", true));
  52. ++$row;
  53. $worksheet
  54. ->setCellValue("B{$row}", "=SUM(COLUMN_TOTAL)")
  55. ->setCellValue("C{$row}", "=SUM(COLUMN_TOTAL)");
  56. }
  57. $spreadsheet->removeSheetByIndex(0);