Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Collection;
|
||||
|
||||
use ArrayObject;
|
||||
use AppendIterator;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use Xentral\Components\Exporter\Exception\InvalidArgumentException;
|
||||
|
||||
final class DataCollection implements Iterator
|
||||
{
|
||||
/** @var AppendIterator $data */
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @param array|Iterator $data
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(...$data)
|
||||
{
|
||||
$this->data = new AppendIterator();
|
||||
foreach ($data as $item) {
|
||||
$this->append($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|Iterator|IteratorAggregate $data
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function append($data)
|
||||
{
|
||||
$type = gettype($data);
|
||||
if ($type === 'object') {
|
||||
$type = get_class($data);
|
||||
if ($data instanceof Iterator) {
|
||||
$type = 'Iterator';
|
||||
}
|
||||
if ($data instanceof IteratorAggregate) {
|
||||
$type = 'IteratorAggregate';
|
||||
}
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'array':
|
||||
$this->data->append((new ArrayObject($data))->getIterator());
|
||||
break;
|
||||
|
||||
case 'Iterator':
|
||||
$this->data->append($data);
|
||||
break;
|
||||
|
||||
case 'IteratorAggregate':
|
||||
$this->data->append($data->getIterator());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('Unsupported type "%s".', $type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
*
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->data->current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
$this->data->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
*
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->data->key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
*
|
||||
* @return boolean Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->data->valid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->data->rewind();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Collection;
|
||||
|
||||
use ArrayObject;
|
||||
use Closure;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use Xentral\Components\Exporter\Exception\InvalidArgumentException;
|
||||
use Xentral\Components\Exporter\Exception\InvalidReturnTypeException;
|
||||
|
||||
final class FormatterCollection implements Iterator
|
||||
{
|
||||
/** @var Iterator $data */
|
||||
private $data;
|
||||
|
||||
/** @var callable $callback */
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* @param array|Iterator $data
|
||||
* @param callable|Closure $callback
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($data, $callback)
|
||||
{
|
||||
if (!is_callable($callback, false)) {
|
||||
throw new InvalidArgumentException('Callback is not callable');
|
||||
}
|
||||
$this->callback = $callback;
|
||||
|
||||
$type = gettype($data);
|
||||
if ($type === 'object') {
|
||||
$type = get_class($data);
|
||||
if ($data instanceof Iterator) {
|
||||
$type = 'Iterator';
|
||||
}
|
||||
if ($data instanceof IteratorAggregate) {
|
||||
$type = 'IteratorAggregate';
|
||||
}
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'array':
|
||||
$this->data = (new ArrayObject($data))->getIterator();
|
||||
break;
|
||||
|
||||
case 'Iterator':
|
||||
$this->data = $data;
|
||||
break;
|
||||
|
||||
case 'IteratorAggregate':
|
||||
$this->data = $data->getIterator();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('Unsupported type "%s".', $type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
*
|
||||
* @throws InvalidReturnTypeException
|
||||
*
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
$result = call_user_func($this->callback, $this->data->current());
|
||||
|
||||
if (!is_array($result)) {
|
||||
throw new InvalidReturnTypeException('Formatter return type is invalid . Callable must return an array.');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
$this->data->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
*
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->data->key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
*
|
||||
* @return boolean Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->data->valid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->data->rewind();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Csv;
|
||||
|
||||
final class CsvConfig
|
||||
{
|
||||
/** @var string $delimiter */
|
||||
private $delimiter;
|
||||
|
||||
/** @var string $enclosure */
|
||||
private $enclosure;
|
||||
|
||||
/** @var string $escapeChar */
|
||||
private $escapeChar;
|
||||
|
||||
/** @var string $sourceCharset */
|
||||
private $sourceCharset;
|
||||
|
||||
/** @var string $targetCharset */
|
||||
private $targetCharset;
|
||||
|
||||
/** @var bool $forceEnclosureEnabled */
|
||||
private $forceEnclosureEnabled;
|
||||
|
||||
/**
|
||||
* @param string $delimiter
|
||||
* @param string $enclosure
|
||||
* @param string $escapeChar
|
||||
* @param string $targetCharset
|
||||
* @param string $sourceCharset
|
||||
* @param bool $forceEnclosureEnabled
|
||||
*/
|
||||
public function __construct(
|
||||
$delimiter = ',',
|
||||
$enclosure = '"',
|
||||
$escapeChar = "\\",
|
||||
$targetCharset = 'UTF-8',
|
||||
$sourceCharset = 'UTF-8',
|
||||
$forceEnclosureEnabled = false
|
||||
) {
|
||||
$this->delimiter = $delimiter;
|
||||
$this->enclosure = $enclosure;
|
||||
$this->escapeChar = $escapeChar;
|
||||
$this->targetCharset = $targetCharset;
|
||||
$this->sourceCharset = $sourceCharset;
|
||||
$this->forceEnclosureEnabled = $forceEnclosureEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDelimiter()
|
||||
{
|
||||
return $this->delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEnclosure()
|
||||
{
|
||||
return $this->enclosure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEscapeChar()
|
||||
{
|
||||
return $this->escapeChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSourceCharset()
|
||||
{
|
||||
return $this->sourceCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTargetCharset()
|
||||
{
|
||||
return $this->targetCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $delimiter
|
||||
*/
|
||||
public function setDelimiter($delimiter)
|
||||
{
|
||||
$this->delimiter = $delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $enclosure
|
||||
*/
|
||||
public function setEnclosure($enclosure)
|
||||
{
|
||||
$this->enclosure = $enclosure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $escapeChar
|
||||
*/
|
||||
public function setEscapeChar($escapeChar)
|
||||
{
|
||||
$this->escapeChar = $escapeChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sourceCharset
|
||||
*/
|
||||
public function setSourceCharset($sourceCharset)
|
||||
{
|
||||
$this->sourceCharset = $sourceCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $targetCharset
|
||||
*/
|
||||
public function setTargetCharset($targetCharset)
|
||||
{
|
||||
$this->targetCharset = $targetCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isForceEnclosureEnabled()
|
||||
{
|
||||
return $this->forceEnclosureEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $forceEnclosureEnabled
|
||||
*/
|
||||
public function setForceEnclosureEnabled($forceEnclosureEnabled)
|
||||
{
|
||||
$this->forceEnclosureEnabled = $forceEnclosureEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Csv;
|
||||
|
||||
use Iterator;
|
||||
use Xentral\Components\Exporter\Exception\FileExistsException;
|
||||
use Xentral\Components\Exporter\Exception\InvalidResourceException;
|
||||
|
||||
final class CsvExporter
|
||||
{
|
||||
/** @var CsvConfig $config */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @param CsvConfig|null $config
|
||||
*/
|
||||
public function __construct(CsvConfig $config = null)
|
||||
{
|
||||
if ($config === null) {
|
||||
$config = new CsvConfig();
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath Resource used for writing
|
||||
* @param array|Iterator $data Multi-dimentional array, Generator or Iterator
|
||||
*
|
||||
* @throws FileExistsException|InvalidResourceException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function export($filePath, $data)
|
||||
{
|
||||
$resource = $this->exportToResource($filePath, $data);
|
||||
fclose($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as ::export() beside that the created resource will be returned
|
||||
*
|
||||
* @param string $filePath Resource used for writing
|
||||
* @param array|Iterator $data Multi-dimentional array, Generator or Iterator
|
||||
*
|
||||
* @throws FileExistsException|InvalidResourceException
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function exportToResource($filePath, $data)
|
||||
{
|
||||
if (is_file($filePath)) {
|
||||
throw new FileExistsException(sprintf('File creation failed. File "%s" already exists.', $filePath));
|
||||
}
|
||||
|
||||
// 'x+' = Create and open for reading and writing.
|
||||
// File pointer will be placed at the beginning of the file.
|
||||
// If the file already exists `fopen` will return false.
|
||||
// 'b' = Enable binary mode
|
||||
$resource = @fopen($filePath, 'x+b');
|
||||
if ($resource === false) {
|
||||
throw new InvalidResourceException(sprintf('Failed to open resource for file path "%s".', $filePath));
|
||||
}
|
||||
|
||||
$writer = new CsvWriter($resource, $this->config);
|
||||
$writer->writeLines($data);
|
||||
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Csv;
|
||||
|
||||
use Iterator;
|
||||
use Xentral\Components\Exporter\Exception\InvalidResourceException;
|
||||
use Xentral\Components\Exporter\Exception\PhpExtensionMissingException;
|
||||
|
||||
final class CsvWriter
|
||||
{
|
||||
/** @var resource $handle */
|
||||
private $handle;
|
||||
|
||||
/** @var CsvConfig $config */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @param CsvConfig|null $config
|
||||
*
|
||||
* @throws InvalidResourceException If resource is not writable or invalid
|
||||
* @throws PhpExtensionMissingException If mbstring is missing
|
||||
*/
|
||||
public function __construct($handle, CsvConfig $config = null)
|
||||
{
|
||||
if (!is_resource($handle)) {
|
||||
throw new InvalidResourceException('First parameter is not a valid resource.');
|
||||
}
|
||||
if (!$this->isStreamWritable($handle)) {
|
||||
throw new InvalidResourceException('Resource is not writable.');
|
||||
}
|
||||
if (!function_exists('mb_convert_encoding')) {
|
||||
throw new PhpExtensionMissingException('Required PHP extension "mbstring" is missing.');
|
||||
}
|
||||
if ($config === null) {
|
||||
$config = new CsvConfig();
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
$this->handle = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|Iterator $lines
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeLines($lines)
|
||||
{
|
||||
foreach ($lines as $line) {
|
||||
$this->writeLine($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $line
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeLine($line)
|
||||
{
|
||||
if ($this->config->isForceEnclosureEnabled()) {
|
||||
$fields = $this->encloseAllValues($line);
|
||||
fwrite(
|
||||
$this->handle,
|
||||
sprintf(
|
||||
"%s\n",
|
||||
implode($this->config->getDelimiter(), $this->convertCharset($fields))
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fputcsv(
|
||||
$this->handle,
|
||||
$this->convertCharset($line),
|
||||
$this->config->getDelimiter(),
|
||||
$this->config->getEnclosure(),
|
||||
$this->config->getEscapeChar()
|
||||
);
|
||||
}
|
||||
|
||||
/***
|
||||
* @param array $line
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function encloseAllValues($line)
|
||||
{
|
||||
$escapeChar = $this->config->getEscapeChar();
|
||||
$enclosure = $this->config->getEnclosure();
|
||||
$result = [];
|
||||
foreach ($line as $key => $value) {
|
||||
$value = str_replace($escapeChar . $enclosure, $enclosure, $value);
|
||||
$value = str_replace($enclosure, $escapeChar . $enclosure, $value);
|
||||
$result[$key] = sprintf('%1$s%2$s%1$s', $enclosure, $value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $line
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function convertCharset($line)
|
||||
{
|
||||
if ($this->config->getSourceCharset() === $this->config->getTargetCharset()) {
|
||||
return $line; // No conversion needed
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($line as $key => $cellData) {
|
||||
$result[$key] = mb_convert_encoding(
|
||||
$cellData,
|
||||
$this->config->getTargetCharset(),
|
||||
$this->config->getSourceCharset()
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isStreamWritable($handle)
|
||||
{
|
||||
$meta = stream_get_meta_data($handle);
|
||||
$currentMode = $meta['mode'];
|
||||
|
||||
$writeModes = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
|
||||
foreach ($writeModes as $writeMode) {
|
||||
if (strpos($currentMode, $writeMode) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use Xentral\Core\Exception\ComponentExceptionInterface;
|
||||
|
||||
interface ExporterExceptionInterface extends ComponentExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class FileExistsException extends RuntimeException implements ExporterExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use InvalidArgumentException as SplInvalidArgumentException;
|
||||
|
||||
class InvalidArgumentException extends SplInvalidArgumentException implements ExporterExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidJsonException extends Exception implements ExporterExceptionInterface
|
||||
{
|
||||
/**
|
||||
* @param int $errorCode
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromJsonError($errorCode)
|
||||
{
|
||||
$exception = new self(self::mapJsonError($errorCode));
|
||||
return $exception;
|
||||
}
|
||||
|
||||
private static function mapJsonError($jsonError)
|
||||
{
|
||||
switch ($jsonError) {
|
||||
case JSON_ERROR_NONE:
|
||||
$msg = 'Unknown error';
|
||||
break;
|
||||
case JSON_ERROR_DEPTH:
|
||||
$msg = 'The maximum stack depth has been exceeded';
|
||||
break;
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
$msg = 'Invalid or malformed JSON';
|
||||
break;
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
$msg = 'Control character error, possibly incorrectly encoded';
|
||||
break;
|
||||
case JSON_ERROR_SYNTAX:
|
||||
$msg = 'Syntax error';
|
||||
break;
|
||||
case JSON_ERROR_UTF8:
|
||||
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||
break;
|
||||
case JSON_ERROR_RECURSION:
|
||||
$msg = 'One or more recursive references in the value to be encoded';
|
||||
break;
|
||||
case JSON_ERROR_INF_OR_NAN:
|
||||
$msg = 'One or more NAN or INF values in the value to be encoded';
|
||||
break;
|
||||
case JSON_ERROR_UNSUPPORTED_TYPE:
|
||||
$msg = 'A value of a type that cannot be encoded was given';
|
||||
break;
|
||||
case JSON_ERROR_INVALID_PROPERTY_NAME:
|
||||
$msg = 'A property name that cannot be encoded was given';
|
||||
break;
|
||||
case JSON_ERROR_UTF16:
|
||||
$msg = 'Malformed UTF-16 characters, possibly incorrectly encoded';
|
||||
break;
|
||||
default:
|
||||
$msg = 'Unknown Error';
|
||||
}
|
||||
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class InvalidResourceException extends RuntimeException implements ExporterExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class InvalidReturnTypeException extends LogicException implements ExporterExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class PhpExtensionMissingException extends RuntimeException implements ExporterExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ResourceWriteException extends Exception implements ExporterExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Json;
|
||||
|
||||
final class JsonConfig
|
||||
{
|
||||
/** @var int $options */
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/manual/en/json.constants.php
|
||||
*
|
||||
* @param int $options
|
||||
*/
|
||||
public function __construct($options = 0)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Json;
|
||||
|
||||
use Xentral\Components\Exporter\Exception\FileExistsException;
|
||||
use Xentral\Components\Exporter\Exception\InvalidResourceException;
|
||||
|
||||
final class JsonExporter
|
||||
{
|
||||
/** @var JsonConfig $config */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @param JsonConfig|null $config
|
||||
*/
|
||||
public function __construct(JsonConfig $config = null)
|
||||
{
|
||||
if ($config === null) {
|
||||
$config = new JsonConfig();
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath Resource used for writing
|
||||
* @param array $data Multi-dimentional array
|
||||
*
|
||||
* @throws \Xentral\Components\Exporter\Exception\InvalidJsonException
|
||||
* @throws \Xentral\Components\Exporter\Exception\ResourceWriteException
|
||||
* @return void
|
||||
*/
|
||||
public function export($filePath, $data)
|
||||
{
|
||||
$resource = $this->exportToResource($filePath, $data);
|
||||
fclose($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as ::export() beside that the created resource will be returned
|
||||
*
|
||||
* @param string $filePath Resource used for writing
|
||||
* @param array $data Multi-dimentional array
|
||||
*
|
||||
* @throws \Xentral\Components\Exporter\Exception\InvalidJsonException
|
||||
* @throws \Xentral\Components\Exporter\Exception\ResourceWriteException
|
||||
* @return resource
|
||||
*/
|
||||
public function exportToResource($filePath, $data)
|
||||
{
|
||||
if (is_file($filePath)) {
|
||||
throw new FileExistsException(sprintf('File creation failed. File "%s" already exists.', $filePath));
|
||||
}
|
||||
|
||||
// 'x+' = Create and open for reading and writing.
|
||||
// File pointer will be placed at the beginning of the file.
|
||||
// If the file already exists `fopen` will return false.
|
||||
// 'b' = Enable binary mode
|
||||
$resource = @fopen($filePath, 'x+b');
|
||||
if ($resource === false) {
|
||||
throw new InvalidResourceException(sprintf('Failed to open resource for file path "%s".', $filePath));
|
||||
}
|
||||
|
||||
$writer = new JsonWriter($resource, $this->config);
|
||||
$writer->write($data);
|
||||
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Exporter\Json;
|
||||
|
||||
use JsonSerializable;
|
||||
use Xentral\Components\Exporter\Exception\InvalidJsonException;
|
||||
use Xentral\Components\Exporter\Exception\InvalidResourceException;
|
||||
use Xentral\Components\Exporter\Exception\PhpExtensionMissingException;
|
||||
use Xentral\Components\Exporter\Exception\ResourceWriteException;
|
||||
|
||||
final class JsonWriter
|
||||
{
|
||||
/** @var resource $handle */
|
||||
private $handle;
|
||||
|
||||
/** @var JsonConfig $config */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @param JsonConfig|null $config
|
||||
*
|
||||
* @throws InvalidResourceException If resource is not writable or invalid
|
||||
* @throws PhpExtensionMissingException If mbstring is missing
|
||||
*/
|
||||
public function __construct($handle, JsonConfig $config = null)
|
||||
{
|
||||
if (!is_resource($handle)) {
|
||||
throw new InvalidResourceException('First parameter is not a valid resource.');
|
||||
}
|
||||
if (!$this->isStreamWritable($handle)) {
|
||||
throw new InvalidResourceException('Resource is not writable.');
|
||||
}
|
||||
if (!function_exists('mb_convert_encoding')) {
|
||||
throw new PhpExtensionMissingException('Required PHP extension "mbstring" is missing.');
|
||||
}
|
||||
if ($config === null) {
|
||||
$config = new JsonConfig();
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
$this->handle = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|JsonSerializable $data
|
||||
*
|
||||
* @throws PhpExtensionMissingException If json is missing
|
||||
* @throws ResourceWriteException
|
||||
* @throws InvalidJsonException
|
||||
*/
|
||||
public function write($data)
|
||||
{
|
||||
if (!function_exists('json_encode')) {
|
||||
throw new PhpExtensionMissingException('Required PHP extension "json" is missing.');
|
||||
}
|
||||
|
||||
$jsonOptions = $this->config->getOptions();
|
||||
$jsonString = @json_encode($data, $jsonOptions);
|
||||
|
||||
if ($jsonString === false) {
|
||||
throw InvalidJsonException::fromJsonError(json_last_error());
|
||||
}
|
||||
|
||||
$writeResult = @fwrite($this->handle, $jsonString);
|
||||
if ($writeResult === false) {
|
||||
throw new ResourceWriteException("JSON could not be written to resource.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isStreamWritable($handle)
|
||||
{
|
||||
$meta = stream_get_meta_data($handle);
|
||||
$currentMode = $meta['mode'];
|
||||
|
||||
$writeModes = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
|
||||
foreach ($writeModes as $writeMode) {
|
||||
if (strpos($currentMode, $writeMode) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
# CSV-Exporter
|
||||
|
||||
## Datei erstellen
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Xentral\Components\Database\Database;
|
||||
use Xentral\Components\Exporter\Csv\CsvExporter;
|
||||
|
||||
/** @var Database $db */
|
||||
$db = $container->get('Database');
|
||||
$data = $db->yieldAll('SELECT e.* FROM employees AS e');
|
||||
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'employees') . '.csv';
|
||||
|
||||
$exporter = new CsvExporter();
|
||||
$exporter->export($filePath, $data);
|
||||
```
|
||||
|
||||
##### Rückgabewerte
|
||||
|
||||
* Die `export()` Methode liefert nichts zurück.
|
||||
* Die `exportToResource()` gibt die geöffnete Ressource zurück. Die Ressource muss manuell (mit `fclose()`)
|
||||
geschlossen werden. Der Dateizeiger ist auf `EOF` platziert.
|
||||
|
||||
##### Funktionsparameter
|
||||
|
||||
Beide `export*()`-Methoden nehmen die gleichen Parameter entgegen.
|
||||
|
||||
* Als erster Parameter wird ein absoluter Dateipfad erwartet. Alternativ kann ein Stream Wrapper angegeben werden.
|
||||
Beispiele:
|
||||
* `php://output` um die CSV direkt auszugeben (`echo`).
|
||||
* `php://memory` um in den Arbeitsspeicher zu schreiben.
|
||||
* `php://temp` um in eine temporäre Datei zu schreiben.
|
||||
|
||||
* Der zweite Parameter nimmt die Daten entgegen die als CSV exportiert werden sollen. Folgende Typen sind erlaubt:
|
||||
* `array`
|
||||
* `Generator`
|
||||
* `Iterator`
|
||||
* `IteratorAggregate`
|
||||
|
||||
##### Konstruktor-Parameter
|
||||
|
||||
* Der erste Konstruktor-Parameter ist optional und nimmt die CSV-Konfiguration entgegen;
|
||||
siehe _Erweiterte Beispiele_ > _CSV konfigurieren_.
|
||||
|
||||
## Datei-Download erstellen
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Xentral\Components\Database\Database;
|
||||
use Xentral\Components\Exporter\Csv\CsvExporter;
|
||||
|
||||
/** @var Database $db */
|
||||
$db = $container->get('Database');
|
||||
$data = $db->yieldAll('SELECT e.* FROM employees AS e');
|
||||
|
||||
$exporter = new CsvExporter();
|
||||
$resource = $exporter->exportToResource('php://memory', $data);
|
||||
rewind($resource);
|
||||
$stat = fstat($resource);
|
||||
|
||||
header('Cache-Control: must-revalidate');
|
||||
header('Pragma: must-revalidate');
|
||||
header('Content-type: text/csv');
|
||||
header('Content-Disposition: attachment; filename="employees.csv"');
|
||||
header('Content-Length: ' . $stat['size']);
|
||||
fpassthru($resource);
|
||||
fclose($resource);
|
||||
```
|
||||
|
||||
|
||||
## Erweiterte Beispiele
|
||||
|
||||
### CSV konfigurieren
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Xentral\Components\Exporter\Csv\CsvConfig;
|
||||
use Xentral\Components\Exporter\Csv\CsvExporter;
|
||||
|
||||
$csvConfig = new CsvConfig();
|
||||
$csvConfig->setDelimiter(';');
|
||||
$csvConfig->setEnclosure('"');
|
||||
$csvConfig->setSourceCharset('UTF-8');
|
||||
$csvConfig->setTargetCharset('ISO-8859-1');
|
||||
|
||||
$exporter = new CsvExporter($csvConfig);
|
||||
$exporter->export($filePath, $data);
|
||||
```
|
||||
|
||||
### Daten zusammenführen
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Xentral\Components\Exporter\Csv\CsvExporter;
|
||||
use Xentral\Components\Exporter\Collection\DataCollection;
|
||||
|
||||
$collection = new DataCollection($headline, $employees);
|
||||
|
||||
$exporter = new CsvExporter();
|
||||
$exporter->export($filePath, $collection);
|
||||
```
|
||||
|
||||
### Daten formatieren
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Xentral\Components\Exporter\Csv\CsvExporter;
|
||||
use Xentral\Components\Exporter\Collection\FormatterCollection;
|
||||
|
||||
$formatter = new FormatterCollection($data, function ($row) {
|
||||
$row['fullname'] = $row['firstname'] . ' ' . $row['lastname'];
|
||||
|
||||
return $row;
|
||||
});
|
||||
|
||||
$exporter = new CsvExporter();
|
||||
$exporter->export($filePath, $formatter);
|
||||
```
|
||||
|
||||
Statt einer anonymen Funktion (`Closure`) kann ein `callable` übergeben werden.
|
||||
Reference in New Issue
Block a user