Refactor writers for better DI (#427)

This commit is a big refactor that improves the code organization.
It focuses on how dependencies are injected into the different classes. This is now done via some factories.

Also, the code is now built around entities (data model that only exposes getters and setters), managers (used to manage an entity) and helpers (used by the managers to perform some specific tasks).

The refactoring is not fully complete, as some dependencies are still hidden...
This commit is contained in:
Adrien Loison authored and GitHub committed 2017-05-29 22:18:40 +02:00
1 parent a366d0d0af
commit bc17311f5f
37 files changed
+1529 -1031

No files matched your search

@@ -0,0 +1,101 @@
<?php
namespace Box\Spout\Writer\ODS\Factory;
use Box\Spout\Common\Helper\StringHelper;
use Box\Spout\Writer\Common\Manager\OptionsManagerInterface;
use Box\Spout\Writer\Common\Options;
use Box\Spout\Writer\Factory\EntityFactory;
use Box\Spout\Writer\Factory\InternalFactoryInterface;
use Box\Spout\Writer\ODS\Helper\FileSystemHelper;
use Box\Spout\Writer\ODS\Helper\StyleHelper;
use Box\Spout\Writer\ODS\Manager\WorkbookManager;
use Box\Spout\Writer\ODS\Manager\WorksheetManager;
use \Box\Spout\Common\Escaper;
/**
* Class InternalFactory
* Factory for all useful types of objects needed by the ODS Writer
*
* @package Box\Spout\Writer\ODS\Factory
*/
class InternalFactory implements InternalFactoryInterface
{
/** @var EntityFactory */
private $entityFactory;
/**
* InternalFactory constructor.
*
* @param EntityFactory $entityFactory
*/
public function __construct(EntityFactory $entityFactory)
{
$this->entityFactory = $entityFactory;
}
/**
* @param OptionsManagerInterface $optionsManager
* @return WorkbookManager
*/
public function createWorkbookManager(OptionsManagerInterface $optionsManager)
{
$workbook = $this->entityFactory->createWorkbook();
$fileSystemHelper = $this->createFileSystemHelper($optionsManager);
$fileSystemHelper->createBaseFilesAndFolders();
$styleHelper = $this->createStyleHelper($optionsManager);
$worksheetManager = $this->createWorksheetManager($styleHelper);
return new WorkbookManager($workbook, $optionsManager, $worksheetManager, $styleHelper, $fileSystemHelper, $this->entityFactory);
}
/**
* @param StyleHelper $styleHelper
* @return WorksheetManager
*/
private function createWorksheetManager(StyleHelper $styleHelper)
{
$stringsEscaper = $this->createStringsEscaper();
$stringsHelper = $this->createStringHelper();
return new WorksheetManager($styleHelper, $stringsEscaper, $stringsHelper);
}
/**
* @param OptionsManagerInterface $optionsManager
* @return FileSystemHelper
*/
public function createFileSystemHelper(OptionsManagerInterface $optionsManager)
{
$tempFolder = $optionsManager->getOption(Options::TEMP_FOLDER);
return new FileSystemHelper($tempFolder);
}
/**
* @param OptionsManagerInterface $optionsManager
* @return StyleHelper
*/
private function createStyleHelper(OptionsManagerInterface $optionsManager)
{
$defaultRowStyle = $optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
return new StyleHelper($defaultRowStyle);
}
/**
* @return Escaper\ODS
*/
private function createStringsEscaper()
{
return Escaper\ODS::getInstance();
}
/**
* @return StringHelper
*/
private function createStringHelper()
{
return new StringHelper();
}
}
@@ -2,8 +2,10 @@
namespace Box\Spout\Writer\ODS\Helper;
use Box\Spout\Writer\Common\Helper\FileSystemWithRootFolderHelperInterface;
use Box\Spout\Writer\Common\Helper\ZipHelper;
use Box\Spout\Writer\ODS\Internal\Worksheet;
use Box\Spout\Writer\Entity\Worksheet;
use Box\Spout\Writer\ODS\Manager\WorksheetManager;
/**
* Class FileSystemHelper
@@ -12,7 +14,7 @@ use Box\Spout\Writer\ODS\Internal\Worksheet;
*
* @package Box\Spout\Writer\ODS\Helper
*/
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper implements FileSystemWithRootFolderHelperInterface
{
const APP_NAME = 'Spout';
const MIMETYPE = 'application/vnd.oasis.opendocument.spreadsheet';
@@ -172,11 +174,12 @@ EOD;
/**
* Creates the "content.xml" file under the root folder
*
* @param WorksheetManager $worksheetManager
* @param Worksheet[] $worksheets
* @param StyleHelper $styleHelper
* @return FileSystemHelper
*/
public function createContentFile($worksheets, $styleHelper)
public function createContentFile($worksheetManager, $worksheets, $styleHelper)
{
$contentXmlFileContents = <<<EOD
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -196,9 +199,9 @@ EOD;
foreach ($worksheets as $worksheet) {
// write the "<table:table>" node, with the final sheet's name
fwrite($contentXmlHandle, $worksheet->getTableElementStartAsString());
fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet));
$worksheetFilePath = $worksheet->getWorksheetFilePath();
$worksheetFilePath = $worksheet->getFilePath();
$this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle);
fwrite($contentXmlHandle, '</table:table>');
+2 -2
View File
@@ -2,7 +2,7 @@
namespace Box\Spout\Writer\ODS\Helper;
use Box\Spout\Writer\Common\Helper\AbstractStyleHelper;
use Box\Spout\Writer\Common\Helper\StyleHelperAbstract;
use Box\Spout\Writer\Style\BorderPart;
/**
@@ -11,7 +11,7 @@ use Box\Spout\Writer\Style\BorderPart;
*
* @package Box\Spout\Writer\ODS\Helper
*/
class StyleHelper extends AbstractStyleHelper
class StyleHelper extends StyleHelperAbstract
{
/** @var string[] [FONT_NAME] => [] Map whose keys contain all the fonts used */
protected $usedFontsSet = [];
-121
View File
@@ -1,121 +0,0 @@
<?php
namespace Box\Spout\Writer\ODS\Internal;
use Box\Spout\Writer\Common\Internal\AbstractWorkbook;
use Box\Spout\Writer\Common\Manager\OptionsManagerInterface;
use Box\Spout\Writer\Common\Options;
use Box\Spout\Writer\ODS\Helper\FileSystemHelper;
use Box\Spout\Writer\ODS\Helper\StyleHelper;
use Box\Spout\Writer\Common\Sheet;
/**
* Class Workbook
* Represents a workbook within a ODS file.
* It provides the functions to work with worksheets.
*
* @package Box\Spout\Writer\ODS\Internal
*/
class Workbook extends AbstractWorkbook
{
/**
* Maximum number of rows a ODS sheet can contain
* @see https://ask.libreoffice.org/en/question/8631/upper-limit-to-number-of-rows-in-calc/
*/
protected static $maxRowsPerWorksheet = 1048576;
/** @var \Box\Spout\Writer\ODS\Helper\FileSystemHelper Helper to perform file system operations */
protected $fileSystemHelper;
/** @var \Box\Spout\Writer\ODS\Helper\StyleHelper Helper to apply styles */
protected $styleHelper;
/**
* @param \Box\Spout\Writer\Common\Manager\OptionsManagerInterface $optionsManager Options manager
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
*/
public function __construct(OptionsManagerInterface $optionsManager)
{
parent::__construct($optionsManager);
$tempFolder = $optionsManager->getOption(Options::TEMP_FOLDER);
$this->fileSystemHelper = new FileSystemHelper($tempFolder);
$this->fileSystemHelper->createBaseFilesAndFolders();
$defaultRowStyle = $optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
$this->styleHelper = new StyleHelper($defaultRowStyle);
}
/**
* @return \Box\Spout\Writer\ODS\Helper\StyleHelper Helper to apply styles to ODS files
*/
protected function getStyleHelper()
{
return $this->styleHelper;
}
/**
* @return int Maximum number of rows/columns a sheet can contain
*/
protected function getMaxRowsPerWorksheet()
{
return self::$maxRowsPerWorksheet;
}
/**
* Creates a new sheet in the workbook. The current sheet remains unchanged.
*
* @return Worksheet The created sheet
* @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing
*/
public function addNewSheet()
{
$newSheetIndex = count($this->worksheets);
$sheet = new Sheet($newSheetIndex, $this->internalId);
$sheetsContentTempFolder = $this->fileSystemHelper->getSheetsContentTempFolder();
$worksheet = new Worksheet($sheet, $sheetsContentTempFolder);
$this->worksheets[] = $worksheet;
return $worksheet;
}
/**
* Closes the workbook and all its associated sheets.
* All the necessary files are written to disk and zipped together to create the ODS file.
* All the temporary files are then deleted.
*
* @param resource $finalFilePointer Pointer to the ODS that will be created
* @return void
*/
public function close($finalFilePointer)
{
/** @var Worksheet[] $worksheets */
$worksheets = $this->worksheets;
$numWorksheets = count($worksheets);
foreach ($worksheets as $worksheet) {
$worksheet->close();
}
// Finish creating all the necessary files before zipping everything together
$this->fileSystemHelper
->createContentFile($worksheets, $this->styleHelper)
->deleteWorksheetTempFolder()
->createStylesFile($this->styleHelper, $numWorksheets)
->zipRootFolderAndCopyToStream($finalFilePointer);
$this->cleanupTempFolder();
}
/**
* Deletes the root folder created in the temp folder and all its contents.
*
* @return void
*/
protected function cleanupTempFolder()
{
$xlsxRootFolder = $this->fileSystemHelper->getRootFolder();
$this->fileSystemHelper->deleteFolderRecursively($xlsxRootFolder);
}
}
@@ -0,0 +1,71 @@
<?php
namespace Box\Spout\Writer\ODS\Manager;
use Box\Spout\Writer\Common\Sheet;
use Box\Spout\Writer\Manager\WorkbookManagerAbstract;
use Box\Spout\Writer\ODS\Helper\FileSystemHelper;
use Box\Spout\Writer\ODS\Helper\StyleHelper;
/**
* Class WorkbookManager
* ODS workbook manager, providing the interfaces to work with workbook.
*
* @package Box\Spout\Writer\ODS\Manager
*/
class WorkbookManager extends WorkbookManagerAbstract
{
/**
* Maximum number of rows a ODS sheet can contain
* @see https://ask.libreoffice.org/en/question/8631/upper-limit-to-number-of-rows-in-calc/
*/
protected static $maxRowsPerWorksheet = 1048576;
/** @var WorksheetManager Object used to manage worksheets */
protected $worksheetManager;
/** @var FileSystemHelper Helper to perform file system operations */
protected $fileSystemHelper;
/** @var StyleHelper Helper to apply styles */
protected $styleHelper;
/** @var int Maximum number of columns among all the written rows */
protected $maxNumColumns = 1;
/**
* @return int Maximum number of rows/columns a sheet can contain
*/
protected function getMaxRowsPerWorksheet()
{
return self::$maxRowsPerWorksheet;
}
/**
* @param Sheet $sheet
* @return string The file path where the data for the given sheet will be stored
*/
public function getWorksheetFilePath(Sheet $sheet)
{
$sheetsContentTempFolder = $this->fileSystemHelper->getSheetsContentTempFolder();
return $sheetsContentTempFolder . '/sheet' . $sheet->getIndex() . '.xml';
}
/**
* Writes all the necessary files to disk and zip them together to create the final file.
*
* @param resource $finalFilePointer Pointer to the spreadsheet that will be created
* @return void
*/
protected function writeAllFilesToDiskAndZipThem($finalFilePointer)
{
$worksheets = $this->getWorksheets();
$numWorksheets = count($worksheets);
$this->fileSystemHelper
->createContentFile($this->worksheetManager, $worksheets, $this->styleHelper)
->deleteWorksheetTempFolder()
->createStylesFile($this->styleHelper, $numWorksheets)
->zipRootFolderAndCopyToStream($finalFilePointer);
}
}
@@ -1,147 +1,116 @@
<?php
namespace Box\Spout\Writer\ODS\Internal;
namespace Box\Spout\Writer\ODS\Manager;
use Box\Spout\Common\Exception\InvalidArgumentException;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Common\Helper\StringHelper;
use Box\Spout\Writer\Common\Cell;
use Box\Spout\Writer\Common\Helper\CellHelper;
use Box\Spout\Writer\Common\Internal\WorksheetInterface;
use Box\Spout\Writer\Entity\Worksheet;
use Box\Spout\Writer\Manager\WorksheetManagerInterface;
use Box\Spout\Writer\ODS\Helper\StyleHelper;
use Box\Spout\Writer\Style\Style;
/**
* Class Worksheet
* Represents a worksheet within a ODS file. The difference with the Sheet object is
* that this class provides an interface to write data
* Class WorksheetManager
* ODS worksheet manager, providing the interfaces to work with ODS worksheets.
*
* @package Box\Spout\Writer\ODS\Internal
* @package Box\Spout\Writer\ODS\Manager
*/
class Worksheet implements WorksheetInterface
class WorksheetManager implements WorksheetManagerInterface
{
/** @var \Box\Spout\Writer\Common\Sheet The "external" sheet */
protected $externalSheet;
/** @var string Path to the XML file that will contain the sheet data */
protected $worksheetFilePath;
/** @var StyleHelper Helper to work with styles */
private $styleHelper;
/** @var \Box\Spout\Common\Escaper\ODS Strings escaper */
protected $stringsEscaper;
private $stringsEscaper;
/** @var \Box\Spout\Common\Helper\StringHelper To help with string manipulation */
protected $stringHelper;
/** @var Resource Pointer to the temporary sheet data file (e.g. worksheets-temp/sheet1.xml) */
protected $sheetFilePointer;
/** @var int Maximum number of columns among all the written rows */
protected $maxNumColumns = 1;
/** @var int Index of the last written row */
protected $lastWrittenRowIndex = 0;
/** @var StringHelper String helper */
private $stringHelper;
/**
* @param \Box\Spout\Writer\Common\Sheet $externalSheet The associated "external" sheet
* @param string $worksheetFilesFolder Temporary folder where the files to create the ODS will be stored
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
* WorksheetManager constructor.
*
* @param StyleHelper $styleHelper
* @param \Box\Spout\Common\Escaper\ODS $stringsEscaper
* @param StringHelper $stringHelper
*/
public function __construct($externalSheet, $worksheetFilesFolder)
public function __construct(
StyleHelper $styleHelper,
\Box\Spout\Common\Escaper\ODS $stringsEscaper,
StringHelper $stringHelper)
{
$this->externalSheet = $externalSheet;
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
$this->stringsEscaper = \Box\Spout\Common\Escaper\ODS::getInstance();
$this->worksheetFilePath = $worksheetFilesFolder . '/sheet' . $externalSheet->getIndex() . '.xml';
$this->stringHelper = new StringHelper();
$this->startSheet();
$this->styleHelper = $styleHelper;
$this->stringsEscaper = $stringsEscaper;
$this->stringHelper = $stringHelper;
}
/**
* Prepares the worksheet to accept data
* The XML file does not contain the "<table:table>" node as it contains the sheet's name
* which may change during the execution of the program. It will be added at the end.
*
* @param Worksheet $worksheet The worksheet to start
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
*/
protected function startSheet()
public function startSheet(Worksheet $worksheet)
{
$this->sheetFilePointer = fopen($this->worksheetFilePath, 'w');
$this->throwIfSheetFilePointerIsNotAvailable();
$sheetFilePointer = fopen($worksheet->getFilePath(), 'w');
$this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
$worksheet->setFilePointer($sheetFilePointer);
}
/**
* Checks if the book has been created. Throws an exception if not created yet.
* Checks if the sheet has been sucessfully created. Throws an exception if not.
*
* @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
* @throws IOException If the sheet data file cannot be opened for writing
*/
protected function throwIfSheetFilePointerIsNotAvailable()
private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
{
if (!$this->sheetFilePointer) {
if (!$sheetFilePointer) {
throw new IOException('Unable to open sheet for writing.');
}
}
/**
* @return string Path to the temporary sheet content XML file
*/
public function getWorksheetFilePath()
{
return $this->worksheetFilePath;
}
/**
* Returns the table XML root node as string.
*
* @param Worksheet $worksheet
* @return string <table> node as string
*/
public function getTableElementStartAsString()
public function getTableElementStartAsString(Worksheet $worksheet)
{
$escapedSheetName = $this->stringsEscaper->escape($this->externalSheet->getName());
$tableStyleName = 'ta' . ($this->externalSheet->getIndex() + 1);
$externalSheet = $worksheet->getExternalSheet();
$escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
$tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
$tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
$tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $this->maxNumColumns . '"/>';
$tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $worksheet->getMaxNumColumns() . '"/>';
return $tableElement;
}
/**
* @return \Box\Spout\Writer\Common\Sheet The "external" sheet
*/
public function getExternalSheet()
{
return $this->externalSheet;
}
/**
* @return int The index of the last written row
*/
public function getLastWrittenRowIndex()
{
return $this->lastWrittenRowIndex;
}
/**
* Adds data to the worksheet.
* Adds data to the given worksheet.
*
* @param Worksheet $worksheet The worksheet to add the row to
* @param array $dataRow Array containing data to be written. Cannot be empty.
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style.
* @param Style $rowStyle Style to be applied to the row. NULL means use default style.
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
* @throws IOException If the data cannot be written
* @throws InvalidArgumentException If a cell value's type is not supported
*/
public function addRow($dataRow, $style)
public function addRow(Worksheet $worksheet, $dataRow, $rowStyle)
{
// $dataRow can be an associative array. We need to transform
// it into a regular array, as we'll use the numeric indexes.
$dataRowWithNumericIndexes = array_values($dataRow);
$styleIndex = ($style->getId() + 1); // 1-based
$styleIndex = ($rowStyle->getId() + 1); // 1-based
$cellsCount = count($dataRow);
$this->maxNumColumns = max($this->maxNumColumns, $cellsCount);
$data = '<table:table-row table:style-name="ro1">';
@@ -166,13 +135,14 @@ class Worksheet implements WorksheetInterface
$data .= '</table:table-row>';
$wasWriteSuccessful = fwrite($this->sheetFilePointer, $data);
$wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $data);
if ($wasWriteSuccessful === false) {
throw new IOException("Unable to write data in {$this->worksheetFilePath}");
throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
}
// only update the count if the write worked
$this->lastWrittenRowIndex++;
$lastWrittenRowIndex = $worksheet->getLastWrittenRowIndex();
$worksheet->setLastWrittenRowIndex($lastWrittenRowIndex + 1);
}
/**
@@ -184,7 +154,7 @@ class Worksheet implements WorksheetInterface
* @return string The cell XML content
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
*/
protected function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated)
private function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated)
{
$data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
@@ -228,14 +198,17 @@ class Worksheet implements WorksheetInterface
/**
* Closes the worksheet
*
* @param Worksheet $worksheet
* @return void
*/
public function close()
public function close(Worksheet $worksheet)
{
if (!is_resource($this->sheetFilePointer)) {
$worksheetFilePointer = $worksheet->getFilePointer();
if (!is_resource($worksheetFilePointer)) {
return;
}
fclose($this->sheetFilePointer);
fclose($worksheetFilePointer);
}
}
}
+2 -57
View File
@@ -2,10 +2,8 @@
namespace Box\Spout\Writer\ODS;
use Box\Spout\Writer\AbstractMultiSheetsWriter;
use Box\Spout\Writer\Common;
use Box\Spout\Writer\Common\Options;
use Box\Spout\Writer\ODS\Internal\Workbook;
use Box\Spout\Writer\WriterMultiSheetsAbstract;
/**
* Class Writer
@@ -13,14 +11,11 @@ use Box\Spout\Writer\ODS\Internal\Workbook;
*
* @package Box\Spout\Writer\ODS
*/
class Writer extends AbstractMultiSheetsWriter
class Writer extends WriterMultiSheetsAbstract
{
/** @var string Content-Type value for the header */
protected static $headerContentType = 'application/vnd.oasis.opendocument.spreadsheet';
/** @var Internal\Workbook The workbook for the ODS file */
protected $book;
/**
* Sets a custom temporary folder for creating intermediate files/folders.
* This must be set before opening the writer.
@@ -37,54 +32,4 @@ class Writer extends AbstractMultiSheetsWriter
$this->optionsManager->setOption(Options::TEMP_FOLDER, $tempFolder);
return $this;
}
/**
* Configures the write and sets the current sheet pointer to a new sheet.
*
* @return void
* @throws \Box\Spout\Common\Exception\IOException If unable to open the file for writing
*/
protected function openWriter()
{
$this->book = new Workbook($this->optionsManager);
$this->book->addNewSheetAndMakeItCurrent();
}
/**
* @return Internal\Workbook The workbook representing the file to be written
*/
protected function getWorkbook()
{
return $this->book;
}
/**
* Adds data to the currently opened writer.
* If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination
* with the creation of new worksheets if one worksheet has reached its maximum capicity.
*
* @param array $dataRow Array containing data to be written.
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row.
* @return void
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the book is not created yet
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
*/
protected function addRowToWriter(array $dataRow, $style)
{
$this->throwIfBookIsNotAvailable();
$this->book->addRowToCurrentWorksheet($dataRow, $style);
}
/**
* Closes the writer, preventing any additional writing.
*
* @return void
*/
protected function closeWriter()
{
if ($this->book) {
$this->book->close($this->filePointer);
}
}
}