在MS Excel和其他电子表格格式中,有两种命名范围:命名范围(Named Ranges)和命名公式(Named Formulae)。它们可以为您的电子表格添加很多强大的功能,但需要正确使用。
所有在此文档中显示的代码示例都可以在/samples/DefinedNames
文件夹中找到。
命名范围
命名范围为单元格或单元格范围提供一个名称引用。然后,您可以在公式中通过该名称引用该单元格或单元格范围。
作为示例,我将创建一个简单的计算器,将税加到价格上。
// 设置一些基本数据
$worksheet->setCellValue('A1', 'Tax Rate:')->setCellValue('B1', '=19%')->setCellValue('A3', 'Net Price:')->setCellValue('B3', 12.99)->setCellValue('A4', 'Tax:')->setCellValue('A5', 'Price including Tax:');
// 定义命名范围
$spreadsheet->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange('TAX_RATE', $worksheet, '=$B$1'));
$spreadsheet->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange('PRICE', $worksheet, '=$B$3'));
// 在公式中引用定义的名称
$worksheet->setCellValue('B4', '=PRICE*TAX_RATE')->setCellValue('B5', '=PRICE*(1+TAX_RATE)');
echo sprintf(
'With a Tax Rate of %.2f and a net price of %.2f, Tax is %.2f and the gross price is %.2f',
$worksheet->getCell('B1')->getCalculatedValue(),
$worksheet->getCell('B3')->getValue(),
$worksheet->getCell('B4')->getCalculatedValue(),
$worksheet->getCell('B5')->getCalculatedValue()
), PHP_EOL;
/samples/DefinedNames/SimpleNamedRange.php
这使得生成的电子表格中的公式在查看时更易于理解。使用这些命名范围(为单元格提供有意义的人类可读名称)可以明确公式的目的。我们无需查找单元格B2
,就可以知道它是什么。如果税率更改为16%,则我们只需更改单元格B1
的值为新的税率(=16%
),或者如果我们想要计算不同净价下的税收费用,那么这将立即反映在引用那些命名范围的所有计算中。无论我在工作表中使用哪个命名范围,它始终引用单元格B1
。实际上,由于我们在定义名称时指定了一个工作表,因此该名称在电子表格中的任何工作表内都是可用的,并且始终表示此工作表内的单元格B2
(但请参阅命名范围范围以下的注释)。
绝对命名范围
在上面的示例中,当我定义命名范围的值(例如='=$B$1'
),我在行和列两侧使用了美元符号。这使命名范围成为绝对引用。
另一个示例:
// 为时间表设置一些基本数据
$worksheet->setCellValue('A1', 'Charge Rate/hour:')->setCellValue('B1', '7.50')->setCellValue('A3', 'Date')->setCellValue('B3', 'Hours')->setCellValue('C3', 'Charge');
// 定义命名范围使用绝对单元格引用
$spreadsheet->addNamedRange(new NamedRange('CHARGE_RATE', $worksheet, '=$B$1'));
$workHours = [
'2020-0-06' => 7.5,
'2020-0-07' => 7.25,
'2020-0-08' => 6.5,
'2020-0-09' => 7.0,
'2020-0-10' => 5.5,
];
// 填充时间表
$startRow = 4;
$row = $startRow;
foreach ($workHours as $date => $hours) {
$worksheet->setCellValue("A{$row}", $date)->setCellValue("B{$row}", $hours)->setCellValue("C{$row}", "=B{$row}*CHARGE_RATE");
$row++;
}
$endRow = $row - 1;
++$row;
$worksheet->setCellValue("B{$row}", "=SUM(B{$startRow}:B{$endRow})")->setCellValue("C{$row}", "=SUM(C{$startRow}:C{$endRow})"); // 注意这里的语法错误!应该使用 `=>` 而不是 `:`。这个例子仅用于演示目的。实际应用中请不要使用这种语法。以下是修复后的代码:
```php
// ...省略其他代码...
相对命名范围
相对命名范围(Relative Named Ranges)在处理时间表时非常有用。在之前的示例中,我们使用了绝对引用(Absolute Reference)来表示每小时的费用,用于计算向客户收取的费用。使用B{$row}
这样的公式需要一定的心理敏捷性,以记住列B
是我们那天的工时。为什么不使用另一个名为HOURS_PER_DAY
的命名范围,这样公式就更容易阅读和有意义呢?
但是,如果我们为HOURS_PER_DAY
使用一个绝对命名范围,那么我们需要为每一天(如MONDAY_HOURS_PER_DAY
,TUESDAY_HOURS_PER_DAY
等)定义一个不同的命名范围;如果我们保持一个月度时间表,我们将不得不为每个月的每一天定义一个不同的命名范围…这比值得麻烦,很快就会变得难以管理。
这就是相对命名范围非常有用的地方。
以下是一个使用相对命名范围的时间表示例:
// 设置一些基本数据
$worksheet
->setCellValue('A1', '计费率/小时:')
->setCellValue('B1', '7.50')
->setCellValue('A3', '日期')
->setCellValue('B3', '工时')
->setCellValue('C3', '费用');
// 定义命名范围
// CHARGE_RATE 是始终指向单元格 B1 的绝对单元格引用
$spreadsheet->addNamedRange(new NamedRange('CHARGE_RATE', $worksheet, '=$B$1'));
// HOURS_PER_DAY 是相对于列 B,但指向该行使用的单元格的相对单元格引用
$spreadsheet->addNamedRange(new NamedRange('HOURS_PER_DAY', $worksheet, '=$B1'));
$workHours = [
'2020-0-06' => 7.5,
'2020-0-07' => 7.25,
'2020-0-08' => 6.5,
'2020-0-09' => 7.0,
'2020-0-10' => 5.5,
];
// 填充时间表
$startRow = 4;
$row = $startRow;
foreach ($workHours as $date => $hours) {
$worksheet
->setCellValue("A{$row}", $date)
->setCellValue("B{$row}", $hours)
->setCellValue("C{$row}", "=HOURS_PER_DAY*CHARGE_RATE");
$row++;
}
$endRow = $row - 1;
++$row;
$worksheet
->setCellValue("B{$row}", "=SUM(B{$startRow}:B{$endRow})")
->setCellValue("C{$row}", "=SUM(C{$startRow}:C{$endRow})");
命名范围的作用域
当我们定义一个命名范围时,需要指定一个工作表,然后该名称就可以在电子表格中的任何工作表中使用,并且始终表示指定工作表中的单元格或单元格范围。
// 为时间表设置一些基本数据
$worksheet
->setCellValue('A1', '每小时收费:')
->setCellValue('B1', '7.50');
// 在第一个工作表上为我们的收费率定义一个全局命名范围
// CHARGE_RATE是一个绝对单元格引用,总是指向单元格B1
// 由于它是全局定义的,因此仍然可以从电子表格中的任何工作表中使用它
$spreadsheet->addNamedRange( new NamedRange('CHARGE_RATE', $worksheet, '=$B$1') );
// 创建第二个工作表作为我们的客户时间表
$worksheet = $spreadsheet->addSheet(new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, 'Client Timesheet'));
// 定义命名范围
// HOURS_PER_DAY是一个相对单元格引用,总是指向列B,但指向使用的行中的单元格
$spreadsheet->addNamedRange( new NamedRange('HOURS_PER_DAY', $worksheet, '=$B1') );
// 为时间表设置一些基本数据
$worksheet
->setCellValue('A1', '日期')
->setCellValue('B1', '工时')
->setCellValue('C1', '费用');
$workHours = [
'2020-01-06' => 7.5,
'2020-01-07' => 7.25,
'2020-01-08' => 6.5,
'2020-01-09' => 7.0,
'2020-01-10' => 5.5,
];
// 填充时间表
$startRow = 2;
$row = $startRow;
foreach ($workHours as $date => $hours) {
$worksheet
->setCellValue("A{$row}", $date)
->setCellValue("B{$row}", $hours)
->setCellValue("C{$row}", "=HOURS_PER_DAY*CHARGE_RATE");
$row++;
}
$endRow = $row - 1;
// COLUMN_TOTAL是另一个相对单元格引用,总是指向相同的行范围,但指向使用的列中的单元格
$spreadsheet->addNamedRange( new NamedRange('COLUMN_DATA_VALUES', $worksheet, "=A\${$startRow}:A\${$endRow}") );
++$row;
$worksheet
->setCellValue("B{$row}", "=SUM(COLUMN_DATA_VALUES)")
->setCellValue("C{$row}", "=SUM(COLUMN_DATA_VALUES)");
echo sprintf(
'在每小时收费为 %.2f 的情况下工作了 %.2f 小时,客户应付 %.2f。',
$worksheet->getCell("B{$row}")->getCalculatedValue(),
$chargeRateCellValue = $spreadsheet
->getSheetByName($spreadsheet->getNamedRange('CHARGE_RATE')->getWorksheet()->getTitle())
->getCell($spreadsheet->getNamedRange('CHARGE_RATE')->getCellsInRange()[0])->getValue(),
$worksheet->getCell("C{$row}")->getCalculatedValue()
), PHP_EOL;
在 Excel 中,命名范围(Named Range)是一种可以局部或全局作用域化的单元格范围,这样可以在不同的工作表上使用相同的命名范围名称,并在不同的工作表上具有不同的值。这意味着你可以在不同的工作表上使用相同的命名范围名称,但具有不同的值。
例如,我们有一个时间表,可能为每个客户使用一个不同的工作表,而对于大多数客户,我们使用相同的每小时费率;但是对于一个特定的客户(可能是为朋友做的工作),我们使用较低的费率。
$clients = [
'Client #1 - Full Hourly Rate' => [
'2020-0-06' => 2.5,
'2020-0-07' => 2.25,
'2020-0-08' => 6.0,
'2020-0-09' => 3.0,
'2020-0-10' => 2.25,
],
'Client #2 - Full Hourly Rate' => [
'2020-0-06' => 1.5,
'2020-0-07' => 2.75,
'2020-0-08' => 0.0,
'2020-0-09' => 4.5,
'2020-0-10' => 3.5,
],
'Client #3 - Reduced Hourly Rate' => [
'2020-0-06' => 3.5,
'2020-0-07' => 2.5,
'2020-0-08' => 1.5,
'2020-0-09' => 0.0,
'2020-0-10' => 1.25,
],
];
foreach ($clients as $clientName => $workHours) {
$worksheet = $spreadsheet->addSheet(new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, $clientName));
// Set up some basic data for a timesheet
$worksheet
->setCellValue('A1', 'Charge Rate/hour:')
->setCellValue('B1', '7.50')
->setCellValue('A3', 'Date')
->setCellValue('B3', 'Hours')
->setCellValue('C3', 'Charge');
;
// Define named ranges
// CHARGE_RATE is an absolute cell reference that always points to cell B1
$spreadsheet->addNamedRange(new NamedRange('CHARGE_RATE', $worksheet, '=$B$1', true));
// 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
$spreadsheet->addNamedRange(new NamedRange('HOURS_PER_DAY', $worksheet, '=$B1', true));
// Populate the Timesheet
$startRow = 4;
$row = $startRow;
foreach ($workHours as $date => $hours) {
$worksheet
->setCellValue("A{$row}", $date)
->setCellValue("B{$row}", $hours)
->setCellValue("C{$row}", "=HOURS_PER_DAY*CHARGE_RATE");
$row++;
}
$endRow = $row - 1;
// 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
$spreadsheet->addNamedRange(new NamedRange('COLUMN_TOTAL', $worksheet, "=A\${$startRow}:A\${$endRow}", true));
++$row;
$worksheet
->setCellValue("B{$row}", "=SUM(COLUMN_TOTAL)")
->setCellValue("C{$row}", "=SUM(COLUMN_TOTAL)");
}
$spreadsheet->removeSheetByIndex(0);