├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── examples ├── big-data.php ├── formula.php ├── more-than-one.php └── one.php ├── src ├── ContentTypes.php ├── DocProps │ ├── App.php │ └── Core.php ├── ExcelWriter.php ├── Helpers │ └── ExcelHelper.php ├── Rels │ └── Relationships.php ├── Sheet.php ├── Writer.php └── Xl │ ├── SharedStrings.php │ ├── Styles.php │ ├── Workbook.php │ └── Worksheets │ └── SheetXml.php └── test ├── XLSX └── XLSXtest.php └── phpunit.xml /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: jDLWtdMaK6TkQcnY7e56CX370AV1HBi9a 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | 4 | *.buildpath 5 | *.project 6 | /.settings 7 | /.idea 8 | /.DS_Store 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.6 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | - 7.3 8 | - 7.4 9 | - nightly 10 | 11 | matrix: 12 | include: 13 | - php: '5.4' 14 | dist: trusty 15 | - php: '5.5' 16 | dist: trusty 17 | fast_finish: true 18 | 19 | sudo: false 20 | 21 | before_script: 22 | - composer self-update 23 | - composer install --prefer-source --dev 24 | - phpenv global "$TRAVIS_PHP_VERSION" 25 | script: ./vendor/bin/phpunit --configuration test/phpunit.xml 26 | 27 | notifications: 28 | email: 29 | - ozy@mailserver.ru 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Denis.Tikhonov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Big data Excel writer. Relatively low memory usage. 2 | Excel spreadsheet in with (Office 2007+) xlsx format, with just basic features 3 | 4 | ### Build: 5 | [![Latest Stable Version](https://poser.pugx.org/ellumilel/php-excel-writer/v/stable)](https://packagist.org/packages/ellumilel/php-excel-writer) 6 | [![Latest Unstable Version](https://poser.pugx.org/ellumilel/php-excel-writer/v/unstable)](https://packagist.org/packages/ellumilel/php-excel-writer) 7 | [![Build Status](https://travis-ci.org/ellumilel/php-excel-writer.svg?branch=master)](http://travis-ci.org/ellumilel/php-excel-writer) 8 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ellumilel/php-excel-writer/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ellumilel/php-excel-writer/?branch=master) 9 | [![License](https://poser.pugx.org/ellumilel/php-excel-writer/license)](https://packagist.org/packages/ellumilel/php-excel-writer) 10 | [![Total Downloads](https://poser.pugx.org/ellumilel/php-excel-writer/downloads)](https://packagist.org/packages/ellumilel/php-excel-writer) 11 | ### Use: 12 | - `ZipArchive`, based on PHP's [Zip extension](http://fr.php.net/manual/en/book.zip.php) 13 | 14 | ### Supports 15 | * supports PHP 5.4+ 16 | * supports simple formulas 17 | * supports currency/date/numeric cell formatting 18 | * takes UTF-8 encoded input 19 | * multiple worksheets 20 | 21 | ### Dev 22 | * PHPUnit 23 | * Optional: PHP_CodeSniffer for PSR-X-compatibility checks 24 | 25 | ### Installation 26 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 27 | Either run 28 | 29 | ``` 30 | php composer.phar require --prefer-dist ellumilel/php-excel-writer 31 | ``` 32 | 33 | or add 34 | 35 | ``` 36 | "ellumilel/php-excel-writer": ">=0.1.3" 37 | ``` 38 | 39 | to the require section of your `composer.json` file. 40 | ### Formats 41 | * 'string' = 'GENERAL' 42 | * 'text' = '@' 43 | * 'integer' = '0' 44 | * 'float_with_sep' = '#,##0.00' 45 | * 'float' = '0.00' 46 | * 'date' = 'YYYY-MM-DD' 47 | * 'datetime' = 'YYYY-MM-DD HH:MM:SS' 48 | * 'dollar' = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00' 49 | * 'money' = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00' 50 | * 'euro' = '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]' 51 | * 'rub' = '#,##0.00 [$₽-419];[Red]-#,##0.00 [$₽-419]' 52 | * 'NN' = 'DDD' 53 | * 'NNN' = 'DDDD' 54 | * 'NNNN' = 'DDDD", "' 55 | 56 | ### Examples 57 | #### Simple: 58 | ``` 59 | $header = [ 60 | 'test1' => 'YYYY-MM-DD HH:MM:SS', 61 | 'test2' => 'string', 62 | 'test3' => 'string', 63 | 'test4' => 'string', 64 | 'test5' => 'string', 65 | 'test6' => 'money', 66 | ]; 67 | 68 | $wExcel = new Ellumilel\ExcelWriter(); 69 | $wExcel->writeSheetHeader('Sheet1', $header); 70 | $wExcel->setAuthor('Your name here'); 71 | for ($i = 0; $i < 5000; $i++) { 72 | $wExcel->writeSheetRow('Sheet1', [ 73 | (new DateTime())->format('Y-m-d H:i:s'), 74 | rand(100, 10000), 75 | rand(100, 10000), 76 | rand(100, 10000), 77 | rand(100, 10000), 78 | rand(100, 10000), 79 | ]); 80 | } 81 | 82 | $wExcel->writeToFile("example.xlsx"); 83 | ``` 84 | #### 3.200.000 cell data example, low memory ~0.84 mb, fast write ~120 sec: 85 | ``` 86 | $header = [ 87 | 'head1' => 'YYYY-MM-DD HH:MM:SS', 88 | 'head2' => 'string', 89 | 'head3' => 'string', 90 | 'head4' => 'string', 91 | 'head5' => 'string', 92 | 'head6' => 'string', 93 | 'head7' => 'string', 94 | 'head8' => 'text', 95 | ]; 96 | $wExcel = new Ellumilel\ExcelWriter(); 97 | $wExcel->setAuthor('BigData Tester'); 98 | $wExcel->writeSheetHeader('Sheet1', $header); 99 | for ($ex = 0; $ex < 400000; $ex++) { 100 | $wExcel->writeSheetRow('Sheet1', [ 101 | (new DateTime())->format('Y-m-d H:i:s'), 102 | 'foo', 103 | 'baz', 104 | 'your text hear', 105 | rand(10000, 100000), 106 | rand(10000, 100000), 107 | rand(10000, 100000), 108 | rand(10000, 100000), 109 | ]); 110 | } 111 | $wExcel->writeToFile("output_big_data.xlsx"); 112 | ``` 113 | #### Advanced formula/format: 114 | ``` 115 | $header = [ 116 | 'created' => 'date', 117 | 'id' => 'integer', 118 | 'count' => '#,##0', 119 | 'amount' => 'dollar', 120 | 'description' => 'string', 121 | 'money' => '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00', 122 | 'sum' => 'dollar', 123 | 'rub' => 'rub', 124 | ]; 125 | $data = [ 126 | [ 127 | '2016-01-01', 128 | 123, 129 | 1002, 130 | '103.00', 131 | 'short string', 132 | '=D2*0.15', 133 | '=DOLLAR('.rand(10000, 100000).', 2)', 134 | rand(10000, 100000), 135 | ], 136 | [ 137 | '2016-04-12', 138 | 234, 139 | 2045, 140 | '2.00', 141 | 'loooooong string', 142 | '=D3*0.15', 143 | '=DOLLAR('.rand(10000, 100000).', 2)', 144 | rand(10000, 100000), 145 | ], 146 | [ 147 | '2016-02-05', 148 | 45, 149 | 56, 150 | '56.00', 151 | 'loooooong loooooong string', 152 | '=D4*0.15', 153 | '=DOLLAR('.rand(10000, 100000).', 2)', 154 | rand(10000, 100000), 155 | ], 156 | [ 157 | '2016-06-27', 158 | 534, 159 | 107, 160 | '678.00', 161 | 'loooooong loooooongloooooong string', 162 | '=D5*0.15', 163 | '=DOLLAR('.rand(10000, 100000).', 2)', 164 | rand(10000, 100000), 165 | ], 166 | ]; 167 | 168 | $wExcel = new Ellumilel\ExcelWriter(); 169 | $wExcel->writeSheet($data, 'Sheet1', $header); 170 | $wExcel->writeToFile('formulas.xlsx'); 171 | ``` 172 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ellumilel/php-excel-writer", 3 | "description": "Big data Excel writer with small memory consuming", 4 | "keywords": [ 5 | "php-excel-writer", 6 | "excel-downloader", 7 | "excel-writer", 8 | "excel", 9 | "export", 10 | "xlsx", 11 | "spreadsheet", 12 | "php-excel", 13 | "big-data" 14 | ], 15 | "homepage": "https://github.com/Ellumilel/php-excel-writer", 16 | "license": "MIT", 17 | "authors": [ 18 | { 19 | "name": "Denis Tikhonov", 20 | "email": "ozy@mailserver.ru", 21 | "homepage": "https://github.com/Ellumilel/php-excel-writer", 22 | "role": "Author" 23 | } 24 | ], 25 | "require-dev": { 26 | "phpunit/phpunit": "~4.8|~5.7|~6.0|^9.4", 27 | "squizlabs/php_codesniffer": "~2.0" 28 | }, 29 | "require": { 30 | "php": "^5.4|^7.0|^8.0" 31 | }, 32 | "autoload": { 33 | "psr-4": { "Ellumilel\\": "src/" } 34 | }, 35 | "minimum-stability": "stable" 36 | } 37 | -------------------------------------------------------------------------------- /examples/big-data.php: -------------------------------------------------------------------------------- 1 | 'YYYY-MM-DD HH:MM:SS', 7 | 'head2' => 'string', 8 | 'head3' => 'string', 9 | 'head4' => 'string', 10 | 'head5' => 'string', 11 | 'head6' => 'string', 12 | 'head7' => 'string', 13 | 'head8' => 'string', 14 | ]; 15 | $wExcel = new Ellumilel\ExcelWriter(); 16 | $wExcel->setAuthor('BigData Tester'); 17 | $wExcel->writeSheetHeader('Sheet1', $header); 18 | for ($ex = 0; $ex < 400000; $ex++) { 19 | $wExcel->writeSheetRow('Sheet1', [ 20 | (new DateTime())->format('Y-m-d H:i:s'), 21 | 'foo', 22 | 'baz', 23 | 'your text hear', 24 | rand(10000, 100000), 25 | rand(10000, 100000), 26 | rand(10000, 100000), 27 | rand(10000, 100000), 28 | ]); 29 | } 30 | $wExcel->writeToFile("output_big_data.xlsx"); 31 | $time = microtime(true) - $start; 32 | echo round(memory_get_usage() / 1048576, 2)." megabytes"; 33 | printf("Complete after %.4F sec.\n", $time); 34 | -------------------------------------------------------------------------------- /examples/formula.php: -------------------------------------------------------------------------------- 1 | 'date', 7 | 'id' => 'integer', 8 | 'count' => '#,##0', 9 | 'amount' => 'dollar', 10 | 'description' => 'string', 11 | 'money' => '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00', 12 | 'sum' => 'dollar', 13 | 'rub' => 'rub', 14 | ]; 15 | $data = [ 16 | [ 17 | '2016-01-01', 18 | 123, 19 | 1002, 20 | '103.00', 21 | 'short string', 22 | '=D2*0.15', 23 | '=DOLLAR('.rand(10000, 100000).', 2)', 24 | rand(10000, 100000), 25 | ], 26 | [ 27 | '2016-04-12', 28 | 234, 29 | 2045, 30 | '2.00', 31 | 'loooooong string', 32 | '=D3*0.15', 33 | '=DOLLAR('.rand(10000, 100000).', 2)', 34 | rand(10000, 100000), 35 | ], 36 | [ 37 | '2016-02-05', 38 | 45, 39 | 56, 40 | '56.00', 41 | 'loooooong loooooong string', 42 | '=D4*0.15', 43 | '=DOLLAR('.rand(10000, 100000).', 2)', 44 | rand(10000, 100000), 45 | ], 46 | [ 47 | '2016-06-27', 48 | 534, 49 | 107, 50 | '678.00', 51 | 'loooooong loooooongloooooong string', 52 | '=D5*0.15', 53 | '=DOLLAR('.rand(10000, 100000).', 2)', 54 | rand(10000, 100000), 55 | ], 56 | ]; 57 | 58 | $wExcel = new Ellumilel\ExcelWriter(); 59 | $wExcel->writeSheet($data, 'Sheet1', $header); 60 | $wExcel->writeToFile('formulas.xlsx'); 61 | $time = microtime(true) - $start; 62 | printf("Complete after: %.4F sec.\n", $time); 63 | -------------------------------------------------------------------------------- /examples/more-than-one.php: -------------------------------------------------------------------------------- 1 | setAuthor('Tester'); 17 | $wExcel->writeSheet($data1, 'Sheet11'); 18 | $wExcel->writeSheet($data2, 'Sheet22'); 19 | $wExcel->writeToFile("output_more_tan_one.xlsx"); 20 | $time = microtime(true) - $start; 21 | printf("Complete after: %.4F sec.\n", $time); 22 | -------------------------------------------------------------------------------- /examples/one.php: -------------------------------------------------------------------------------- 1 | 'date', 7 | 'test2' => 'string', 8 | 'test3' => 'euro', 9 | 'test4' => 'dollar', 10 | 'test5' => 'float', 11 | 'test6' => 'float_with_sep', 12 | 'test7' => 'string', 13 | ]; 14 | $wExcel = new Ellumilel\ExcelWriter(); 15 | $wExcel->setAuthor('Tester'); 16 | $wExcel->writeSheetHeader('Sheet1', $header); 17 | for ($j = 0; $j < 100; $j++) { 18 | $wExcel->writeSheetRow('Sheet1', [ 19 | (new DateTime())->format('Y-m-d'), 20 | rand(1000, 10000), 21 | rand(1000, 10000), 22 | rand(1000, 10000), 23 | rand(1000, 10000), 24 | rand(1000, 10000), 25 | '=HYPERLINK("http://yandex.ru/asd'.rand(1000, 10000).'/sdf='.rand(1000, 10000).'","ссылка")', 26 | ]); 27 | } 28 | $wExcel->writeToFile("output_one.xlsx"); 29 | $time = microtime(true) - $start; 30 | printf("Complete after: %.4F sec.\n", $time); 31 | -------------------------------------------------------------------------------- /src/ContentTypes.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class ContentTypes 12 | { 13 | /** @var string */ 14 | private $xml = "\n"; 15 | /** @var string */ 16 | private $urlContentTypes = 'http://schemas.openxmlformats.org/package/2006/content-types'; 17 | /** @var bool */ 18 | private $sharedStringsRelations = false; 19 | /** @var array */ 20 | private $sheets = []; 21 | 22 | /** 23 | * Relationships constructor. 24 | * 25 | * @param bool $sharedStringsRelations 26 | */ 27 | public function __construct($sharedStringsRelations = false) 28 | { 29 | $this->sharedStringsRelations = $sharedStringsRelations; 30 | } 31 | 32 | /** 33 | * @param array $sheets 34 | * 35 | * @return $this 36 | */ 37 | public function setSheet(array $sheets) 38 | { 39 | $this->sheets = $sheets; 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function buildContentTypesXML() 48 | { 49 | $xml = ''; 50 | $xml .= $this->xml; 51 | $xml .= ''; 52 | $xml .= ''; 53 | $xml .= ''; 54 | $xml .= 'sheets as $sheet) { 61 | $xml .= 'sharedStringsRelations) { 66 | $xml .= ' 11 | */ 12 | class App 13 | { 14 | /** @var string */ 15 | private $xml = "\n"; 16 | /** @var string */ 17 | private $urlSchemaFormat = 'http://schemas.openxmlformats.org/officeDocument/2006'; 18 | /** @var int */ 19 | private $totalTime = 0; 20 | 21 | /** 22 | * App constructor. 23 | * 24 | * @param int $totalTime 25 | */ 26 | public function __construct($totalTime = 0) 27 | { 28 | $this->totalTime = $totalTime; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function buildAppXML() 35 | { 36 | $appXml = ''; 37 | $appXml .= $this->xml; 38 | $appXml .= $this->getAppProperties(); 39 | 40 | return $appXml; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | private function getAppProperties() 47 | { 48 | $properties = ''; 51 | $properties .= $this->getTotalTime(); 52 | $properties .= ''; 53 | 54 | return $properties; 55 | } 56 | 57 | /** 58 | * @return string 59 | */ 60 | private function getTotalTime() 61 | { 62 | return ''.$this->totalTime.''; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/DocProps/Core.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Core 12 | { 13 | /** @var string */ 14 | private $author = 'Unknown Author'; 15 | /** @var int */ 16 | private $revision = 0; 17 | /** @var string */ 18 | private $urlCp = 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'; 19 | /** @var string */ 20 | private $urlDc = 'http://purl.org/dc/elements/1.1/'; 21 | /** @var string */ 22 | private $urlDcType = 'http://purl.org/dc/dcmitype/'; 23 | /** @var string */ 24 | private $urlDcTerms = 'http://purl.org/dc/terms/'; 25 | /** @var string */ 26 | private $urlSchema = 'http://www.w3.org/2001/XMLSchema-instance'; 27 | /** @var string */ 28 | private $xml = "\n"; 29 | 30 | /** 31 | * Core constructor. 32 | * 33 | * @param integer $revision 34 | */ 35 | public function __construct($revision = 0) 36 | { 37 | $this->revision = $revision; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function buildCoreXML() 44 | { 45 | $coreXml = ''; 46 | $coreXml .= $this->xml; 47 | $coreXml .= $this->getCoreProperties(); 48 | 49 | return $coreXml; 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | private function getCoreProperties() 56 | { 57 | $properties = ''; 60 | $properties .= $this->created(); 61 | $properties .= $this->creator(); 62 | $properties .= $this->revision(); 63 | $properties .= ''; 64 | 65 | return $properties; 66 | } 67 | 68 | /** 69 | * CreatedDate ex: '2016-08-30T15:52:19.00Z'; 70 | * @return string 71 | */ 72 | private function created() 73 | { 74 | return ''.date("Y-m-d\TH:i:s.00\Z").''; 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | private function creator() 81 | { 82 | return ''.str_replace("'", "'", htmlspecialchars($this->author)).''; 83 | } 84 | 85 | /** 86 | * @return string 87 | */ 88 | private function revision() 89 | { 90 | return ''.$this->revision.''; 91 | } 92 | 93 | /** 94 | * @param string $author 95 | * 96 | * @return $this 97 | */ 98 | public function setAuthor($author) 99 | { 100 | if (!empty($author)) { 101 | $this->author = $author; 102 | } 103 | 104 | return $this; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/ExcelWriter.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class ExcelWriter 19 | { 20 | /** @var array */ 21 | protected $sheets = []; 22 | /** @var array */ 23 | protected $sharedStrings = []; 24 | /** @var int */ 25 | protected $sharedStringCount = 0; 26 | /** @var array */ 27 | protected $tempFiles = []; 28 | /** @var array */ 29 | protected $cellFormats = []; 30 | /** @var array */ 31 | protected $cellTypes = []; 32 | /** @var string */ 33 | protected $currentSheet = ''; 34 | /** @var null */ 35 | protected $tmpDir = null; 36 | /** @var null */ 37 | protected $fileName = null; 38 | /** @var Core */ 39 | protected $core; 40 | /** @var App */ 41 | protected $app; 42 | /** @var Workbook */ 43 | protected $workbook; 44 | /** @var SheetXml */ 45 | protected $sheetXml; 46 | 47 | /** 48 | * ExcelWriter constructor. 49 | * @throws \Exception 50 | */ 51 | public function __construct() 52 | { 53 | if (!class_exists('ZipArchive')) { 54 | throw new \Exception('ZipArchive not found'); 55 | } 56 | if (!ini_get('date.timezone')) { 57 | date_default_timezone_set('UTC'); 58 | } 59 | $this->addCellFormat($cell_format = 'GENERAL'); 60 | $this->core = new Core(); 61 | $this->app = new App(); 62 | $this->workbook = new Workbook(); 63 | $this->sheetXml = new SheetXml(); 64 | } 65 | 66 | /** 67 | * @param string $author 68 | */ 69 | public function setAuthor($author) 70 | { 71 | $this->core->setAuthor($author); 72 | } 73 | 74 | public function __destruct() 75 | { 76 | if (!empty($this->tempFiles)) { 77 | foreach ($this->tempFiles as $tempFile) { 78 | if (file_exists($tempFile)) { 79 | unlink($tempFile); 80 | } 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * @param $dir 87 | */ 88 | public function setTmpDir($dir) 89 | { 90 | $this->tmpDir = $dir; 91 | } 92 | 93 | /** 94 | * Set output filename: yourFileName.xlsx 95 | * 96 | * @param string $fileName 97 | */ 98 | public function setFileName($fileName) 99 | { 100 | $this->fileName = $fileName; 101 | } 102 | 103 | /** 104 | * Return tmpFileName 105 | * @return string 106 | */ 107 | protected function tempFilename() 108 | { 109 | $tmpDir = is_null($this->tmpDir) ? sys_get_temp_dir() : $this->tmpDir; 110 | $filename = @tempnam($tmpDir, "excelWriter_"); 111 | $this->tempFiles[] = $filename; 112 | 113 | return $filename; 114 | } 115 | 116 | /** 117 | * @param bool $headers 118 | */ 119 | public function writeToStdOut($headers = true) 120 | { 121 | if (empty($this->tmpDir)) { 122 | $tempFile = $this->tempFilename().'.xlsx'; 123 | } else { 124 | $tempFile = $this->fileName; 125 | } 126 | 127 | $this->writeToFile($tempFile); 128 | if (file_exists($tempFile)) { 129 | if ($headers) { 130 | header('Content-Description: File Transfer'); 131 | header('Content-Type: application/octet-stream'); 132 | header('Content-Disposition: attachment; filename="'.basename($tempFile).'"'); 133 | header('Expires: 0'); 134 | header('Cache-Control: must-revalidate'); 135 | header('Pragma: public'); 136 | header('Content-Length: '.filesize($tempFile)); 137 | } 138 | readfile($tempFile); 139 | } 140 | } 141 | 142 | /** 143 | * @return string 144 | */ 145 | public function writeToString() 146 | { 147 | $tempFile = $this->tempFilename(); 148 | $this->writeToFile($tempFile); 149 | $string = file_get_contents($tempFile); 150 | 151 | return $string; 152 | } 153 | 154 | /** 155 | * @param string $filename 156 | */ 157 | public function writeToFile($filename) 158 | { 159 | $zip = new \ZipArchive(); 160 | foreach ($this->sheets as $sheetName => $sheet) { 161 | $this->finalizeSheet($sheetName); 162 | } 163 | $this->checkAndUnlink($zip, $filename); 164 | $this->workbook->setSheet($this->sheets); 165 | 166 | $contentTypes = new ContentTypes(!empty($this->sharedStrings)); 167 | $contentTypes->setSheet($this->sheets); 168 | 169 | $rel = new Relationships(!empty($this->sharedStrings)); 170 | $rel->setSheet($this->sheets); 171 | $zip->addEmptyDir("docProps/"); 172 | $zip->addFromString("docProps/app.xml", $this->app->buildAppXML()); 173 | $zip->addFromString("docProps/core.xml", $this->core->buildCoreXML()); 174 | $zip->addEmptyDir("_rels/"); 175 | $zip->addFromString("_rels/.rels", $rel->buildRelationshipsXML()); 176 | $zip->addEmptyDir("xl/worksheets/"); 177 | foreach ($this->sheets as $sheet) { 178 | /** @var Sheet $sheet */ 179 | $zip->addFile($sheet->getFilename(), "xl/worksheets/".$sheet->getXmlName()); 180 | } 181 | if (!empty($this->sharedStrings)) { 182 | $zip->addFile($this->writeSharedStringsXML(), "xl/sharedStrings.xml"); 183 | } 184 | $zip->addFromString("xl/workbook.xml", $this->workbook->buildWorkbookXML()); 185 | $zip->addFile($this->writeStylesXML(), "xl/styles.xml"); 186 | $zip->addFromString("[Content_Types].xml", $contentTypes->buildContentTypesXML()); 187 | $zip->addEmptyDir("xl/_rels/"); 188 | $zip->addFromString("xl/_rels/workbook.xml.rels", $rel->buildWorkbookRelationshipsXML()); 189 | $zip->close(); 190 | } 191 | 192 | /** 193 | * @param string $sheetName 194 | */ 195 | protected function initializeSheet($sheetName) 196 | { 197 | if ($this->currentSheet == $sheetName || isset($this->sheets[$sheetName])) { 198 | return; 199 | } 200 | $sheetFilename = $this->tempFilename(); 201 | $sheetXmlName = 'sheet' . (count($this->sheets) + 1).".xml"; 202 | $sheetObj = new Sheet(); 203 | $sheetObj 204 | ->setFilename($sheetFilename) 205 | ->setSheetName($sheetName) 206 | ->setXmlName($sheetXmlName) 207 | ->setWriter(new Writer($sheetFilename)) 208 | ; 209 | $this->sheets[$sheetName] = $sheetObj; 210 | /** @var Sheet $sheet */ 211 | $sheet = &$this->sheets[$sheetName]; 212 | $selectedTab = count($this->sheets) == 1 ? 'true' : 'false'; 213 | $maxCell = ExcelHelper::xlsCell(ExcelHelper::EXCEL_MAX_ROW, ExcelHelper::EXCEL_MAX_COL); 214 | $sheet->getWriter()->write($this->sheetXml->getXml()); 215 | $sheet->getWriter()->write($this->sheetXml->getWorksheet()); 216 | $sheet->getWriter()->write($this->sheetXml->getSheetPr()); 217 | $sheet->setMaxCellTagStart($sheet->getWriter()->fTell()); 218 | $sheet->getWriter()->write($this->sheetXml->getDimension($maxCell)); 219 | $sheet->setMaxCellTagEnd($sheet->getWriter()->fTell()); 220 | $sheet->getWriter()->write($this->sheetXml->getSheetViews($selectedTab)); 221 | $sheet->getWriter()->write($this->sheetXml->getCools()); 222 | $sheet->getWriter()->write(''); 223 | } 224 | 225 | /** 226 | * @param $cellFormat 227 | * 228 | * @return string 229 | */ 230 | private function determineCellType($cellFormat) 231 | { 232 | $cellFormat = str_replace("[RED]", "", $cellFormat); 233 | if ($cellFormat == 'GENERAL') { 234 | return 'string'; 235 | } 236 | if ($cellFormat == '0') { 237 | return 'numeric'; 238 | } 239 | if ($cellFormat == '@') { 240 | return '@'; 241 | } 242 | $checkArray = [ 243 | 'datetime' => [ 244 | "/[H]{1,2}:[M]{1,2}/", 245 | "/[M]{1,2}:[S]{1,2}/", 246 | ], 247 | 'numeric' => [ 248 | "/0/", 249 | ], 250 | 'date' => [ 251 | "/[YY]{2,4}/", 252 | "/[D]{1,2}/", 253 | "/[M]{1,2}/", 254 | ], 255 | 'currency' => [ 256 | "/$/", 257 | ], 258 | 'percent' => [ 259 | "/%/", 260 | ], 261 | ]; 262 | foreach ($checkArray as $type => $item) { 263 | foreach ($item as $prMatch) { 264 | if (preg_match($prMatch, $cellFormat)) { 265 | return $type; 266 | } 267 | } 268 | } 269 | 270 | return 'string'; 271 | } 272 | 273 | /** 274 | * backwards compatibility 275 | * 276 | * @param $cellFormat 277 | * 278 | * @return int|mixed 279 | */ 280 | private function addCellFormat($cellFormat) 281 | { 282 | $cellFormat = strtoupper($this->getCellFormat($cellFormat)); 283 | $position = array_search($cellFormat, $this->cellFormats, $strict = true); 284 | if ($position === false) { 285 | $position = count($this->cellFormats); 286 | $this->cellFormats[] = ExcelHelper::escapeCellFormat($cellFormat); 287 | $this->cellTypes[] = $this->determineCellType($cellFormat); 288 | } 289 | 290 | return $position; 291 | } 292 | 293 | /** 294 | * @link https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.numberingformats(v=office.15).aspx 295 | * 296 | * @param string $cellFormat 297 | * 298 | * @return string 299 | */ 300 | private function getCellFormat($cellFormat) 301 | { 302 | $formatArray = [ 303 | 'string' => 'GENERAL', 304 | 'text' => '@', 305 | 'integer' => '0', 306 | 'float_with_sep' => '#,##0.00', 307 | 'float' => '0.00', 308 | 'date' => 'YYYY-MM-DD', 309 | 'datetime' => 'YYYY-MM-DD HH:MM:SS', 310 | 'dollar' => '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00', 311 | 'money' => '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00', 312 | 'euro' => '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]', 313 | 'rub' => '#,##0.00 [$₽-419];[Red]-#,##0.00 [$₽-419]', 314 | 'NN' => 'DDD', 315 | 'NNN' => 'DDDD', 316 | 'NNNN' => 'DDDD", "', 317 | ]; 318 | 319 | if (array_key_exists($cellFormat, $formatArray)) { 320 | return $formatArray[$cellFormat]; 321 | } 322 | return $cellFormat; 323 | } 324 | 325 | /** 326 | * @param string $sheetName 327 | * @param array $headerTypes 328 | * @param bool $suppressRow 329 | */ 330 | public function writeSheetHeader($sheetName, array $headerTypes, $suppressRow = false) 331 | { 332 | if (empty($sheetName) || empty($headerTypes) || !empty($this->sheets[$sheetName])) { 333 | return; 334 | } 335 | $this->initializeSheet($sheetName); 336 | /** @var Sheet $sheet */ 337 | $sheet = &$this->sheets[$sheetName]; 338 | $sheet->setColumns([]); 339 | foreach ($headerTypes as $val) { 340 | $sheet->setColumn($this->addCellFormat($val)); 341 | } 342 | if (!$suppressRow) { 343 | $this->writeRowHeader($sheet, array_keys($headerTypes)); 344 | $sheet->increaseRowCount(); 345 | } 346 | $this->currentSheet = $sheetName; 347 | } 348 | 349 | /** 350 | * @param Sheet $sheet 351 | * @param array $headerRow 352 | */ 353 | private function writeRowHeader(Sheet $sheet, $headerRow) 354 | { 355 | $sheet->getWriter()->write( 356 | ''); 363 | } 364 | 365 | /** 366 | * @param string $sheetName 367 | * @param array $row 368 | */ 369 | public function writeSheetRow($sheetName, array $row) 370 | { 371 | if (empty($sheetName) || empty($row)) { 372 | return; 373 | } 374 | $this->initializeSheet($sheetName); 375 | /** @var Sheet $sheet */ 376 | $sheet = &$this->sheets[$sheetName]; 377 | $columns = $sheet->getColumns(); 378 | if (empty($columns)) { 379 | $sheet->setColumns(array_fill(0, count($row), '0')); 380 | } 381 | $sheet->getWriter()->write( 382 | ''); 398 | $sheet->increaseRowCount(); 399 | $this->currentSheet = $sheetName; 400 | } 401 | 402 | /** 403 | * @param string $sheetName 404 | */ 405 | protected function finalizeSheet($sheetName) 406 | { 407 | if (empty($sheetName) || ($this->sheets[$sheetName] instanceof Sheet && 408 | $this->sheets[$sheetName]->getFinalized() 409 | ) 410 | ) { 411 | return; 412 | } 413 | 414 | /** @var Sheet $sheet */ 415 | $sheet = &$this->sheets[$sheetName]; 416 | $sheet->getWriter()->write(''); 417 | $mergeCells = $sheet->getMergeCells(); 418 | if (!empty($mergeCells)) { 419 | $sheet->getWriter()->write($this->sheetXml->getMergeCells($mergeCells)); 420 | } 421 | $sheet->getWriter()->write($this->sheetXml->getPrintOptions()); 422 | $sheet->getWriter()->write($this->sheetXml->getPageMargins()); 423 | $sheet->getWriter()->write($this->sheetXml->getPageSetup()); 424 | $sheet->getWriter()->write($this->sheetXml->getHeaderFooter()); 425 | $sheet->getWriter()->write(''); 426 | $maxCell = ExcelHelper::xlsCell($sheet->getRowCount() - 1, count($sheet->getColumns()) - 1); 427 | $maxCellTag = $this->sheetXml->getDimension($maxCell); 428 | $paddingLength = $sheet->getMaxCellTagEnd() - $sheet->getMaxCellTagStart() - strlen($maxCellTag); 429 | $sheet->getWriter()->fSeek($sheet->getMaxCellTagStart()); 430 | $sheet->getWriter()->write($maxCellTag.str_repeat(" ", $paddingLength)); 431 | $sheet->getWriter()->close(); 432 | $sheet->setFinalized(true); 433 | } 434 | 435 | /** 436 | * @param string $sheetName 437 | * @param int $startCellRow 438 | * @param int $startCellColumn 439 | * @param int $endCellRow 440 | * @param int $endCellColumn 441 | */ 442 | public function markMergedCell($sheetName, $startCellRow, $startCellColumn, $endCellRow, $endCellColumn) 443 | { 444 | if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) { 445 | return; 446 | } 447 | $this->initializeSheet($sheetName); 448 | /** @var Sheet $sheet */ 449 | $sheet = &$this->sheets[$sheetName]; 450 | $startCell = ExcelHelper::xlsCell($startCellRow, $startCellColumn); 451 | $endCell = ExcelHelper::xlsCell($endCellRow, $endCellColumn); 452 | $sheet->setMergeCells($startCell.":".$endCell); 453 | } 454 | 455 | /** 456 | * @param array $data 457 | * @param string $sheetName 458 | * @param array $headerTypes 459 | */ 460 | public function writeSheet(array $data, $sheetName = '', array $headerTypes = []) 461 | { 462 | $sheetName = empty($sheetName) ? 'Sheet1' : $sheetName; 463 | $data = empty($data) ? [['']] : $data; 464 | if (!empty($headerTypes)) { 465 | $this->writeSheetHeader($sheetName, $headerTypes); 466 | } 467 | foreach ($data as $i => $row) { 468 | $this->writeSheetRow($sheetName, $row); 469 | } 470 | $this->finalizeSheet($sheetName); 471 | } 472 | 473 | /** 474 | * @param Writer $file 475 | * @param int $rowNumber 476 | * @param int $columnNumber 477 | * @param mixed $value 478 | * @param $cellIndex 479 | */ 480 | protected function writeCell(Writer $file, $rowNumber, $columnNumber, $value, $cellIndex) 481 | { 482 | $cellType = $this->cellTypes[$cellIndex]; 483 | $cellName = ExcelHelper::xlsCell($rowNumber, $columnNumber); 484 | $cell = $this->sheetXml->getCell($cellName, $cellIndex, $cellType, $value); 485 | if ($cell === false) { 486 | $file->write( 487 | ''.ExcelHelper::xmlspecialchars( 488 | $this->setSharedString($value) 489 | ).'' 490 | ); 491 | } else { 492 | $file->write($cell); 493 | } 494 | } 495 | 496 | /** 497 | * @return string 498 | */ 499 | protected function writeStylesXML() 500 | { 501 | $temporaryFilename = $this->tempFilename(); 502 | $file = new Writer($temporaryFilename); 503 | $styles = new Styles(); 504 | $styles->setCellFormats($this->cellFormats); 505 | $file->write($styles->buildStylesXML()); 506 | 507 | return $temporaryFilename; 508 | } 509 | 510 | /** 511 | * @param $v 512 | * 513 | * @return int|mixed 514 | */ 515 | protected function setSharedString($v) 516 | { 517 | if (isset($this->sharedStrings[$v])) { 518 | $stringValue = $this->sharedStrings[$v]; 519 | } else { 520 | $stringValue = count($this->sharedStrings); 521 | $this->sharedStrings[$v] = $stringValue; 522 | } 523 | $this->sharedStringCount++; 524 | 525 | return $stringValue; 526 | } 527 | 528 | /** 529 | * @return string 530 | */ 531 | protected function writeSharedStringsXML() 532 | { 533 | $tempFilename = $this->tempFilename(); 534 | $file = new Writer($tempFilename); 535 | $sharedStrings = new SharedStrings($this->sharedStringCount, $this->sharedStrings); 536 | $file->write($sharedStrings->buildSharedStringsXML()); 537 | $file->close(); 538 | 539 | return $tempFilename; 540 | } 541 | 542 | /** 543 | * @param \ZipArchive $zip 544 | * @param string $filename 545 | */ 546 | private function checkAndUnlink(\ZipArchive $zip, $filename) 547 | { 548 | if (file_exists($filename) && is_writable($filename)) { 549 | unlink($filename); 550 | } 551 | if (empty($this->sheets) || !$zip->open($filename, \ZipArchive::CREATE)) { 552 | throw new \RuntimeException( 553 | "Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined or unable to create zip." 554 | ); 555 | } 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /src/Helpers/ExcelHelper.php: -------------------------------------------------------------------------------- 1 | format('L'); 48 | $mDays = [31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 49 | 50 | if (($year < 1900 || $year > 9999) || ($month < 1 || $month > 12) || $day < 1 || $day > $mDays[$month - 1]) { 51 | return 0; 52 | } 53 | 54 | $days = $day + ($range * 365) + array_sum(array_slice($mDays, 0, $month - 1)); 55 | $days += intval(($range) / 4) - intval(($range + $offset) / 100); 56 | $days += intval(($range + $offset + $norm) / 400) - intval($leap); 57 | if ($days > 59) { 58 | $days++; 59 | } 60 | 61 | return $days + $seconds; 62 | } 63 | 64 | /** 65 | * @param $val 66 | * 67 | * @return mixed 68 | */ 69 | public static function xmlspecialchars($val) 70 | { 71 | return str_replace("'", "'", htmlspecialchars($val)); 72 | } 73 | 74 | /** 75 | * @param int $rowNumber 76 | * @param int $columnNumber 77 | * 78 | * @return string Cell label/coordinates (A1, C3, AA42) 79 | */ 80 | public static function xlsCell($rowNumber, $columnNumber) 81 | { 82 | $n = $columnNumber; 83 | for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) { 84 | $r = chr($n % 26 + 0x41).$r; 85 | } 86 | 87 | return $r.($rowNumber + 1); 88 | } 89 | 90 | /** 91 | * @link https://msdn.microsoft.com/ru-RU/library/aa365247%28VS.85%29.aspx 92 | * 93 | * @param string $filename 94 | * 95 | * @return mixed 96 | */ 97 | public static function checkFilename($filename) 98 | { 99 | $invalidCharacter = array_merge( 100 | array_map('chr', range(0, 31)), 101 | ['<', '>', '?', '"', ':', '|', '\\', '/', '*', '&'] 102 | ); 103 | 104 | return str_replace($invalidCharacter, '', $filename); 105 | } 106 | 107 | /** 108 | * @todo check escaping 109 | * 110 | * @param string $cellFormat 111 | * 112 | * @return string 113 | */ 114 | public static function escapeCellFormat($cellFormat) 115 | { 116 | $ignoreUntil = ''; 117 | $escaped = ''; 118 | for ($i = 0, $ix = strlen($cellFormat); $i < $ix; $i++) { 119 | $c = $cellFormat[$i]; 120 | if ($ignoreUntil == '' && $c == '[') { 121 | $ignoreUntil = ']'; 122 | } else { 123 | if ($ignoreUntil == '' && $c == '"') { 124 | $ignoreUntil = '"'; 125 | } else { 126 | if ($ignoreUntil == $c) { 127 | $ignoreUntil = ''; 128 | } 129 | } 130 | } 131 | if ($ignoreUntil == '' && 132 | ($c == ' ' || $c == '-' || $c == '(' || $c == ')') && 133 | ($i == 0 || $cellFormat[$i - 1] != '_') 134 | ) { 135 | $escaped .= "\\".$c; 136 | } else { 137 | $escaped .= $c; 138 | } 139 | } 140 | 141 | return $escaped; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Rels/Relationships.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Relationships 14 | { 15 | /** @var string */ 16 | private $xml = "\n"; 17 | /** @var string */ 18 | private $urlSchema = 'http://schemas.openxmlformats.org/officeDocument/2006'; 19 | /** @var string */ 20 | private $urlRel = 'http://schemas.openxmlformats.org/package/2006/relationships'; 21 | /** @var bool */ 22 | private $sharedStringsRelations = false; 23 | /** @var array */ 24 | private $sheets = []; 25 | 26 | /** 27 | * Relationships constructor. 28 | * 29 | * @param bool $sharedStringsRelations 30 | */ 31 | public function __construct($sharedStringsRelations = false) 32 | { 33 | $this->sharedStringsRelations = $sharedStringsRelations; 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function buildRelationshipsXML() 40 | { 41 | $relXml = ''; 42 | $relXml .= $this->xml; 43 | $relXml .= ''; 44 | $relXml .= 'urlSchema.'/relationships/metadata/core-properties"'; 47 | $relXml .= ' Target="docProps/core.xml"/>'; 48 | $relXml .= 'xml; 63 | $relXml .= ''; 64 | $relXml .= ''; 65 | foreach ($this->sheets as $sheet) { 66 | /** @var Sheet $sheet */ 67 | $relXml .= ''; 69 | $i++; 70 | } 71 | if (!empty($this->sharedStringsRelations)) { 72 | $relXml .= ''; 74 | } 75 | $relXml .= "\n"; 76 | $relXml .= ''; 77 | 78 | return $relXml; 79 | } 80 | 81 | /** 82 | * @param array $sheets 83 | * 84 | * @return $this 85 | */ 86 | public function setSheet(array $sheets) 87 | { 88 | $this->sheets = $sheets; 89 | 90 | return $this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Sheet.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Sheet 10 | { 11 | /** @var string */ 12 | private $filename; 13 | /** @var string */ 14 | private $sheetName; 15 | /** @var string */ 16 | private $xmlName; 17 | /** @var int */ 18 | private $rowCount = 0; 19 | /** @var Writer */ 20 | private $writer; 21 | /** @var array */ 22 | private $columns = []; 23 | /** @var array */ 24 | private $mergeCells = []; 25 | /** @var int */ 26 | private $maxCellTagStart = 0; 27 | /** @var int */ 28 | private $maxCellTagEnd = 0; 29 | /** @var bool */ 30 | private $finalized = false; 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getFilename() 36 | { 37 | return $this->filename; 38 | } 39 | 40 | /** 41 | * @param string $filename 42 | * 43 | * @return $this 44 | */ 45 | public function setFilename($filename) 46 | { 47 | $this->filename = $filename; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function getSheetName() 56 | { 57 | return $this->sheetName; 58 | } 59 | 60 | /** 61 | * @param string $sheetName 62 | * 63 | * @return $this 64 | */ 65 | public function setSheetName($sheetName) 66 | { 67 | $this->sheetName = $sheetName; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getXmlName() 76 | { 77 | return $this->xmlName; 78 | } 79 | 80 | /** 81 | * @param string $xmlName 82 | * 83 | * @return $this 84 | */ 85 | public function setXmlName($xmlName) 86 | { 87 | $this->xmlName = $xmlName; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * @return int 94 | */ 95 | public function getRowCount() 96 | { 97 | return $this->rowCount; 98 | } 99 | 100 | /** 101 | * @param int $rowCount 102 | * 103 | * @return $this 104 | */ 105 | public function setRowCount($rowCount) 106 | { 107 | $this->rowCount = $rowCount; 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * @return int 114 | */ 115 | public function increaseRowCount() 116 | { 117 | return $this->rowCount++; 118 | } 119 | 120 | /** 121 | * @return Writer 122 | */ 123 | public function getWriter() 124 | { 125 | return $this->writer; 126 | } 127 | 128 | /** 129 | * @param Writer $writer 130 | * 131 | * @return $this 132 | */ 133 | public function setWriter(Writer $writer) 134 | { 135 | $this->writer = $writer; 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * @return array 142 | */ 143 | public function getColumns() 144 | { 145 | return $this->columns; 146 | } 147 | 148 | /** 149 | * @param int $column 150 | * 151 | * @return $this 152 | */ 153 | public function setColumn($column) 154 | { 155 | $this->columns[] = $column; 156 | 157 | return $this; 158 | } 159 | 160 | /** 161 | * @param array $columns 162 | * 163 | * @return $this 164 | */ 165 | public function setColumns(array $columns) 166 | { 167 | $this->columns = $columns; 168 | 169 | return $this; 170 | } 171 | 172 | /** 173 | * @return array 174 | */ 175 | public function getMergeCells() 176 | { 177 | return $this->mergeCells; 178 | } 179 | 180 | /** 181 | * @param string $mergeCells 182 | * 183 | * @return $this 184 | */ 185 | public function setMergeCells($mergeCells) 186 | { 187 | $this->mergeCells[] = $mergeCells; 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * @return int 194 | */ 195 | public function getMaxCellTagStart() 196 | { 197 | return $this->maxCellTagStart; 198 | } 199 | 200 | /** 201 | * @param int $maxCellTagStart 202 | * 203 | * @return $this 204 | */ 205 | public function setMaxCellTagStart($maxCellTagStart) 206 | { 207 | $this->maxCellTagStart = $maxCellTagStart; 208 | 209 | return $this; 210 | } 211 | 212 | /** 213 | * @return int 214 | */ 215 | public function getMaxCellTagEnd() 216 | { 217 | return $this->maxCellTagEnd; 218 | } 219 | 220 | /** 221 | * @param int $maxCellTagEnd 222 | * 223 | * @return $this 224 | */ 225 | public function setMaxCellTagEnd($maxCellTagEnd) 226 | { 227 | $this->maxCellTagEnd = $maxCellTagEnd; 228 | 229 | return $this; 230 | } 231 | 232 | /** 233 | * @return bool 234 | */ 235 | public function getFinalized() 236 | { 237 | return $this->finalized; 238 | } 239 | 240 | /** 241 | * @param bool $finalized 242 | * 243 | * @return $this 244 | */ 245 | public function setFinalized($finalized) 246 | { 247 | $this->finalized = $finalized; 248 | 249 | return $this; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/Writer.php: -------------------------------------------------------------------------------- 1 | fd = fopen($filename, $openFlags); 31 | $this->bufferSize = $bufferSize; 32 | 33 | if ($this->fd === false) { 34 | throw new \Exception("Unable to open $filename for writing."); 35 | } 36 | } 37 | 38 | /** 39 | * @param $string 40 | */ 41 | public function write($string) 42 | { 43 | $this->buffer .= $string; 44 | if (isset($this->buffer[$this->bufferSize])) { 45 | $this->purge(); 46 | } 47 | } 48 | 49 | /** 50 | * add to file 51 | */ 52 | protected function purge() 53 | { 54 | if ($this->fd) { 55 | fwrite($this->fd, $this->buffer); 56 | $this->buffer = ''; 57 | } 58 | } 59 | 60 | /** 61 | * close writing 62 | */ 63 | public function close() 64 | { 65 | $this->purge(); 66 | if ($this->fd) { 67 | fclose($this->fd); 68 | $this->fd = null; 69 | } 70 | } 71 | 72 | /** 73 | * close after end 74 | */ 75 | public function __destruct() 76 | { 77 | $this->close(); 78 | } 79 | 80 | /** 81 | * @return int 82 | */ 83 | public function fTell() 84 | { 85 | if ($this->fd) { 86 | $this->purge(); 87 | 88 | return ftell($this->fd); 89 | } 90 | 91 | return -1; 92 | } 93 | 94 | /** 95 | * @param $pos 96 | * 97 | * @return int 98 | */ 99 | public function fSeek($pos) 100 | { 101 | if ($this->fd) { 102 | $this->purge(); 103 | 104 | return fseek($this->fd, $pos); 105 | } 106 | 107 | return -1; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Xl/SharedStrings.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class SharedStrings 12 | { 13 | /** @var string */ 14 | private $xml = "\n"; 15 | /** @var string */ 16 | private $urlSchemaFormat = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'; 17 | /** @var int */ 18 | private $sharedStringsCount = 0; 19 | /** @var int */ 20 | private $sharedStringsLength = 0; 21 | /** @var array */ 22 | private $sharedStrings = []; 23 | 24 | /** 25 | * SharedStrings constructor. 26 | * 27 | * @param int $sharedStringsCount 28 | * @param array $sharedStrings 29 | */ 30 | public function __construct($sharedStringsCount = 0, $sharedStrings = []) 31 | { 32 | $this->sharedStringsCount = $sharedStringsCount; 33 | $this->sharedStrings = $sharedStrings; 34 | $this->sharedStringsLength = count($sharedStrings); 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function buildSharedStringsXML() 41 | { 42 | $ssXml = ''; 43 | $ssXml .= $this->xml; 44 | $ssXml .= $this->getSst(); 45 | 46 | return $ssXml; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | private function getSst() 53 | { 54 | $sst = '' 58 | ; 59 | 60 | foreach ($this->sharedStrings as $s => $item) { 61 | $sst .= ''.str_replace("'", "'", htmlspecialchars($s)).''; 62 | } 63 | 64 | $sst .= ''; 65 | 66 | return $sst; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Xl/Styles.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Styles 14 | { 15 | /** @var string */ 16 | private $xml = "\n"; 17 | /** @var string */ 18 | private $urlOpenXmlFormat = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'; 19 | /** @var array */ 20 | private $cellFormats; 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function buildStylesXML() 26 | { 27 | $xml = ''; 28 | $xml .= $this->xml; 29 | $xml .= ''; 30 | $xml .= ''; 31 | foreach ($this->cellFormats as $i => $v) { 32 | $xml .= ''; 35 | } 36 | $xml .= ''; 37 | $xml .= $this->getFonts(); 38 | $xml .= $this->getFills(); 39 | $xml .= $this->getStyleXfs(); 40 | $xml .= $this->getXfs(); 41 | $xml .= $this->getCellStyles(); 42 | $xml .= ''; 43 | 44 | return $xml; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | private function getFonts() 51 | { 52 | $fonts = ''; 53 | $fonts .= ''; 54 | $fonts .= ''; 55 | $fonts .= ''; 56 | $fonts .= ''; 57 | $fonts .= ''; 58 | 59 | return $fonts; 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | private function getFills() 66 | { 67 | $fills = ''; 68 | $fills .= ''; 69 | $fills .= ''; 70 | $fills .= ''; 71 | $fills .= ''; 72 | 73 | return $fills; 74 | } 75 | 76 | /** 77 | * @return string 78 | */ 79 | private function getStyleXfs() 80 | { 81 | $xfs = ''; 82 | $xfs .= ''; 83 | $xfs .= ''; 84 | $xfs .= ''; 86 | $xfs .= ''; 87 | $xfs .= ''; 88 | $xfs .= ''; 89 | $xfs .= ''; 90 | $xfs .= ''; 91 | $xfs .= ''; 92 | $xfs .= ''; 93 | $xfs .= ''; 94 | $xfs .= ''; 95 | $xfs .= ''; 96 | $xfs .= ''; 97 | $xfs .= ''; 98 | $xfs .= ''; 99 | $xfs .= ''; 100 | $xfs .= ''; 101 | $xfs .= ''; 102 | $xfs .= ''; 103 | $xfs .= ''; 104 | $xfs .= ''; 105 | $xfs .= ''; 106 | 107 | return $xfs; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | private function getXfs() 114 | { 115 | $xfs = ''; 116 | foreach ($this->cellFormats as $i => $v) { 117 | $xfs .= ''; 119 | } 120 | $xfs .= ''; 121 | 122 | return $xfs; 123 | } 124 | 125 | /** 126 | * @return string 127 | */ 128 | private function getCellStyles() 129 | { 130 | $style = ''; 131 | $style .= ''; 132 | $style .= ''; 133 | $style .= ''; 134 | $style .= ''; 135 | $style .= ''; 136 | $style .= ''; 137 | $style .= ''; 138 | 139 | return $style; 140 | } 141 | 142 | /** 143 | * @param array $cellFormats 144 | * 145 | * @return $this 146 | */ 147 | public function setCellFormats(array $cellFormats) 148 | { 149 | $this->cellFormats = $cellFormats; 150 | 151 | return $this; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Xl/Workbook.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Workbook 14 | { 15 | /** @var string */ 16 | private $xml = "\n"; 17 | /** @var string */ 18 | private $urlSchemaFormat = 'http://schemas.openxmlformats.org/officeDocument/2006'; 19 | /** @var string */ 20 | private $urlOpenXmlFormat = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'; 21 | /** @var array */ 22 | private $sheets = []; 23 | 24 | /** 25 | * @return string 26 | */ 27 | public function buildWorkbookXML() 28 | { 29 | $i = 0; 30 | $xml = ''; 31 | $xml .= $this->xml; 32 | $xml .= ''; 33 | $xml .= 'sheets as $sheet_name => $sheet) { 41 | $xml .= 'getSheetName())).'"'; 42 | $xml .= ' sheetId="'.($i + 1).'" state="visible" r:id="rId'.($i + 2).'"/>'; 43 | $i++; 44 | } 45 | $xml .= ''; 46 | $xml .= ''; 47 | 48 | return $xml; 49 | } 50 | 51 | /** 52 | * @param array $sheets 53 | * 54 | * @return $this 55 | */ 56 | public function setSheet(array $sheets) 57 | { 58 | $this->sheets = $sheets; 59 | 60 | return $this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Xl/Worksheets/SheetXml.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class SheetXml 14 | { 15 | /** @var string */ 16 | private $xml = "\n"; 17 | /** @var string */ 18 | private $urlOpenXmlFormat = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'; 19 | /** @var string */ 20 | private $urlSchemaFormat = 'http://schemas.openxmlformats.org/officeDocument/2006'; 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function getXml() 26 | { 27 | return $this->xml; 28 | } 29 | 30 | /** 31 | * @return string 32 | */ 33 | public function getSheetPr() 34 | { 35 | $sPr = ''; 36 | $sPr .= ''; 37 | $sPr .= ''; 38 | 39 | return $sPr; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function getWorksheet() 46 | { 47 | $wSheet = ''; 48 | 49 | return $wSheet; 50 | } 51 | 52 | /** 53 | * @param string $selectedTab 54 | * 55 | * @return string 56 | */ 57 | public function getSheetViews($selectedTab) 58 | { 59 | $sViews = ''; 60 | $sViews .= ' 80 | 81 | 82 | 83 | 84 | */ 85 | $sCols = ''; 86 | $sCols .= ''; 87 | $sCols .= ''; 88 | 89 | return $sCols; 90 | } 91 | 92 | /** 93 | * @param string $maxCell 94 | * 95 | * @return string 96 | */ 97 | public function getDimension($maxCell) 98 | { 99 | $sCols = ''; 100 | 101 | return $sCols; 102 | } 103 | 104 | /** 105 | * @todo refactor 106 | * 107 | * @return string 108 | */ 109 | public function getHeaderFooter() 110 | { 111 | $hf = ''; 112 | $hf .= '&C&"Times New Roman,Regular"&12&A'; 113 | $hf .= '&C&"Times New Roman,Regular"&12Page &P'; 114 | $hf .= ''; 115 | 116 | return $hf; 117 | } 118 | 119 | /** 120 | * @return string 121 | */ 122 | public function getPageSetup() 123 | { 124 | $ps = ''; 137 | } 138 | 139 | /** 140 | * @return string 141 | */ 142 | public function getPrintOptions() 143 | { 144 | return ''; 146 | } 147 | 148 | /** 149 | * @param array $mergeCells 150 | * 151 | * @return string 152 | */ 153 | public function getMergeCells(array $mergeCells) 154 | { 155 | $mc = ''; 156 | foreach ($mergeCells as $range) { 157 | $mc .= ''; 158 | } 159 | $mc .= ''; 160 | 161 | return $mc; 162 | } 163 | 164 | /** 165 | * @param $cellName 166 | * @param $cellIndex 167 | * @param $cellType 168 | * @param $value 169 | * 170 | * @return bool|string 171 | */ 172 | public function getCell($cellName, $cellIndex, $cellType, $value) 173 | { 174 | if ($cellType == '@') { 175 | return false; 176 | } 177 | 178 | if (!is_scalar($value) || $value === '') { 179 | return ''; 180 | } 181 | 182 | if (is_string($value) && $value[0] == '=') { 183 | return $this->getFormulaCell($cellName, $cellIndex, $value); 184 | } 185 | 186 | if ($cellType == 'date' || $cellType == 'datetime') { 187 | return $this->getDateCell($cellName, $cellIndex, $value); 188 | } elseif ($cellType == 'currency' || $cellType == 'percent' || $cellType == 'numeric') { 189 | return $this->getCurrencyCell($cellName, $cellIndex, $value); 190 | } 191 | 192 | return $this->checkIntCell($cellName, $cellIndex, $value); 193 | } 194 | 195 | /** 196 | * @param $cellName 197 | * @param $cellIndex 198 | * @param $value 199 | * 200 | * @return bool|string 201 | */ 202 | private function checkIntCell($cellName, $cellIndex, $value) 203 | { 204 | if (!is_string($value)) { 205 | return $this->getIntCell($cellName, $cellIndex, $value); 206 | } else { 207 | if ($value[0] != '0' && 208 | $value[0] != '+' && 209 | filter_var( 210 | $value, 211 | FILTER_VALIDATE_INT, 212 | ['options' => ['max_range' => ExcelHelper::EXCEL_MAX_RANGE]] 213 | ) 214 | ) { 215 | return $this->getIntCell($cellName, $cellIndex, $value); 216 | } else { 217 | return false; 218 | } 219 | } 220 | } 221 | 222 | /** 223 | * @param $cellName 224 | * @param $cellIndex 225 | * @param $value 226 | * 227 | * @return string 228 | */ 229 | private function getDateCell($cellName, $cellIndex, $value) 230 | { 231 | return sprintf( 232 | '%s', 233 | $cellName, 234 | $cellIndex, 235 | ExcelHelper::convertDateTime($value) 236 | ); 237 | } 238 | 239 | /** 240 | * @param $cellName 241 | * @param $cellIndex 242 | * @param $value 243 | * 244 | * @return string 245 | */ 246 | private function getCurrencyCell($cellName, $cellIndex, $value) 247 | { 248 | return sprintf( 249 | '%s', 250 | $cellName, 251 | $cellIndex, 252 | ExcelHelper::xmlspecialchars($value) 253 | ); 254 | } 255 | 256 | /** 257 | * @param $cellName 258 | * @param $cellIndex 259 | * @param $value 260 | * 261 | * @return string 262 | */ 263 | private function getIntCell($cellName, $cellIndex, $value) 264 | { 265 | return ''.intval($value).''; 266 | } 267 | 268 | /** 269 | * @param $cellName 270 | * @param $cellIndex 271 | * @param $value 272 | * 273 | * @return string 274 | */ 275 | private function getFormulaCell($cellName, $cellIndex, $value) 276 | { 277 | return sprintf( 278 | '%s', 279 | $cellName, 280 | $cellIndex, 281 | ExcelHelper::xmlspecialchars($value) 282 | ); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /test/XLSX/XLSXtest.php: -------------------------------------------------------------------------------- 1 | assertFalse(file_exists($output_file)); 16 | 17 | $header = [ 18 | 'test1' => 'integer', 19 | 'test2' => 'integer', 20 | 'test3' => 'integer', 21 | 'test4' => 'integer', 22 | ]; 23 | $writer = new Ellumilel\ExcelWriter(); 24 | $writer->setAuthor('Tester'); 25 | $writer->writeSheetHeader('Sheet1', $header); 26 | $writer->writeSheetRow('Sheet1', [1, 2, 3, 4]); 27 | $writer->writeToFile($output_file); 28 | 29 | 30 | $this->assertEquals(true, file_exists($output_file)); 31 | 32 | unlink($output_file); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./XLSX/XLSXtest.php 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------