Skip to content

Instantly share code, notes, and snippets.

@DaveRandom
Last active December 15, 2015 03:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DaveRandom/5195058 to your computer and use it in GitHub Desktop.
Save DaveRandom/5195058 to your computer and use it in GitHub Desktop.
Small library for manipulating Excel spreadsheets using COM on a Windows machine with Microsoft Office is installed
<?php
spl_autoload_register(function($className) use($classMap) {
static $classMap;
if (!isset($classMap)) {
$classMap = array(
'excelcom\workbook' => __DIR__ . '/Workbook.php';,
'excelcom\worksheet' => __DIR__ . '/Worksheet.php';,
'excelcom\cell' => __DIR__ . '/Cell.php';,
);
}
$className = strtolower($className);
if (isset($classMap[$className])) {
require $classMap[$className];
}
});
<?php
/**
* Class for objects representing a single cell in an Excel worksheet
*
* PHP version 5.3/Windows, requires Microsoft Office Excel to be installed on the host system
*
* @package ExcelCOM
* @author Chris Wright <info@daverandom.com>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 1.0.0
*/
namespace ExcelCOM;
/**
* Class for objects representing a single cell in an Excel worksheet
*
* @package ExcelCOM
* @author Chris Wright <info@daverandom.com>
*/
class Cell
{
/**
* @var \VARIANT Object from COM representing the internal cell
*/
private $cell;
/**
* @var \DaveRandom\ExcelCOM\Worksheet Reference to the parent Worksheet object
*/
private $worksheet;
/**
* Constructor
*
* @param \VARIANT $cell Object from COM representing the internal cell
* @param \DaveRandom\ExcelCOM\Worksheet $workbook Reference to the parent Worksheet object
*/
public function __construct($cell, Worksheet $worksheet)
{
$this->cell = $cell;
$this->worksheet = $worksheet;
}
/**
* Called when the object is used in a string context
*
* @return string A string representation of the cell value
*/
public function __toString()
{
return (string) $this->getValue();
}
/**
* Get the parent Worksheet object
*
* @return \DaveRandom\ExcelCOM\Worksheet The parent Worksheet object
*/
public function getWorksheet()
{
return $this->worksheet;
}
/**
* Set the cell value
*
* @param mixed $value The new cell value
*
* @return \DaveRandom\ExcelCOM\Cell The current Cell object for method chaining
*
* @throws \RuntimeException When the set operation fails
*/
public function setValue($value)
{
try {
$this->cell->Value = $value;
} catch (\Exception $e) {
throw new \RuntimeException('Unable to set cell value', 0, $e);
}
return $this;
}
/**
* Get the cell value
*
* @return mixed The cell value
*/
public function getValue()
{
return $this->cell->Value;
}
}
<?php
// Simple example for setting the value of cell B4
use \DaveRandom\ExcelCOM\Workbook,
\DaveRandom\ExcelCOM\Worksheet,
\DaveRandom\ExcelCOM\Cell;
require 'ExcelCOM/bootstrap.php';
$workbook = new Workbook('path/to/file.xls');
// Get a worksheet by numeric index (1-indexed)
// Also accepts the string name of a sheet
$worksheet = $workbook->getWorksheet(1);
// Get Cell B4 and set the value
$cell = $worksheet->getCell('B4');
$cell->setValue('Hello world!');
// Save and close the workbook
$workbook->save();
$workbook->close();
<?php
/**
* Class for objects representing an Excel workbook
*
* PHP version 5.3/Windows, requires Microsoft Office Excel to be installed on the host system
*
* @package ExcelCOM
* @author Chris Wright <info@daverandom.com>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 1.0.0
*/
namespace ExcelCOM;
/**
* Class for objects representing an Excel workbook
*
* @package ExcelCOM
* @author Chris Wright <info@daverandom.com>
*/
class Workbook
{
/**
* @var bool Whether the file is currently open
*
* This is a work-around for the fact that you end up with an orphaned Excel.exe when
* the script dies before performing a graceful close
*/
private $open = false;
/**
* @var \COM Master COM object
*/
private $com;
/**
* @var \VARIANT Object from COM representing the internal workbook
*/
private $workbook;
/**
* @var \DaveRandom\ExcelCOM\Cell[] Cache of Cell objects, used to avoid creating the same Cell more than once
*/
private $worksheetCache = array();
/**
* Constructor
*
* @param string $path Path to the file on disk
*
* @throws \RuntimeException When the file does not exist or cannot be opened
*/
public function __construct($path)
{
$realPath = realpath($path);
if (!$realPath || !is_file($realPath)) {
throw new \RuntimeException("The specified file '$path' does not exist");
}
try {
$this->com = new \COM('Excel.Application');
$this->workbook = $this->com->Workbooks->Open($realPath);
} catch (\Exception $e) {
throw new \RuntimeException("Unable to open workbook '$realPath'", 0, $e);
}
// To avoid orphaned Excel.exe if a fatal error occurs. I know it looks odd to
// register a proper destructor as a shutdown function, but it really is required
// in order to circumvent the problem, I promise.
$this->open = true;
register_shutdown_function(array($this, '__destruct'));
}
/**
* Destructor
*
* Force a clean shutdown of Excel without saving changes
*/
public function __destruct()
{
$this->close(false);
}
/**
* Get an object representing a worksheet in the file
*
* @param string|int $sheetId The worksheet name or number. Numbers are 1-indexed. Returns the current active sheet if omitted.
*
* @return \DaveRandom\ExcelCOM\Worksheet The worksheet object
*
* @throws \RuntimeException When a reference to the worksheet could not be obtained
*/
public function getWorksheet($sheetId = null)
{
try {
if (isset($sheetId)) {
$sheet = $this->workbook->Sheets($sheetId);
} else {
$sheet = $this->workbook->ActiveSheet;
}
} catch (\Exception $e) {
throw new \RuntimeException('Unable to obtain a reference to the specified sheet', 0, $e);
}
if (!$sheet) {
throw new \RuntimeException('The workbook has no active worksheet');
}
return new Worksheet($sheet, $this);
}
/**
* Save any changes to the file
*
* @param string $as Path to save the file. Overwrites the source file if omitted.
*
* @return \DaveRandom\ExcelCOM\Workbook The current Workbook object for method chaining
*
* @throws \RuntimeException When the file could not be saved
*/
public function save($as = null)
{
try {
$this->com->Calculate();
if ($as !== null) {
$as = str_replace('/', '\\', $as);
$this->workbook->SaveAs($as);
} else {
$this->workbook->Save();
}
} catch (\Exception $e) {
throw new \RuntimeException('Unable to save workbook', 0, $e);
}
return $this;
}
/**
* Close the current workbook
*
* @param bool $save Save changes to source file if true.
*
* @throws \RuntimeException When the file could not be closed, this most likely indicates a problem saving.
*/
public function close($save = false)
{
if ($this->open) {
try {
$this->open = false;
$this->workbook->Close((bool) $save);
$this->workbook = $this->com = null;
} catch (\Exception $e) {
throw new \RuntimeException('Unable to close workbook', 0, $e);
}
}
}
}
<?php
/**
* Class for objects representing a single worksheet in an Excel workbook
*
* PHP version 5.3/Windows, requires Microsoft Office Excel to be installed on the host system
*
* @package ExcelCOM
* @author Chris Wright <info@daverandom.com>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 1.0.0
*/
namespace ExcelCOM;
/**
* Class for objects representing a single worksheet in an Excel workbook
*
* @package ExcelCOM
* @author Chris Wright <info@daverandom.com>
*/
class Worksheet
{
/**
* @var \VARIANT Object from COM representing the internal worksheet
*/
private $worksheet;
/**
* @var \DaveRandom\ExcelCOM\Workbook Reference to the parent Workbook object
*/
private $workbook;
/**
* @var \DaveRandom\ExcelCOM\Cell[] Cache of Cell objects, used to avoid creating the same Cell more than once
*/
private $cellCache = array();
/**
* Constructor
*
* @param \VARIANT $worksheet Object from COM representing the internal worksheet
* @param \DaveRandom\ExcelCOM\Workbook $workbook Reference to the parent Workbook object
*/
public function __construct($worksheet, Workbook $workbook)
{
$this->worksheet = $worksheet;
$this->workbook = $workbook;
}
/**
* Convert an alpha column reference to an integer
*
* @param string $x Alpha-string representing an X-coordinate in the worksheet
*
* @return int Numeric representation of input
*
* @throws \OutOfRangeException When the input does not represent a valid X-coordinate
*/
private function xAlphaToInt($x)
{
switch (strlen($x)) {
case 1:
return ord($x) - 64;
case 2:
return ((ord($x[0]) - 64) * 26) + ord($x[1]) - 64;
default:
throw new \OutOfRangeException('Invalid cell coordinate');
}
}
/**
* Get a Cell object from the internal cache, creating it if it doesn't yet exist
*
* @param int $x The X-coordinate of the cell
* @param int $y The Y-coordinate of the cell
*
* @return \DaveRandom\ExcelCOM\Cell The Cell object
*
* @throws \RuntimeException When a reference to the cell cannot be obtained
*/
private function getCellCached($x, $y)
{
if (!isset($this->cellCache[$x][$y])) {
try {
$cell = $this->worksheet->Cells($y, $x);
} catch (\Exception $e) {
throw new \RuntimeException('Unable to get reference to the specified cell', 0, $e);
}
$this->cellCache[$x][$y] = new Cell($cell, $this);
}
return $this->cellCache[$x][$y];
}
/**
* Get the parent Workbook object
*
* @return \DaveRandom\ExcelCOM\Workbook The parent Workbook object
*/
public function getWorkbook()
{
return $this->workbook;
}
/**
* Get a Cell object representing the cell at the specified coordinate
*
* @param string|int $x The X-coordinate of the cell or a full cell identifier string
* @param int $y The Y-coordinate of the cell, ignored if $x received a full cell indentifier
*
* @return \DaveRandom\ExcelCOM\Cell The Cell object
*
* @throws \OutOfRangeException When an invalid cell coordinate is requested
* @throws \RuntimeException When a reference to the cell cannot be obtained
*/
public function getCell($x, $y = null)
{
if (preg_match('/^(?P<x>[A-Z]+)(?P<y>\d+)?$/', strtoupper(trim($x)), $coords)) {
if (!empty($coords['y'])) {
$y = $coords['y'];
}
$x = $this->xAlphaToInt($coords['x']);
}
if (!isset($y)) {
throw new \OutOfRangeException('Invalid cell coordinate');
}
$x = (int) $x;
$y = (int) $y;
if (!$x || !$y || $x > 256 || $y > 65536) {
throw new \OutOfRangeException('Invalid cell coordinate');
}
return $this->getCellCached($x, $y);
}
/**
* Unprotect the worksheet
*
* @param string $password The password used to protect the sheet, no password if omitted
*
* @return \DaveRandom\ExcelCOM\Worksheet The current Worksheet object for method chaining
*
* @throws \RuntimeException When the unprotect operation fails
*/
public function unprotect($password = null)
{
try {
if (isset($password)) {
$this->worksheet->Unprotect($password);
} else {
$this->worksheet->Unprotect();
}
} catch (\Exception $e) {
throw new \RuntimeException('Unable to unprotect sheet', 0, $e);
}
return $this;
}
/**
* Protect the worksheet
*
* @param string $password The password used to protect the sheet, no password if omitted
*
* @return \DaveRandom\ExcelCOM\Worksheet The current Worksheet object for method chaining
*
* @throws \RuntimeException When the protect operation fails
*/
public function protect($password = null)
{
try {
if ($password !== null) {
$this->worksheet->Protect($password);
} else {
$this->worksheet->Protect();
}
} catch (\Exception $e) {
throw new \RuntimeException('Unable to protect sheet', 0, $e);
}
return $this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment