Generating Excel sheet with dynamic column names
Tags: excel, php
Published: 2021-01-18 13:29:25

---
title: Generating Excel sheet with dynamic column names
published: true
description: Forget about naming Excel columns using letters and use this technique instead.
tags: excel, php
cover_image: https://atrandafir.dev/img/posts/excel-dynamic-columns/dynamic-excel-columns-php.png
---

## The problem

When we export an Excel sheet from `PHP`, or other language, the most common use case is to export a table from the database, and some of the times it also contains some information in the header, and a footer with some totals.

Bottom line, we have a table, and some columns.

This is how you'd go normally, by placing manually each column letter in the different parts of the document, such as:  

- Header
- Rows
- Styling

```php
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Normal way of naming Excel columns');

$i=3;

// Header
$sheet->setCellValue('A'.$i, 'Name');
$sheet->setCellValue('B'.$i, 'Phone');
$sheet->setCellValue('C'.$i, 'City');

// Rows
foreach ($rows as $row) {
  $i++;
  $sheet->setCellValue('A'.$i, $row['name']);
  $sheet->setCellValue('B'.$i, $row['phone']);
  $sheet->setCellValue('C'.$i, $row['city']);
}

// Styling
$lastRowIndex=$i;

$sheet->getStyle('A3:C'.$lastRowIndex)->applyFromArray([
    'borders' => [
        'allBorders' => [
            'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
        ],
    ],
]);

$sheet->getStyle('A3:C3')->getFill()
    ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
    ->getStartColor()->setARGB('FFA0A0A0');
    
$sheet->getColumnDimension('A')->setWidth(12);
$sheet->getColumnDimension('B')->setWidth(25);
$sheet->getColumnDimension('C')->setWidth(25);

$writer = new Xlsx($spreadsheet);
$writer->save('normal.xlsx');
```

The problem with this approach is that if you want to add new columns, you'll have to spend some time renaming all the letters. And you'll also have to remember which letter you have assigned to each column when you apply styling.

So if you want to add the column `Company` after `Phone`, you'll have to put it on the letter `C`, and rename the next column `C` to `D`, and do this in renaming everywhere, not just in the header, also in the rows and in the styling, etc.

You'd say it's a quick task, but..   

- What if you had 10-20 columns or more? 
- What if you wanted to have some cases when you show a column and other cases when you want to hide it based on user preference when exporting?

## The solution

Okay it might not be the best solution, I'm sure others might think of better ways to do it but this is one way to avoid spending lots of time renaming the columns.

```php
// Load the column names helper
require 'ExCol.php';

ExCol::reset(); // reset mapping before using

$show_company_column=true;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Dynamic way of naming Excel columns');

$i=3;

// Header
$sheet->setCellValue(ExCol::get('name', $i), 'Name');
$sheet->setCellValue(ExCol::get('phone', $i), 'Phone');
if ($show_company_column) {
  $sheet->setCellValue(ExCol::get('company', $i), 'Company');
}
$sheet->setCellValue(ExCol::get('city', $i), 'City');

// Rows
foreach ($rows as $row) {
  $i++;
  $sheet->setCellValue(ExCol::get('name', $i), $row['name']);
  $sheet->setCellValue(ExCol::get('phone', $i), $row['phone']);
  if ($show_company_column) {
    $sheet->setCellValue(ExCol::get('company', $i), $row['company']);
  }
  $sheet->setCellValue(ExCol::get('city', $i), $row['city']);
}

$lastColLetter=ExCol::getLast();

// Styling
$lastRowIndex=$i;

$sheet->getStyle('A3:'.$lastColLetter.$lastRowIndex)->applyFromArray([
    'borders' => [
        'allBorders' => [
            'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
        ],
    ],
]);

$sheet->getStyle("A3:{$lastColLetter}3")->getFill()
    ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
    ->getStartColor()->setARGB('FFA0A0A0');
    
$sheet->getColumnDimension(ExCol::get('name'))->setWidth(12);
if ($show_company_column) {
  $sheet->getColumnDimension(ExCol::get('company'))->setWidth(35);
}
$sheet->getColumnDimension(ExCol::get('phone'))->setWidth(25);
$sheet->getColumnDimension(ExCol::get('city'))->setWidth(25);

$writer = new Xlsx($spreadsheet);
$writer->save('dynamic.xlsx');
```

So how does it work?  

Well, we have created a small helper class named `ExCol`.  

_I know, I could have found a better name but at least it is short._

```php
<?php
    
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
    
class ExCol {
  
  static private $_map=array();
  
  static public function get($col, $row=null) {
    if (!in_array($col, self::$_map)) {
      self::$_map[]=$col;
    }
    $index = array_search($col, self::$_map);
    $columnLetter = Coordinate::stringFromColumnIndex($index + 1);
    return $columnLetter.($row?$row:null);
  }
  
  static public function getLast() {
    return Coordinate::stringFromColumnIndex(count(self::$_map));
  }

  static public function reset() {
    self::$_map=array();
  }
  
}
```

With this class, every time we need a letter for a column, we just call the method `ExCol::get()`.  

Examples:  

```php
ExCol::get('my_first_column'); // Returns A
ExCol::get('my_second_column'); // Returns B
ExCol::get('my_third_column'); // Returns C
ExCol::get('my_third_column', 2); // Returns C2
```

Optionally we can also pass a `$row` number so instead of getting back `C`, we'll get `C2` and avoid having to use concatenation.

So the class stores statically an index of the columns requested in the order they were requested. And based on the index assigned to each column, it will return its letter, by using `PhpSpreadsheet`'s `Coordinate::stringFromColumnIndex()` method.

So let's go over the new parts in the code.  

If we use the helper on a script that is used to generate multiple Excel files, we might want to call the `reset()` method to clear the index just in case so it won't contain the columns of the previous file.

```php
ExCol::reset(); // reset mapping before using
```

We then define a variable for showing or hiding one of the columns:  

```php
$show_company_column=true;
```

And when printing the header, we only include that column if required:  

```php
// Header
$sheet->setCellValue(ExCol::get('name', $i), 'Name');
$sheet->setCellValue(ExCol::get('phone', $i), 'Phone');
if ($show_company_column) {
  $sheet->setCellValue(ExCol::get('company', $i), 'Company');
}
$sheet->setCellValue(ExCol::get('city', $i), 'City');
```

Same happens when looping the rows and with the styling:  

```php
// Rows
foreach ($rows as $row) {
  $i++;
  $sheet->setCellValue(ExCol::get('name', $i), $row['name']);
  $sheet->setCellValue(ExCol::get('phone', $i), $row['phone']);
  if ($show_company_column) {
    $sheet->setCellValue(ExCol::get('company', $i), $row['company']);
  }
  $sheet->setCellValue(ExCol::get('city', $i), $row['city']);
}
```

So you can see we're dynamically including one column, so if we only put 3 columns we'll the helper will give us `A`, `B` and `C`, but if we put 4, we'll get `A`, `B`, `C`, `D`.

If you want to control dynamically the columns to show, or you need to add more columns or remove some, there will be no need for renaming anything as it will all happen automatically.

Full source code here:  

https://github.com/atrandafir/excel-dynamic-columns

Original post at:  

https://atrandafir.dev/post/generating-excel-sheet-dynamic-column-names