Sample.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Helper;
  3. use PhpOffice\PhpSpreadsheet\Chart\Chart;
  4. use PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer;
  5. use PhpOffice\PhpSpreadsheet\IOFactory;
  6. use PhpOffice\PhpSpreadsheet\Settings;
  7. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  8. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  9. use PhpOffice\PhpSpreadsheet\Writer\IWriter;
  10. use RecursiveDirectoryIterator;
  11. use RecursiveIteratorIterator;
  12. use RecursiveRegexIterator;
  13. use ReflectionClass;
  14. use RegexIterator;
  15. use RuntimeException;
  16. use Throwable;
  17. /**
  18. * Helper class to be used in sample code.
  19. */
  20. class Sample
  21. {
  22. /**
  23. * Returns whether we run on CLI or browser.
  24. */
  25. public function isCli(): bool
  26. {
  27. return PHP_SAPI === 'cli';
  28. }
  29. /**
  30. * Return the filename currently being executed.
  31. */
  32. public function getScriptFilename(): string
  33. {
  34. return basename($_SERVER['SCRIPT_FILENAME'], '.php');
  35. }
  36. /**
  37. * Whether we are executing the index page.
  38. */
  39. public function isIndex(): bool
  40. {
  41. return $this->getScriptFilename() === 'index';
  42. }
  43. /**
  44. * Return the page title.
  45. */
  46. public function getPageTitle(): string
  47. {
  48. return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename();
  49. }
  50. /**
  51. * Return the page heading.
  52. */
  53. public function getPageHeading(): string
  54. {
  55. return $this->isIndex() ? '' : '<h1>' . str_replace('_', ' ', $this->getScriptFilename()) . '</h1>';
  56. }
  57. /**
  58. * Returns an array of all known samples.
  59. *
  60. * @return string[][] [$name => $path]
  61. */
  62. public function getSamples(): array
  63. {
  64. // Populate samples
  65. $baseDir = realpath(__DIR__ . '/../../../samples');
  66. if ($baseDir === false) {
  67. // @codeCoverageIgnoreStart
  68. throw new RuntimeException('realpath returned false');
  69. // @codeCoverageIgnoreEnd
  70. }
  71. $directory = new RecursiveDirectoryIterator($baseDir);
  72. $iterator = new RecursiveIteratorIterator($directory);
  73. $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
  74. $files = [];
  75. foreach ($regex as $file) {
  76. $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
  77. if (is_array($file)) {
  78. // @codeCoverageIgnoreStart
  79. throw new RuntimeException('str_replace returned array');
  80. // @codeCoverageIgnoreEnd
  81. }
  82. $info = pathinfo($file);
  83. $category = str_replace('_', ' ', $info['dirname'] ?? '');
  84. $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename']));
  85. if (!in_array($category, ['.', 'bootstrap', 'templates']) && $name !== 'Header') {
  86. if (!isset($files[$category])) {
  87. $files[$category] = [];
  88. }
  89. $files[$category][$name] = $file;
  90. }
  91. }
  92. // Sort everything
  93. ksort($files);
  94. foreach ($files as &$f) {
  95. asort($f);
  96. }
  97. return $files;
  98. }
  99. /**
  100. * Write documents.
  101. *
  102. * @param string[] $writers
  103. */
  104. public function write(Spreadsheet $spreadsheet, string $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null, bool $resetActiveSheet = true): void
  105. {
  106. // Set active sheet index to the first sheet, so Excel opens this as the first sheet
  107. if ($resetActiveSheet) {
  108. $spreadsheet->setActiveSheetIndex(0);
  109. }
  110. // Write documents
  111. foreach ($writers as $writerType) {
  112. $path = $this->getFilename($filename, mb_strtolower($writerType));
  113. $writer = IOFactory::createWriter($spreadsheet, $writerType);
  114. $writer->setIncludeCharts($withCharts);
  115. if ($writerCallback !== null) {
  116. $writerCallback($writer);
  117. }
  118. $callStartTime = microtime(true);
  119. $writer->save($path);
  120. $this->logWrite($writer, $path, $callStartTime);
  121. if ($this->isCli() === false) {
  122. // @codeCoverageIgnoreStart
  123. echo '<a href="/download.php?type=' . pathinfo($path, PATHINFO_EXTENSION) . '&name=' . basename($path) . '">Download ' . basename($path) . '</a><br />';
  124. // @codeCoverageIgnoreEnd
  125. }
  126. }
  127. $this->logEndingNotes();
  128. }
  129. protected function isDirOrMkdir(string $folder): bool
  130. {
  131. return \is_dir($folder) || \mkdir($folder);
  132. }
  133. /**
  134. * Returns the temporary directory and make sure it exists.
  135. */
  136. public function getTemporaryFolder(): string
  137. {
  138. $tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
  139. if (!$this->isDirOrMkdir($tempFolder)) {
  140. throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder));
  141. }
  142. return $tempFolder;
  143. }
  144. /**
  145. * Returns the filename that should be used for sample output.
  146. */
  147. public function getFilename(string $filename, string $extension = 'xlsx'): string
  148. {
  149. $originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
  150. return $this->getTemporaryFolder() . '/' . str_replace('.' . $originalExtension, '.' . $extension, basename($filename));
  151. }
  152. /**
  153. * Return a random temporary file name.
  154. */
  155. public function getTemporaryFilename(string $extension = 'xlsx'): string
  156. {
  157. $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-');
  158. if ($temporaryFilename === false) {
  159. // @codeCoverageIgnoreStart
  160. throw new RuntimeException('tempnam returned false');
  161. // @codeCoverageIgnoreEnd
  162. }
  163. unlink($temporaryFilename);
  164. return $temporaryFilename . '.' . $extension;
  165. }
  166. public function log(string $message): void
  167. {
  168. $eol = $this->isCli() ? PHP_EOL : '<br />';
  169. echo ($this->isCli() ? date('H:i:s ') : '') . $message . $eol;
  170. }
  171. /**
  172. * Render chart as part of running chart samples in browser.
  173. * Charts are not rendered in unit tests, which are command line.
  174. *
  175. * @codeCoverageIgnore
  176. */
  177. public function renderChart(Chart $chart, string $fileName, ?Spreadsheet $spreadsheet = null): void
  178. {
  179. if ($this->isCli() === true) {
  180. return;
  181. }
  182. Settings::setChartRenderer(MtJpGraphRenderer::class);
  183. $fileName = $this->getFilename($fileName, 'png');
  184. $title = $chart->getTitle();
  185. $caption = null;
  186. if ($title !== null) {
  187. $calculatedTitle = $title->getCalculatedTitle($spreadsheet);
  188. if ($calculatedTitle !== null) {
  189. $caption = $title->getCaption();
  190. $title->setCaption($calculatedTitle);
  191. }
  192. }
  193. try {
  194. $chart->render($fileName);
  195. $this->log('Rendered image: ' . $fileName);
  196. $imageData = @file_get_contents($fileName);
  197. if ($imageData !== false) {
  198. echo '<div><img src="data:image/gif;base64,' . base64_encode($imageData) . '" /></div>';
  199. } else {
  200. $this->log('Unable to open chart' . PHP_EOL);
  201. }
  202. } catch (Throwable $e) {
  203. $this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL);
  204. }
  205. if (isset($title, $caption)) {
  206. $title->setCaption($caption);
  207. }
  208. Settings::unsetChartRenderer();
  209. }
  210. public function titles(string $category, string $functionName, ?string $description = null): void
  211. {
  212. $this->log(sprintf('%s Functions:', $category));
  213. $description === null
  214. ? $this->log(sprintf('Function: %s()', rtrim($functionName, '()')))
  215. : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.')));
  216. }
  217. public function displayGrid(array $matrix): void
  218. {
  219. $renderer = new TextGrid($matrix, $this->isCli());
  220. echo $renderer->render();
  221. }
  222. public function logCalculationResult(
  223. Worksheet $worksheet,
  224. string $functionName,
  225. string $formulaCell,
  226. ?string $descriptionCell = null
  227. ): void {
  228. if ($descriptionCell !== null) {
  229. $this->log($worksheet->getCell($descriptionCell)->getValue());
  230. }
  231. $this->log($worksheet->getCell($formulaCell)->getValue());
  232. $this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValue());
  233. }
  234. /**
  235. * Log ending notes.
  236. */
  237. public function logEndingNotes(): void
  238. {
  239. // Do not show execution time for index
  240. $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB');
  241. }
  242. /**
  243. * Log a line about the write operation.
  244. */
  245. public function logWrite(IWriter $writer, string $path, float $callStartTime): void
  246. {
  247. $callEndTime = microtime(true);
  248. $callTime = $callEndTime - $callStartTime;
  249. $reflection = new ReflectionClass($writer);
  250. $format = $reflection->getShortName();
  251. $codePath = $this->isCli() ? $path : "<code>$path</code>";
  252. $message = "Write {$format} format to {$codePath} in " . sprintf('%.4f', $callTime) . ' seconds';
  253. $this->log($message);
  254. }
  255. /**
  256. * Log a line about the read operation.
  257. */
  258. public function logRead(string $format, string $path, float $callStartTime): void
  259. {
  260. $callEndTime = microtime(true);
  261. $callTime = $callEndTime - $callStartTime;
  262. $message = "Read {$format} format from <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
  263. $this->log($message);
  264. }
  265. }