| Server IP : 139.59.63.204 / Your IP : 216.73.217.62 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ubuntu-s-1vcpu-1gb-blr1-01 6.8.0-110-generic #110-Ubuntu SMP PREEMPT_DYNAMIC Thu Mar 19 15:09:20 UTC 2026 x86_64 User : root ( 0) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/abyogasms.com/vendor/rap2hpoutre/fast-excel/src/ |
Upload File : |
<?php
namespace Rap2hpoutre\FastExcel;
use DateTimeInterface;
use Illuminate\Support\Collection;
use InvalidArgumentException;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Writer\Common\AbstractOptions;
use OpenSpout\Writer\WriterInterface;
use Traversable;
/**
* Trait Exportable.
*
* @property bool $transpose
* @property bool $with_header
* @property callable|null $writer_configurator
* @property \Illuminate\Support\Collection $data
*/
trait Exportable
{
/**
* @var Style
*/
private $header_style;
private $rows_style;
/** @var Style[] */
private $header_column_styles = [];
/** @var Style[] */
private $column_styles = [];
/** @var bool */
private $string_values = false;
/** @var array<string, string> */
private $column_formats = [];
/**
* @param AbstractOptions $options
*
* @return mixed
*/
abstract protected function setOptions(&$options);
/** @param Style[] $styles */
public function setHeaderColumnStyles($styles): static
{
$this->header_column_styles = $styles;
return $this;
}
/** @param Style[] $styles */
public function setColumnStyles($styles): static
{
$this->column_styles = $styles;
return $this;
}
/**
* Export every scalar value as a string (text cell). Useful to preserve
* leading zeros or long numeric IDs (e.g. phone numbers) that would
* otherwise be written as numbers. Per-column setColumnFormat() rules take
* precedence over this flag.
*
* @param bool $enabled
*/
public function stringValues(bool $enabled = true): static
{
$this->string_values = $enabled;
return $this;
}
/**
* Force the cell type for specific columns, keyed by column name, e.g.
* ['phone' => 'string', 'price' => 'number']. A 'string' column is written
* as text; a 'number' column casts numeric strings back to int/float. These
* rules win over stringValues().
*
* @param array<string, string> $formats
*/
public function setColumnFormat(array $formats): static
{
$this->column_formats = $formats;
return $this;
}
/**
* @param string $path
* @param callable|null $callback
*
* @throws \OpenSpout\Common\Exception\InvalidArgumentException
* @throws \OpenSpout\Common\Exception\UnsupportedTypeException
* @throws \OpenSpout\Writer\Exception\WriterNotOpenedException
* @throws \OpenSpout\Common\Exception\IOException
*
* @return string
*/
public function export($path, ?callable $callback = null)
{
self::exportOrDownload($path, 'openToFile', $callback);
return realpath($path) ?: $path;
}
/**
* @param $path
* @param callable|null $callback
*
* @throws \OpenSpout\Common\Exception\InvalidArgumentException
* @throws \OpenSpout\Common\Exception\UnsupportedTypeException
* @throws \OpenSpout\Writer\Exception\WriterNotOpenedException
* @throws \OpenSpout\Common\Exception\IOException
*
* @return \Symfony\Component\HttpFoundation\StreamedResponse|string
*/
public function download($path, ?callable $callback = null)
{
if (method_exists(response(), 'streamDownload')) {
return response()->streamDownload(function () use ($path, $callback) {
self::exportOrDownload($path, 'openToBrowser', $callback);
}, $path);
}
self::exportOrDownload($path, 'openToBrowser', $callback);
return '';
}
/**
* @param $path
* @param string $function
* @param callable|null $callback
*
* @throws \OpenSpout\Common\Exception\IOException
* @throws \OpenSpout\Common\Exception\InvalidArgumentException
* @throws \OpenSpout\Common\Exception\UnsupportedTypeException
* @throws \OpenSpout\Writer\Exception\WriterNotOpenedException
* @throws \OpenSpout\Common\Exception\SpoutException
*/
private function exportOrDownload($path, $function, ?callable $callback = null)
{
$writer = $this->makeWriter($path);
$has_sheets = ($writer instanceof \OpenSpout\Writer\XLSX\Writer || $writer instanceof \OpenSpout\Writer\ODS\Writer);
// CSV (and any other single-sheet format) cannot hold multiple sheets.
// Fail with a clear message instead of a cryptic "undefined method
// ...::getCurrentSheet()" fatal further down.
if (!$has_sheets && $this->data instanceof SheetCollection && $this->data->count() > 1) {
throw new InvalidArgumentException(
'The "'.pathinfo($path, PATHINFO_EXTENSION).'" format does not support multiple sheets; use xlsx or ods.'
);
}
$writer->$function($path);
// It can export one sheet (Collection) or N sheets (SheetCollection)
$data = $this->transpose ? $this->transposeData() : ($this->data instanceof SheetCollection ? $this->data : collect([$this->data]));
$last_key = $data->keys()->last();
foreach ($data as $key => $collection) {
if ($collection instanceof Collection) {
$this->writeRowsFromCollection($writer, $collection, $callback);
} elseif ($collection instanceof Traversable) {
$this->writeRowsFromGenerator($writer, $collection, $callback);
} elseif (is_array($collection)) {
$this->writeRowsFromArray($writer, $collection, $callback);
} else {
throw new InvalidArgumentException('Unsupported type for $data');
}
if ($has_sheets && is_string($key)) {
$writer->getCurrentSheet()->setName($key);
}
if ($has_sheets && $last_key !== $key) {
$writer->addNewSheetAndMakeItCurrent();
}
}
$writer->close();
}
private function makeWriter(string $path): WriterInterface
{
$extension = $this->resolveWriterExtension($path);
$options = match ($extension) {
'csv' => new \OpenSpout\Writer\CSV\Options(),
'ods' => new \OpenSpout\Writer\ODS\Options(),
default => new \OpenSpout\Writer\XLSX\Options(),
};
$this->setOptions($options);
if (is_callable($this->writer_configurator ?? null)) {
$writer = call_user_func($this->writer_configurator, $options, $extension);
if ($writer instanceof WriterInterface) {
return $writer;
}
}
return match ($extension) {
'csv' => new \OpenSpout\Writer\CSV\Writer($options),
'ods' => new \OpenSpout\Writer\ODS\Writer($options),
default => new \OpenSpout\Writer\XLSX\Writer($options),
};
}
private function resolveWriterExtension(string $path): string
{
if (str_ends_with($path, 'csv')) {
return 'csv';
}
if (str_ends_with($path, 'ods')) {
return 'ods';
}
return 'xlsx';
}
/**
* Transpose data from rows to columns.
*
* @return SheetCollection
*/
private function transposeData()
{
$data = $this->data instanceof SheetCollection ? $this->data : collect([$this->data]);
$transposedData = [];
foreach ($data as $key => $collection) {
foreach ($collection as $row => $columns) {
foreach ($columns as $column => $value) {
$transposedData[$key][$column][$row] = $value;
}
}
}
return new SheetCollection($transposedData);
}
/**
* Write collection rows to the writer, streaming row by row.
*
* @SuppressWarnings(PHPMD.StaticAccess)
*/
private function writeRowsFromCollection($writer, Collection $collection, ?callable $callback = null)
{
// Apply callback
if ($callback) {
$collection->transform(function ($value) use ($callback) {
return $callback($value);
});
}
if ($collection->isEmpty()) {
if ($this->data instanceof SheetCollection) {
$this->writePlaceholderRowForEmptySheet($writer);
}
return;
}
// Prepare collection (i.e remove non-string)
$this->prepareCollection($collection);
// Add header row.
if ($this->with_header) {
$this->writeHeader($writer, $collection->first());
}
$use_styles = $this->rows_style || $this->column_styles;
// Write rows one by one so Row objects can be garbage-collected as
// they are written, instead of materializing them all up front.
foreach ($collection as $values) {
// Row::fromValues works only with arrays
if (!is_array($values)) {
$values = $values->toArray();
}
if ($use_styles) {
// Column styles are matched against the value keys; use positional
// keys so numeric style indexes work with associative rows.
$writer->addRow($this->createRow(array_values($values), $this->rows_style, $this->column_styles));
} else {
$writer->addRow(Row::fromValues($values));
}
}
}
private function writeRowsFromGenerator($writer, Traversable $generator, ?callable $callback = null)
{
$hasRows = false;
foreach ($generator as $key => $item) {
$hasRows = true;
// Apply callback
if ($callback) {
$item = $callback($item);
}
// Prepare row (i.e remove non-string)
$item = $this->transformRow($item);
// Add header row.
if ($this->with_header && $key === 0) {
$this->writeHeader($writer, $item);
}
// Write rows (one by one).
$writer->addRow($this->createRow($item, $this->rows_style, $this->column_styles));
}
if (!$hasRows && $this->data instanceof SheetCollection) {
$this->writePlaceholderRowForEmptySheet($writer);
}
}
private function writeRowsFromArray($writer, array $array, ?callable $callback = null)
{
$collection = collect($array);
// Rows must be arrays or objects; anything else (e.g. a flat list of
// scalars) is silently skipped, as before. Empty-sheet placeholder
// handling is done by writeRowsFromCollection.
if ($collection->isNotEmpty() && !is_object($collection->first()) && !is_array($collection->first())) {
return;
}
$this->writeRowsFromCollection($writer, $collection, $callback);
}
private function writeHeader($writer, $first_row)
{
if ($first_row === null) {
return;
}
$keys = array_keys(is_array($first_row) ? $first_row : $first_row->toArray());
$writer->addRow($this->createRow($keys, $this->header_style, $this->header_column_styles));
}
/**
* Prepare collection by removing non string if required.
*/
protected function prepareCollection(Collection $collection)
{
$need_conversion = false;
$first_row = $collection->first();
if (!$first_row) {
return;
}
foreach ($first_row as $item) {
if (!is_string($item)) {
$need_conversion = true;
break;
}
}
if ($need_conversion || $this->string_values || $this->column_formats) {
$collection->transform(fn ($data) => $this->transformRow($data));
}
}
/**
* Transform one row (i.e remove non-string).
*/
private function transformRow($data): array
{
$row = [];
foreach (is_array($data) ? $data : collect($data)->all() as $key => $value) {
$value = is_null($value) ? '' : $this->formatValue($value, $key);
if (is_string($value) || is_int($value) || is_float($value) || $value instanceof DateTimeInterface) {
$row[$key] = $value;
}
}
return $row;
}
/**
* Apply the configured export format to a single value. A per-column rule
* (setColumnFormat) wins over the global stringValues() flag. Dates and
* non-numeric strings are always left untouched.
*
* @param mixed $value
* @param int|string $key
*
* @return mixed
*/
private function formatValue($value, $key)
{
$format = $this->column_formats[$key] ?? ($this->string_values ? 'string' : null);
if ($format === 'string' && (is_int($value) || is_float($value))) {
return (string) $value;
}
if ($format === 'number' && is_string($value) && is_numeric($value)) {
return $value + 0;
}
return $value;
}
/**
* @param Style $style
*
* @return Exportable
*/
public function headerStyle(Style $style)
{
$this->header_style = $style;
return $this;
}
/**
* @param Style $style
*
* @return Exportable
*/
public function rowsStyle(Style $style)
{
$this->rows_style = $style;
return $this;
}
/**
* OpenSpout multi-sheet workbooks require at least one non-empty row per worksheet.
* Empty rows are skipped by the XLSX writer and would otherwise produce a corrupt file.
*
* @param \OpenSpout\Writer\WriterInterface $writer
*/
private function writePlaceholderRowForEmptySheet($writer): void
{
if (!$writer instanceof \OpenSpout\Writer\XLSX\Writer && !$writer instanceof \OpenSpout\Writer\ODS\Writer) {
return;
}
$writer->addRow($this->createRow([' ']));
}
/**
* Create openspout row from values with optional row and cell styling.
*
* @SuppressWarnings(PHPMD.StaticAccess)
*/
private function createRow(array $values = [], ?Style $rows_style = null, array $column_styles = []): Row
{
return Row::fromValuesWithStyles($values, $rows_style, $column_styles);
}
}